From cb734d5b86e910dc3c674c622feb76e7e8cf9bac Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Thu, 5 Mar 2020 17:53:18 +0300 Subject: [PATCH] Download files on phone (#277) --- app/build.gradle | 1 + app/src/main/AndroidManifest.xml | 42 ++++++----- .../anytype/app/AndroidApplication.kt | 1 + .../agileburo/anytype/di/feature/PageDI.kt | 17 ++++- .../agileburo/anytype/di/main/DeviceModule.kt | 34 +++++++++ .../anytype/di/main/MainComponent.kt | 1 + .../agileburo/anytype/ui/page/PageFragment.kt | 3 +- .../main/res/xml/network_security_config.xml | 6 ++ .../core_ui/features/page/BlockAdapter.kt | 6 +- .../core_ui/features/page/BlockView.kt | 4 +- .../core_ui/features/page/BlockViewHolder.kt | 6 +- .../anytype/core_ui/BlockAdapterTest.kt | 3 +- .../anytype/data/auth/other/DataDownloader.kt | 11 +++ .../anytype/data/auth/other/Device.kt | 5 ++ device/.gitignore | 1 + device/build.gradle | 56 ++++++++++++++ device/consumer-rules.pro | 0 device/proguard-rules.pro | 21 ++++++ device/src/main/AndroidManifest.xml | 1 + .../anytype/device/base/AndroidDevice.kt | 11 +++ .../device/download/DeviceDownloader.kt | 39 ++++++++++ device/src/main/res/values/strings.xml | 3 + .../anytype/domain/download/DownloadFile.kt | 36 +++++++++ .../anytype/domain/download/Downloader.kt | 15 ++++ .../presentation/mapper/MapperExtension.kt | 3 +- .../presentation/page/PageViewModel.kt | 19 +++++ .../presentation/page/PageViewModelFactory.kt | 7 +- .../anytype/presentation/MockBlockFactory.kt | 26 +++++++ .../presentation/page/PageViewModelTest.kt | 73 ++++++++++++++++++- settings.gradle | 3 +- 30 files changed, 422 insertions(+), 32 deletions(-) create mode 100644 app/src/main/java/com/agileburo/anytype/di/main/DeviceModule.kt create mode 100644 app/src/main/res/xml/network_security_config.xml create mode 100644 data/src/main/java/com/agileburo/anytype/data/auth/other/DataDownloader.kt create mode 100644 data/src/main/java/com/agileburo/anytype/data/auth/other/Device.kt create mode 100644 device/.gitignore create mode 100644 device/build.gradle create mode 100644 device/consumer-rules.pro create mode 100644 device/proguard-rules.pro create mode 100644 device/src/main/AndroidManifest.xml create mode 100644 device/src/main/java/com/agileburo/anytype/device/base/AndroidDevice.kt create mode 100644 device/src/main/java/com/agileburo/anytype/device/download/DeviceDownloader.kt create mode 100644 device/src/main/res/values/strings.xml create mode 100644 domain/src/main/java/com/agileburo/anytype/domain/download/DownloadFile.kt create mode 100644 domain/src/main/java/com/agileburo/anytype/domain/download/Downloader.kt diff --git a/app/build.gradle b/app/build.gradle index e419f031ca..b1f45de354 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -75,6 +75,7 @@ dependencies { implementation project(':domain') implementation project(':data') + implementation project(':device') implementation project(':persistence') implementation project(':middleware') implementation project(':presentation') diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ed268521be..47d44372b4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,35 +1,39 @@ + package="com.agileburo.anytype"> - - + + + android:allowBackup="true" + android:fullBackupContent="@xml/my_backup_rules" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:networkSecurityConfig="@xml/network_security_config" + android:roundIcon="@mipmap/ic_launcher_round" + android:supportsRtl="true" + android:theme="@style/AppTheme"> + android:windowSoftInputMode="stateHidden|adjustResize"> - - - - + + + + + + - - + + diff --git a/app/src/main/java/com/agileburo/anytype/app/AndroidApplication.kt b/app/src/main/java/com/agileburo/anytype/app/AndroidApplication.kt index 0fe38d983d..033f2e1401 100644 --- a/app/src/main/java/com/agileburo/anytype/app/AndroidApplication.kt +++ b/app/src/main/java/com/agileburo/anytype/app/AndroidApplication.kt @@ -17,6 +17,7 @@ class AndroidApplication : Application() { .dataModule(DataModule()) .configModule(ConfigModule()) .utilModule(UtilModule()) + .deviceModule(DeviceModule()) .build() } diff --git a/app/src/main/java/com/agileburo/anytype/di/feature/PageDI.kt b/app/src/main/java/com/agileburo/anytype/di/feature/PageDI.kt index 223495e782..1a2a680692 100644 --- a/app/src/main/java/com/agileburo/anytype/di/feature/PageDI.kt +++ b/app/src/main/java/com/agileburo/anytype/di/feature/PageDI.kt @@ -3,6 +3,8 @@ package com.agileburo.anytype.di.feature import com.agileburo.anytype.core_utils.di.scope.PerScreen import com.agileburo.anytype.domain.block.interactor.* import com.agileburo.anytype.domain.block.repo.BlockRepository +import com.agileburo.anytype.domain.download.DownloadFile +import com.agileburo.anytype.domain.download.Downloader import com.agileburo.anytype.domain.event.interactor.EventChannel import com.agileburo.anytype.domain.event.interactor.InterceptEvents import com.agileburo.anytype.domain.misc.UrlBuilder @@ -53,7 +55,8 @@ class PageModule { splitBlock: SplitBlock, createPage: CreatePage, documentExternalEventReducer: DocumentExternalEventReducer, - urlBuilder: UrlBuilder + urlBuilder: UrlBuilder, + downloadFile: DownloadFile ): PageViewModelFactory = PageViewModelFactory( openPage = openPage, closePage = closePage, @@ -72,7 +75,8 @@ class PageModule { mergeBlocks = mergeBlocks, splitBlock = splitBlock, documentEventReducer = documentExternalEventReducer, - urlBuilder = urlBuilder + urlBuilder = urlBuilder, + downloadFile = downloadFile ) @Provides @@ -196,6 +200,15 @@ class PageModule { repo = repo ) + @Provides + @PerScreen + fun provideDownloadFileUseCase( + downloader: Downloader + ): DownloadFile = DownloadFile( + downloader = downloader, + context = Dispatchers.Main + ) + @Provides @PerScreen fun provideDocumentExternalEventReducer(): DocumentExternalEventReducer = diff --git a/app/src/main/java/com/agileburo/anytype/di/main/DeviceModule.kt b/app/src/main/java/com/agileburo/anytype/di/main/DeviceModule.kt new file mode 100644 index 0000000000..da5cf561ac --- /dev/null +++ b/app/src/main/java/com/agileburo/anytype/di/main/DeviceModule.kt @@ -0,0 +1,34 @@ +package com.agileburo.anytype.di.main + +import android.content.Context +import com.agileburo.anytype.data.auth.other.DataDownloader +import com.agileburo.anytype.data.auth.other.Device +import com.agileburo.anytype.device.base.AndroidDevice +import com.agileburo.anytype.device.download.DeviceDownloader +import com.agileburo.anytype.domain.download.Downloader +import dagger.Module +import dagger.Provides +import javax.inject.Singleton + +@Module +class DeviceModule { + + @Provides + @Singleton + fun provideDownloader( + device: Device + ): Downloader = DataDownloader(device = device) + + @Provides + @Singleton + fun provideDevice( + downloader: DeviceDownloader + ): Device = AndroidDevice(downloader = downloader) + + @Provides + @Singleton + fun provideDeviceDownloader( + context: Context + ): DeviceDownloader = DeviceDownloader(context = context) + +} \ No newline at end of file diff --git a/app/src/main/java/com/agileburo/anytype/di/main/MainComponent.kt b/app/src/main/java/com/agileburo/anytype/di/main/MainComponent.kt index 51c8145763..f1fa3d6372 100644 --- a/app/src/main/java/com/agileburo/anytype/di/main/MainComponent.kt +++ b/app/src/main/java/com/agileburo/anytype/di/main/MainComponent.kt @@ -12,6 +12,7 @@ import javax.inject.Singleton EventModule::class, ImageModule::class, ConfigModule::class, + DeviceModule::class, UtilModule::class ] ) diff --git a/app/src/main/java/com/agileburo/anytype/ui/page/PageFragment.kt b/app/src/main/java/com/agileburo/anytype/ui/page/PageFragment.kt index 3d4db6a081..e741654fe0 100644 --- a/app/src/main/java/com/agileburo/anytype/ui/page/PageFragment.kt +++ b/app/src/main/java/com/agileburo/anytype/ui/page/PageFragment.kt @@ -89,7 +89,8 @@ open class PageFragment : NavigationFragment(R.layout.fragment_page), onNonEmptyBlockBackspaceClicked = vm::onNonEmptyBlockBackspaceClicked, onFooterClicked = vm::onOutsideClicked, onPageClicked = vm::onPageClicked, - onTextInputClicked = vm::onTextInputClicked + onTextInputClicked = vm::onTextInputClicked, + onDownloadFileClicked = vm::onDownloadFileClicked ) } diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 0000000000..85285232de --- /dev/null +++ b/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,6 @@ + + + + 127.0.0.1 + + \ No newline at end of file diff --git a/core-ui/src/main/java/com/agileburo/anytype/core_ui/features/page/BlockAdapter.kt b/core-ui/src/main/java/com/agileburo/anytype/core_ui/features/page/BlockAdapter.kt index bb86ebd57e..81275c510a 100644 --- a/core-ui/src/main/java/com/agileburo/anytype/core_ui/features/page/BlockAdapter.kt +++ b/core-ui/src/main/java/com/agileburo/anytype/core_ui/features/page/BlockAdapter.kt @@ -47,7 +47,8 @@ class BlockAdapter( private val onEndLineEnterClicked: (String, Editable) -> Unit, private val onFooterClicked: () -> Unit, private val onPageClicked: (String) -> Unit, - private val onTextInputClicked: () -> Unit + private val onTextInputClicked: () -> Unit, + private val onDownloadFileClicked: (String) -> Unit ) : RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BlockViewHolder { @@ -380,7 +381,8 @@ class BlockAdapter( } is BlockViewHolder.File -> { holder.bind( - item = blocks[position] as BlockView.File + item = blocks[position] as BlockView.File, + onDownloadFileClicked = onDownloadFileClicked ) } is BlockViewHolder.Page -> { diff --git a/core-ui/src/main/java/com/agileburo/anytype/core_ui/features/page/BlockView.kt b/core-ui/src/main/java/com/agileburo/anytype/core_ui/features/page/BlockView.kt index 63629f4a45..60e00091ed 100644 --- a/core-ui/src/main/java/com/agileburo/anytype/core_ui/features/page/BlockView.kt +++ b/core-ui/src/main/java/com/agileburo/anytype/core_ui/features/page/BlockView.kt @@ -271,12 +271,14 @@ sealed class BlockView : ViewType { * @property size a file's size * @property name a name * @property size file size (in bytes) + * @property url file url */ data class File( override val id: String, val size: Long, val name: String, - val mime: String + val mime: String, + val url: String ) : BlockView() { override fun getViewType() = HOLDER_FILE } diff --git a/core-ui/src/main/java/com/agileburo/anytype/core_ui/features/page/BlockViewHolder.kt b/core-ui/src/main/java/com/agileburo/anytype/core_ui/features/page/BlockViewHolder.kt index 341717c7fb..8ae71d5c22 100644 --- a/core-ui/src/main/java/com/agileburo/anytype/core_ui/features/page/BlockViewHolder.kt +++ b/core-ui/src/main/java/com/agileburo/anytype/core_ui/features/page/BlockViewHolder.kt @@ -603,7 +603,10 @@ sealed class BlockViewHolder(view: View) : RecyclerView.ViewHolder(view) { private val size = itemView.fileSize private val name = itemView.filename - fun bind(item: BlockView.File) { + fun bind( + item: BlockView.File, + onDownloadFileClicked: (String) -> Unit + ) { name.text = item.name size.text = FileSizeFormatter.formatFileSize(itemView.context, item.size) when (MimeTypes.category(item.mime)) { @@ -612,6 +615,7 @@ sealed class BlockViewHolder(view: View) : RecyclerView.ViewHolder(view) { // TODO add images when they are ready. } } + itemView.setOnClickListener { onDownloadFileClicked(item.id) } } } diff --git a/core-ui/src/test/java/com/agileburo/anytype/core_ui/BlockAdapterTest.kt b/core-ui/src/test/java/com/agileburo/anytype/core_ui/BlockAdapterTest.kt index 05e60ed4ba..16853c4834 100644 --- a/core-ui/src/test/java/com/agileburo/anytype/core_ui/BlockAdapterTest.kt +++ b/core-ui/src/test/java/com/agileburo/anytype/core_ui/BlockAdapterTest.kt @@ -846,7 +846,8 @@ class BlockAdapterTest { onSelectionChanged = { _, _ -> }, onFooterClicked = {}, onPageClicked = {}, - onTextInputClicked = {} + onTextInputClicked = {}, + onDownloadFileClicked = {} ) } } \ No newline at end of file diff --git a/data/src/main/java/com/agileburo/anytype/data/auth/other/DataDownloader.kt b/data/src/main/java/com/agileburo/anytype/data/auth/other/DataDownloader.kt new file mode 100644 index 0000000000..bf56f211bb --- /dev/null +++ b/data/src/main/java/com/agileburo/anytype/data/auth/other/DataDownloader.kt @@ -0,0 +1,11 @@ +package com.agileburo.anytype.data.auth.other + +import com.agileburo.anytype.domain.common.Url +import com.agileburo.anytype.domain.download.Downloader + +class DataDownloader(private val device: Device) : Downloader { + + 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/agileburo/anytype/data/auth/other/Device.kt b/data/src/main/java/com/agileburo/anytype/data/auth/other/Device.kt new file mode 100644 index 0000000000..999332b789 --- /dev/null +++ b/data/src/main/java/com/agileburo/anytype/data/auth/other/Device.kt @@ -0,0 +1,5 @@ +package com.agileburo.anytype.data.auth.other + +interface Device { + fun download(url: String, name: String) +} \ No newline at end of file diff --git a/device/.gitignore b/device/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/device/.gitignore @@ -0,0 +1 @@ +/build diff --git a/device/build.gradle b/device/build.gradle new file mode 100644 index 0000000000..d5ef4fa03b --- /dev/null +++ b/device/build.gradle @@ -0,0 +1,56 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + def config = rootProject.extensions.getByName("ext") + + compileSdkVersion config["compile_sdk"] + + defaultConfig { + minSdkVersion config["min_sdk"] + targetSdkVersion config["target_sdk"] + versionCode config["version_code"] + versionName config["version_name"] + + testInstrumentationRunner config["test_runner"] + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + testOptions { + unitTests { + includeAndroidResources = true + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + def applicationDependencies = rootProject.ext.mainApplication + def unitTestDependencies = rootProject.ext.unitTesting + + implementation project(':data') + + implementation applicationDependencies.kotlin + implementation applicationDependencies.coroutines + implementation applicationDependencies.androidxCore + + implementation applicationDependencies.timber + + testImplementation unitTestDependencies.junit + testImplementation unitTestDependencies.kotlinTest +} \ No newline at end of file diff --git a/device/consumer-rules.pro b/device/consumer-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/device/proguard-rules.pro b/device/proguard-rules.pro new file mode 100644 index 0000000000..f1b424510d --- /dev/null +++ b/device/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/device/src/main/AndroidManifest.xml b/device/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..c858d147cd --- /dev/null +++ b/device/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/device/src/main/java/com/agileburo/anytype/device/base/AndroidDevice.kt b/device/src/main/java/com/agileburo/anytype/device/base/AndroidDevice.kt new file mode 100644 index 0000000000..0876df418d --- /dev/null +++ b/device/src/main/java/com/agileburo/anytype/device/base/AndroidDevice.kt @@ -0,0 +1,11 @@ +package com.agileburo.anytype.device.base + +import com.agileburo.anytype.data.auth.other.Device +import com.agileburo.anytype.device.download.DeviceDownloader + +class AndroidDevice(private val downloader: DeviceDownloader) : Device { + + override fun download(url: String, name: String) { + downloader.download(url = url, name = name) + } +} \ No newline at end of file diff --git a/device/src/main/java/com/agileburo/anytype/device/download/DeviceDownloader.kt b/device/src/main/java/com/agileburo/anytype/device/download/DeviceDownloader.kt new file mode 100644 index 0000000000..8167172348 --- /dev/null +++ b/device/src/main/java/com/agileburo/anytype/device/download/DeviceDownloader.kt @@ -0,0 +1,39 @@ +package com.agileburo.anytype.device.download + +import android.app.DownloadManager +import android.app.DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED +import android.content.Context +import android.content.Context.DOWNLOAD_SERVICE +import android.net.Uri +import android.os.Environment.DIRECTORY_DOWNLOADS +import timber.log.Timber + +class DeviceDownloader(private val context: Context) { + + private val manager by lazy { + context.getSystemService(DOWNLOAD_SERVICE) as DownloadManager + } + + fun download(url: String, name: String) { + + Timber.d("Downloading file: $name from url: $url") + + val uri = Uri.parse(url) + + context.getExternalFilesDir(DIRECTORY_DOWNLOADS)?.mkdirs() + + val request = DownloadManager.Request(uri) + .setTitle(name) + .setDescription(DESCRIPTION_TEXT) + .setAllowedOverMetered(true) + .setAllowedOverRoaming(true) + .setNotificationVisibility(VISIBILITY_VISIBLE_NOTIFY_COMPLETED) + .setDestinationInExternalPublicDir(DIRECTORY_DOWNLOADS, name) + + manager.enqueue(request) + } + + companion object { + const val DESCRIPTION_TEXT = "Downloading..." + } +} \ No newline at end of file diff --git a/device/src/main/res/values/strings.xml b/device/src/main/res/values/strings.xml new file mode 100644 index 0000000000..b94f028372 --- /dev/null +++ b/device/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Device + diff --git a/domain/src/main/java/com/agileburo/anytype/domain/download/DownloadFile.kt b/domain/src/main/java/com/agileburo/anytype/domain/download/DownloadFile.kt new file mode 100644 index 0000000000..43e2663363 --- /dev/null +++ b/domain/src/main/java/com/agileburo/anytype/domain/download/DownloadFile.kt @@ -0,0 +1,36 @@ +package com.agileburo.anytype.domain.download + +import com.agileburo.anytype.domain.base.BaseUseCase +import com.agileburo.anytype.domain.base.Either +import com.agileburo.anytype.domain.common.Url +import com.agileburo.anytype.domain.download.DownloadFile.Params +import kotlin.coroutines.CoroutineContext + +/** + * Use-case for starting downloading files. + * @see Params + */ +class DownloadFile( + private val downloader: Downloader, + context: CoroutineContext +) : BaseUseCase(context) { + + override suspend fun run(params: Params) = try { + downloader.download( + url = params.url, + name = params.name + ).let { Either.Right(it) } + } catch (t: Throwable) { + Either.Left(t) + } + + /** + * Params for downloading file. + * @property name file name + * @property url url of the file to download + */ + data class Params( + val name: String, + val url: Url + ) +} \ No newline at end of file diff --git a/domain/src/main/java/com/agileburo/anytype/domain/download/Downloader.kt b/domain/src/main/java/com/agileburo/anytype/domain/download/Downloader.kt new file mode 100644 index 0000000000..ad0727816d --- /dev/null +++ b/domain/src/main/java/com/agileburo/anytype/domain/download/Downloader.kt @@ -0,0 +1,15 @@ +package com.agileburo.anytype.domain.download + +import com.agileburo.anytype.domain.common.Url + +/** + * Base interface for downloaders. + */ +interface Downloader { + /** + * Starts downloading file from url. + * @param name file name + * @param url url of the file to download + */ + fun download(url: Url, name: String) +} \ No newline at end of file diff --git a/presentation/src/main/java/com/agileburo/anytype/presentation/mapper/MapperExtension.kt b/presentation/src/main/java/com/agileburo/anytype/presentation/mapper/MapperExtension.kt index 8e946ac333..58baa7bc9d 100644 --- a/presentation/src/main/java/com/agileburo/anytype/presentation/mapper/MapperExtension.kt +++ b/presentation/src/main/java/com/agileburo/anytype/presentation/mapper/MapperExtension.kt @@ -98,7 +98,8 @@ fun Block.toView( id = id, size = content.size, name = content.name, - mime = content.mime + mime = content.mime, + url = urlBuilder.file(content.hash) ) else -> TODO() } diff --git a/presentation/src/main/java/com/agileburo/anytype/presentation/page/PageViewModel.kt b/presentation/src/main/java/com/agileburo/anytype/presentation/page/PageViewModel.kt index 14b13f3bc9..4d8d341ea7 100644 --- a/presentation/src/main/java/com/agileburo/anytype/presentation/page/PageViewModel.kt +++ b/presentation/src/main/java/com/agileburo/anytype/presentation/page/PageViewModel.kt @@ -16,6 +16,7 @@ import com.agileburo.anytype.domain.block.model.Block.Content import com.agileburo.anytype.domain.block.model.Block.Prototype import com.agileburo.anytype.domain.block.model.Position import com.agileburo.anytype.domain.common.Id +import com.agileburo.anytype.domain.download.DownloadFile import com.agileburo.anytype.domain.event.interactor.InterceptEvents import com.agileburo.anytype.domain.event.model.Event import com.agileburo.anytype.domain.ext.* @@ -51,6 +52,7 @@ class PageViewModel( private val removeLinkMark: RemoveLinkMark, private val mergeBlocks: MergeBlocks, private val splitBlock: SplitBlock, + private val downloadFile: DownloadFile, private val documentExternalEventReducer: StateReducer, Event>, private val urlBuilder: UrlBuilder ) : ViewStateViewModel(), @@ -839,6 +841,23 @@ class PageViewModel( } } + fun onDownloadFileClicked(id: String) { + val block = blocks.first { it.id == id } + val file = block.content() + downloadFile.invoke( + scope = viewModelScope, + params = DownloadFile.Params( + url = urlBuilder.file(file.hash), + name = file.name + ) + ) { result -> + result.either( + fnL = { Timber.e(it, "Error while trying to download file: $file") }, + fnR = { Timber.d("Started download file: $file") } + ) + } + } + private fun addNewBlockAtTheEnd() { proceedWithCreatingNewTextBlock( id = "", diff --git a/presentation/src/main/java/com/agileburo/anytype/presentation/page/PageViewModelFactory.kt b/presentation/src/main/java/com/agileburo/anytype/presentation/page/PageViewModelFactory.kt index 15cfa56315..3d01e1446b 100644 --- a/presentation/src/main/java/com/agileburo/anytype/presentation/page/PageViewModelFactory.kt +++ b/presentation/src/main/java/com/agileburo/anytype/presentation/page/PageViewModelFactory.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import com.agileburo.anytype.domain.block.interactor.* import com.agileburo.anytype.domain.block.model.Block +import com.agileburo.anytype.domain.download.DownloadFile import com.agileburo.anytype.domain.event.interactor.InterceptEvents import com.agileburo.anytype.domain.event.model.Event import com.agileburo.anytype.domain.misc.UrlBuilder @@ -30,7 +31,8 @@ open class PageViewModelFactory( private val mergeBlocks: MergeBlocks, private val splitBlock: SplitBlock, private val documentEventReducer: StateReducer, Event>, - private val urlBuilder: UrlBuilder + private val urlBuilder: UrlBuilder, + private val downloadFile: DownloadFile ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") @@ -53,7 +55,8 @@ open class PageViewModelFactory( splitBlock = splitBlock, createPage = createPage, documentExternalEventReducer = documentEventReducer, - urlBuilder = urlBuilder + urlBuilder = urlBuilder, + downloadFile = downloadFile ) as T } } \ No newline at end of file diff --git a/presentation/src/test/java/com/agileburo/anytype/presentation/MockBlockFactory.kt b/presentation/src/test/java/com/agileburo/anytype/presentation/MockBlockFactory.kt index f98cf296dd..9ed23e6dc3 100644 --- a/presentation/src/test/java/com/agileburo/anytype/presentation/MockBlockFactory.kt +++ b/presentation/src/test/java/com/agileburo/anytype/presentation/MockBlockFactory.kt @@ -30,6 +30,32 @@ object MockBlockFactory { ) ) + fun makeFileBlock(): Block = Block( + id = MockDataFactory.randomUuid(), + fields = Block.Fields(emptyMap()), + content = Block.Content.File( + hash = MockDataFactory.randomUuid(), + name = MockDataFactory.randomString(), + state = Block.Content.File.State.DONE, + added = MockDataFactory.randomLong(), + mime = MockDataFactory.randomString(), + size = MockDataFactory.randomLong(), + type = Block.Content.File.Type.FILE + ), + children = emptyList() + ) + + fun makeTitleBlock(): Block = Block( + id = MockDataFactory.randomUuid(), + fields = Block.Fields(emptyMap()), + content = Block.Content.Text( + text = MockDataFactory.randomString(), + marks = emptyList(), + style = Block.Content.Text.Style.TITLE + ), + children = emptyList() + ) + fun makeOnePageWithTwoTextBlocks( root: String, firstChild: String, diff --git a/presentation/src/test/java/com/agileburo/anytype/presentation/page/PageViewModelTest.kt b/presentation/src/test/java/com/agileburo/anytype/presentation/page/PageViewModelTest.kt index dafe8251c0..f914d05852 100644 --- a/presentation/src/test/java/com/agileburo/anytype/presentation/page/PageViewModelTest.kt +++ b/presentation/src/test/java/com/agileburo/anytype/presentation/page/PageViewModelTest.kt @@ -9,6 +9,7 @@ import com.agileburo.anytype.domain.block.interactor.* import com.agileburo.anytype.domain.block.model.Block import com.agileburo.anytype.domain.block.model.Position import com.agileburo.anytype.domain.config.Config +import com.agileburo.anytype.domain.download.DownloadFile import com.agileburo.anytype.domain.event.interactor.InterceptEvents import com.agileburo.anytype.domain.event.model.Event import com.agileburo.anytype.domain.ext.content @@ -92,6 +93,9 @@ class PageViewModelTest { @Mock lateinit var updateBackgroundColor: UpdateBackgroundColor + @Mock + lateinit var downloadFile: DownloadFile + private lateinit var vm: PageViewModel @Before @@ -2693,6 +2697,72 @@ class PageViewModelTest { ) } + @Test + fun `should start downloading 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.Page( + style = Block.Content.Page.Style.SET + ), + children = listOf(title.id, file.id) + ), + title, + file + ) + + val flow: Flow> = flow { + delay(100) + emit( + listOf( + Event.Command.ShowBlock( + rootId = root, + blocks = page, + context = root + ) + ) + ) + } + + val builder = UrlBuilder( + config = Config( + home = MockDataFactory.randomUuid(), + gateway = MockDataFactory.randomString() + ) + ) + + stubObserveEvents(flow) + stubOpenPage() + buildViewModel(builder) + + vm.open(root) + + coroutineTestRule.advanceTime(100) + + // TESTING + + vm.onDownloadFileClicked(id = file.id) + + verify(downloadFile, times(1)).invoke( + scope = any(), + params = eq( + DownloadFile.Params( + name = file.content().name, + url = builder.file( + hash = file.content().hash + ) + ) + ), + onResult = any() + ) + } + private fun simulateNormalPageOpeningFlow() { val root = MockDataFactory.randomUuid() @@ -2769,7 +2839,8 @@ class PageViewModelTest { mergeBlocks = mergeBlocks, splitBlock = splitBlock, documentExternalEventReducer = DocumentExternalEventReducer(), - urlBuilder = urlBuilder + urlBuilder = urlBuilder, + downloadFile = downloadFile ) } } \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 2dd92434f6..e470ebf2c9 100644 --- a/settings.gradle +++ b/settings.gradle @@ -6,7 +6,8 @@ include ':app', ':persistence', ':domain', ':data', + ':device', ':presentation', ':core-ui', ':library-kanban-widget', - ':library-page-icon-picker-widget' + ':library-page-icon-picker-widget' \ No newline at end of file