diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index e429dd5af8..5ed9991ea3 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -57,6 +57,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{
- SharingFragment.new(command.data).show(
+ is Command.Sharing.Text -> {
+ SharingFragment.text(command.data).show(
+ supportFragmentManager,
+ SHARE_DIALOG_LABEL
+ )
+ }
+ is Command.Sharing.Image -> {
+ SharingFragment.image(command.uri).show(
+ supportFragmentManager,
+ SHARE_DIALOG_LABEL
+ )
+ }
+ is Command.Sharing.Images -> {
+ SharingFragment.images(command.uris).show(
+ supportFragmentManager,
+ SHARE_DIALOG_LABEL
+ )
+ }
+ is Command.Sharing.Files -> {
+ SharingFragment.files(command.uris).show(
+ supportFragmentManager,
+ SHARE_DIALOG_LABEL
+ )
+ }
+ is Command.Sharing.File -> {
+ SharingFragment.file(command.uri).show(
supportFragmentManager,
SHARE_DIALOG_LABEL
)
@@ -121,8 +154,11 @@ class MainActivity : AppCompatActivity(R.layout.activity_main), AppNavigation.Pr
}
}
}
- if (savedInstanceState == null && intent.action == Intent.ACTION_SEND) {
- proceedWithShareIntent(intent)
+ if (savedInstanceState == null) {
+ val action = intent.action
+ if (action == Intent.ACTION_SEND || action == Intent.ACTION_SEND_MULTIPLE) {
+ proceedWithShareIntent(intent)
+ }
}
}
@@ -172,6 +208,9 @@ class MainActivity : AppCompatActivity(R.layout.activity_main), AppNavigation.Pr
Intent.ACTION_SEND -> {
proceedWithShareIntent(intent)
}
+ Intent.ACTION_SEND_MULTIPLE -> {
+ proceedWithShareIntent(intent)
+ }
}
}
if (BuildConfig.DEBUG) {
@@ -180,8 +219,47 @@ class MainActivity : AppCompatActivity(R.layout.activity_main), AppNavigation.Pr
}
private fun proceedWithShareIntent(intent: Intent) {
- intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
- vm.onIntentShare(it)
+ if (BuildConfig.DEBUG) Timber.d("Proceeding with share intent: $intent")
+ when {
+ intent.type == Mimetype.MIME_TEXT_PLAIN.value -> {
+ vm.onIntentTextShare(intent.getStringExtra(Intent.EXTRA_TEXT).orEmpty())
+ }
+ intent.type?.startsWith(SHARE_IMAGE_INTENT_PATTERN) == true -> {
+ proceedWithImageShareIntent(intent)
+ }
+ intent.type?.startsWith(SHARE_FILE_INTENT_PATTERN) == true -> {
+ proceedWithFileShareIntent(intent)
+ }
+ intent.type == Mimetype.MIME_FILE_ALL.value -> {
+ proceedWithFileShareIntent(intent)
+ }
+ else -> Timber.e("Unexpected scenario: ${intent.type}")
+ }
+ }
+
+ private fun proceedWithFileShareIntent(intent: Intent) {
+ if (intent.action == Intent.ACTION_SEND_MULTIPLE) {
+ vm.onIntentMultipleFilesShare(intent.parseActionSendMultipleUris())
+ } else {
+ val uri = intent.parseActionSendUri()
+ if (uri != null) {
+ vm.onIntentMultipleFilesShare(listOf(uri))
+ } else {
+ toast("Could not parse URI")
+ }
+ }
+ }
+
+ private fun proceedWithImageShareIntent(intent: Intent) {
+ if (intent.action == Intent.ACTION_SEND_MULTIPLE) {
+ vm.onIntentMultipleImageShare(uris = intent.parseActionSendMultipleUris())
+ } else {
+ val uri = intent.parseActionSendUri()
+ if (uri != null) {
+ vm.onIntentMultipleImageShare(listOf(uri))
+ } else {
+ toast("Could not parse URI")
+ }
}
}
@@ -249,5 +327,8 @@ class MainActivity : AppCompatActivity(R.layout.activity_main), AppNavigation.Pr
companion object {
const val AUTO_UPDATE_URL = "https://fra1.digitaloceanspaces.com/anytype-release/latest-android.json"
const val SHARE_DIALOG_LABEL = "anytype.dialog.share.label"
+ const val SHARE_IMAGE_INTENT_PATTERN = "image/"
+ const val SHARE_FILE_INTENT_PATTERN = "application/"
+
}
}
diff --git a/app/src/main/java/com/anytypeio/anytype/ui/sharing/Sharing.kt b/app/src/main/java/com/anytypeio/anytype/ui/sharing/Sharing.kt
index 75bf3f8e18..67b89503b0 100644
--- a/app/src/main/java/com/anytypeio/anytype/ui/sharing/Sharing.kt
+++ b/app/src/main/java/com/anytypeio/anytype/ui/sharing/Sharing.kt
@@ -63,7 +63,7 @@ fun AddToAnytypeScreenUrlPreview() {
@Composable
fun AddToAnytypeScreenNotePreview() {
AddToAnytypeScreen(
- data = SharingData.Raw("The Work of Art in the Age of its Technological Reproducibility"),
+ data = SharingData.Text("The Work of Art in the Age of its Technological Reproducibility"),
onCancelClicked = {},
onDoneClicked = {},
spaces = emptyList(),
@@ -80,15 +80,23 @@ fun AddToAnytypeScreen(
onSelectSpaceClicked: (SpaceView) -> Unit
) {
var isSaveAsMenuExpanded by remember { mutableStateOf(false) }
- val items = if (data is SharingData.Url)
- listOf(SAVE_AS_NOTE, SAVE_AS_BOOKMARK)
- else
- listOf(SAVE_AS_NOTE)
+ val items = when (data) {
+ is SharingData.Url -> listOf(SAVE_AS_NOTE, SAVE_AS_BOOKMARK)
+ is SharingData.Image -> listOf(SAVE_AS_IMAGE)
+ is SharingData.File -> listOf(SAVE_AS_FILE)
+ is SharingData.Images -> listOf(SAVE_AS_IMAGES)
+ is SharingData.Files -> listOf(SAVE_AS_FILES)
+ is SharingData.Text -> listOf(SAVE_AS_NOTE)
+ }
var selectedIndex by remember {
mutableStateOf(
when(data) {
is SharingData.Url -> SAVE_AS_BOOKMARK
- else -> SAVE_AS_NOTE
+ is SharingData.Image -> SAVE_AS_IMAGE
+ is SharingData.File -> SAVE_AS_FILE
+ is SharingData.Images -> SAVE_AS_IMAGES
+ is SharingData.Files -> SAVE_AS_FILES
+ is SharingData.Text -> SAVE_AS_NOTE
}
)
}
@@ -111,10 +119,14 @@ fun AddToAnytypeScreen(
)
Text(
- text = if (selectedIndex == SAVE_AS_BOOKMARK)
- stringResource(id = R.string.sharing_menu_save_as_bookmark_option)
- else
- stringResource(id = R.string.sharing_menu_save_as_note_option),
+ text = when (selectedIndex) {
+ SAVE_AS_BOOKMARK -> stringResource(id = R.string.sharing_menu_save_as_bookmark_option)
+ SAVE_AS_IMAGE -> stringResource(id = R.string.sharing_menu_save_as_image_option)
+ SAVE_AS_FILE -> stringResource(id = R.string.sharing_menu_save_as_file_option)
+ SAVE_AS_IMAGES -> stringResource(id = R.string.sharing_menu_save_as_images_option)
+ SAVE_AS_FILES -> stringResource(id = R.string.sharing_menu_save_as_files_option)
+ else -> stringResource(id = R.string.sharing_menu_save_as_note_option)
+ },
modifier = Modifier
.align(Alignment.BottomStart)
.padding(bottom = 14.dp, start = 20.dp),
@@ -390,6 +402,10 @@ private fun SmallSpaceIcon(
const val SAVE_AS_NOTE = 0
const val SAVE_AS_BOOKMARK = 1
+const val SAVE_AS_IMAGE = 2
+const val SAVE_AS_FILE = 3
+const val SAVE_AS_IMAGES = 4
+const val SAVE_AS_FILES = 5
typealias SaveAsOption = Int
sealed class SharingData {
@@ -398,8 +414,27 @@ sealed class SharingData {
override val data: String
get() = url
}
- data class Raw(val raw: String) : SharingData() {
+ data class Text(val raw: String) : SharingData() {
override val data: String
get() = raw
}
+ data class Image(val uri: String) : SharingData() {
+ override val data: String
+ get() = uri
+ }
+
+ data class Images(val uris: List): SharingData() {
+ override val data: String
+ get() = uris.toString()
+ }
+
+ data class Files(val uris: List): SharingData() {
+ override val data: String
+ get() = uris.toString()
+ }
+
+ data class File(val uri: String): SharingData() {
+ override val data: String
+ get() = uri
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/anytypeio/anytype/ui/sharing/SharingFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/sharing/SharingFragment.kt
index 8955ca77f2..c9a5ed457c 100644
--- a/app/src/main/java/com/anytypeio/anytype/ui/sharing/SharingFragment.kt
+++ b/app/src/main/java/com/anytypeio/anytype/ui/sharing/SharingFragment.kt
@@ -15,6 +15,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.fragment.findNavController
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_utils.ext.arg
+import com.anytypeio.anytype.core_utils.ext.argStringList
import com.anytypeio.anytype.core_utils.ext.toast
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment
import com.anytypeio.anytype.di.common.componentManager
@@ -22,18 +23,37 @@ import com.anytypeio.anytype.presentation.home.OpenObjectNavigation
import com.anytypeio.anytype.presentation.sharing.AddToAnytypeViewModel
import com.anytypeio.anytype.ui.editor.EditorFragment
import com.anytypeio.anytype.ui.settings.typography
+import java.lang.IllegalStateException
import javax.inject.Inject
class SharingFragment : BaseBottomSheetComposeFragment() {
- private val sharedData : SharingData get() {
- val result = arg(SHARING_DATE_KEY)
- return if (URLUtil.isValidUrl(result)) {
- SharingData.Url(result)
- } else {
- SharingData.Raw(result)
+ private val sharedData: SharingData
+ get() {
+ val args = requireArguments()
+ return if (args.containsKey(SHARING_TEXT_KEY)) {
+ val result = arg(SHARING_TEXT_KEY)
+ if (URLUtil.isValidUrl(result)) {
+ SharingData.Url(result)
+ } else {
+ SharingData.Text(result)
+ }
+ } else if (args.containsKey(SHARING_IMAGE_KEY)) {
+ val result = arg(SHARING_IMAGE_KEY)
+ SharingData.Image(uri = result)
+ } else if (args.containsKey(SHARING_FILE_KEY)) {
+ val result = arg(SHARING_FILE_KEY)
+ SharingData.File(uri = result)
+ } else if (args.containsKey(SHARING_MULTIPLE_IMAGES_KEY)) {
+ val result = argStringList(SHARING_MULTIPLE_IMAGES_KEY)
+ SharingData.Images(uris = result)
+ } else if (args.containsKey(SHARING_MULTIPLE_FILES_KEY)) {
+ val result = argStringList(SHARING_MULTIPLE_FILES_KEY)
+ SharingData.Files(uris = result)
+ } else {
+ throw IllegalStateException("Unexpcted shared data")
+ }
}
- }
@Inject
lateinit var factory: AddToAnytypeViewModel.Factory
@@ -56,6 +76,24 @@ class SharingFragment : BaseBottomSheetComposeFragment() {
when(option) {
SAVE_AS_BOOKMARK -> vm.onCreateBookmark(url = sharedData.data)
SAVE_AS_NOTE -> vm.onCreateNote(sharedData.data)
+ SAVE_AS_IMAGE -> vm.onShareMedia(listOf(sharedData.data))
+ SAVE_AS_FILE -> vm.onShareMedia(listOf(sharedData.data))
+ SAVE_AS_IMAGES -> {
+ val data = sharedData
+ if (data is SharingData.Images) {
+ vm.onShareMedia(uris = data.uris)
+ } else {
+ toast("Unexpected data format")
+ }
+ }
+ SAVE_AS_FILES -> {
+ val data = sharedData
+ if (data is SharingData.Files) {
+ vm.onShareMedia(uris = data.uris)
+ } else {
+ toast("Unexpected data format")
+ }
+ }
}
},
onCancelClicked = {
@@ -120,9 +158,30 @@ class SharingFragment : BaseBottomSheetComposeFragment() {
}
companion object {
- private const val SHARING_DATE_KEY = "arg.sharing.data-key"
- fun new(data: String) : SharingFragment = SharingFragment().apply {
- arguments = bundleOf(SHARING_DATE_KEY to data)
+ private const val SHARING_TEXT_KEY = "arg.sharing.text-key"
+ private const val SHARING_IMAGE_KEY = "arg.sharing.image-key"
+ private const val SHARING_FILE_KEY = "arg.sharing.file-key"
+ private const val SHARING_MULTIPLE_IMAGES_KEY = "arg.sharing.multiple-images-key"
+ private const val SHARING_MULTIPLE_FILES_KEY = "arg.sharing.multiple-files-key"
+
+ fun text(data: String) : SharingFragment = SharingFragment().apply {
+ arguments = bundleOf(SHARING_TEXT_KEY to data)
+ }
+
+ fun image(uri: String) : SharingFragment = SharingFragment().apply {
+ arguments = bundleOf(SHARING_IMAGE_KEY to uri)
+ }
+
+ fun images(uris: List) : SharingFragment = SharingFragment().apply {
+ arguments = bundleOf(SHARING_MULTIPLE_IMAGES_KEY to ArrayList(uris))
+ }
+
+ fun files(uris: List) : SharingFragment = SharingFragment().apply {
+ arguments = bundleOf(SHARING_MULTIPLE_FILES_KEY to ArrayList(uris))
+ }
+
+ fun file(uri: String) : SharingFragment = SharingFragment().apply {
+ arguments = bundleOf(SHARING_FILE_KEY to uri)
}
}
}
\ No newline at end of file
diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/Block.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/Block.kt
index a2bcba5abd..3828a934a6 100644
--- a/core-models/src/main/java/com/anytypeio/anytype/core_models/Block.kt
+++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/Block.kt
@@ -398,7 +398,8 @@ data class Block(
data class File(
val type: Content.File.Type,
- val state: Content.File.State
+ val state: Content.File.State,
+ val targetObjectId: Id? = null
) : Prototype()
data class Link(
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 c0728f8da3..9613378063 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
@@ -21,6 +21,7 @@ sealed class Command {
) : Command()
class UploadFile(
+ val space: SpaceId? = null,
val path: String,
val type: Block.Content.File.Type?
)
diff --git a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/AndroidExtension.kt b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/AndroidExtension.kt
index d252913470..624f4b83e5 100644
--- a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/AndroidExtension.kt
+++ b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/AndroidExtension.kt
@@ -13,6 +13,7 @@ import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
+import android.os.Parcelable
import android.provider.MediaStore
import android.text.Editable
import android.text.InputType
@@ -377,6 +378,25 @@ fun Fragment.shareFile(uri: Uri) {
}
}
+fun Intent.parseActionSendMultipleUris() : List {
+ val extras = getParcelableArrayListExtra(Intent.EXTRA_STREAM) ?: arrayListOf()
+ return extras.mapNotNull { extra ->
+ if (extra is Uri)
+ extra.toString()
+ else
+ null
+ }
+}
+
+fun Intent.parseActionSendUri() : String? {
+ val extra = getParcelableExtra(Intent.EXTRA_STREAM)
+ return if (extra is Uri) {
+ extra.toString()
+ } else {
+ null
+ }
+}
+
inline fun Pair.letNotNull(block: (T1, T2) -> R): R? {
return if (first != null && second != null) {
block(first!!, second!!)
diff --git a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/FilePickerUtils.kt b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/FilePickerUtils.kt
index 3e9845ffda..ea236d1af8 100644
--- a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/FilePickerUtils.kt
+++ b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/FilePickerUtils.kt
@@ -14,7 +14,7 @@ object FilePickerUtils {
Mimetype.MIME_VIDEO_ALL -> context.isPermissionGranted(getPermissionToRequestForVideos())
Mimetype.MIME_IMAGE_ALL -> context.isPermissionGranted(getPermissionToRequestForImages())
Mimetype.MIME_IMAGE_AND_VIDEO -> context.isPermissionGranted(getPermissionToRequestForImagesAndVideos())
- Mimetype.MIME_FILE_ALL, Mimetype.MIME_YAML -> {
+ else -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
true
} else {
@@ -37,6 +37,7 @@ object FilePickerUtils {
Mimetype.MIME_FILE_ALL -> getPermissionToRequestForFiles()
Mimetype.MIME_IMAGE_AND_VIDEO -> getPermissionToRequestForImagesAndVideos()
Mimetype.MIME_YAML -> getPermissionToRequestForFiles()
+ else -> getPermissionToRequestForFiles()
}
}
diff --git a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/FragmentExtensions.kt b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/FragmentExtensions.kt
index 10b1181329..d13ae4f65e 100644
--- a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/FragmentExtensions.kt
+++ b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/FragmentExtensions.kt
@@ -46,6 +46,10 @@ fun Fragment.argList(key: String): ArrayList {
return checkNotNull(value)
}
+fun Fragment.argStringList(key: String): ArrayList {
+ return requireArguments().getStringArrayList(key) ?: ArrayList()
+}
+
fun CoroutineScope.subscribe(flow: Flow, body: suspend (T) -> Unit): Job =
flow.cancellable().onEach { body(it) }.launchIn(this)
diff --git a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/Mimetype.kt b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/Mimetype.kt
index fd6beffec7..630e4cd610 100644
--- a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/Mimetype.kt
+++ b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/Mimetype.kt
@@ -1,9 +1,11 @@
package com.anytypeio.anytype.core_utils.ext
enum class Mimetype(val value: String) {
+ MIME_TEXT_PLAIN("text/plain"),
MIME_VIDEO_ALL("video/*"),
MIME_IMAGE_ALL("image/*"),
MIME_FILE_ALL("*/*"),
MIME_IMAGE_AND_VIDEO("image/*,video/*"),
- MIME_YAML("application/zip")
+ MIME_YAML("application/zip"),
+ MIME_APPLICATION_ALL("application/*")
}
\ 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 f50fce1a08..911d2e3978 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,7 +256,7 @@ class BlockDataRepository(
override suspend fun uploadFile(
command: Command.UploadFile
- ): Hash = remote.uploadFile(command)
+ ): Id = remote.uploadFile(command)
override suspend fun downloadFile(
command: Command.DownloadFile
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 a9e1ed8405..8f8e7e32b2 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
@@ -80,7 +80,7 @@ interface BlockRemote {
suspend fun paste(command: Command.Paste): Response.Clipboard.Paste
suspend fun copy(command: Command.Copy): Response.Clipboard.Copy
- suspend fun uploadFile(command: Command.UploadFile): String
+ suspend fun uploadFile(command: Command.UploadFile): Id
suspend fun downloadFile(command: Command.DownloadFile): String
suspend fun setRelationKey(command: Command.SetRelationKey): Payload
diff --git a/device/build.gradle b/device/build.gradle
index 68a7c164de..0a230d3724 100644
--- a/device/build.gradle
+++ b/device/build.gradle
@@ -7,6 +7,7 @@ dependencies {
implementation project(':data')
implementation project(':domain')
+ implementation project(':localization')
implementation libs.kotlin
implementation libs.coroutinesAndroid
diff --git a/device/src/main/java/com/anytypeio/anytype/device/SharedFileUploader.kt b/device/src/main/java/com/anytypeio/anytype/device/SharedFileUploader.kt
new file mode 100644
index 0000000000..43c8dbfa31
--- /dev/null
+++ b/device/src/main/java/com/anytypeio/anytype/device/SharedFileUploader.kt
@@ -0,0 +1,85 @@
+package com.anytypeio.anytype.device
+
+import android.content.Context
+import android.net.Uri
+import android.provider.OpenableColumns
+import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
+import com.anytypeio.anytype.domain.device.FileSharer
+import com.anytypeio.anytype.localization.R
+import java.io.File
+import java.io.FileOutputStream
+import javax.inject.Inject
+import kotlinx.coroutines.withContext
+import timber.log.Timber
+
+class SharedFileUploader @Inject constructor(
+ private val context: Context,
+ private val dispatchers: AppCoroutineDispatchers
+) : FileSharer {
+
+ override suspend fun getPath(uri: String): String = withContext(dispatchers.io) {
+ if (BuildConfig.DEBUG) Timber.d("Getting path for: $uri")
+ val parsed = Uri.parse(uri)
+ checkNotNull(parsed)
+ parsePathFromUri(parsed)
+ }
+
+ override suspend fun clear() {
+ TODO("Not yet implemented")
+ }
+
+ private fun parsePathFromUri(extra: Uri) : String {
+ val name = if (extra.scheme == CONTENT_URI_SCHEME) {
+ context.contentResolver.query(
+ extra,
+ null,
+ null,
+ null,
+ null
+ ).use { cursor ->
+ if (cursor != null && cursor.moveToFirst()) {
+ val idx = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
+ if (idx != -1) {
+ cursor.getString(idx)
+ } else {
+ context.resources.getString(R.string.untitled)
+ }
+ } else {
+ context.resources.getString(R.string.untitled)
+ }
+ }
+ } else {
+ val rawPath = extra.path
+ if (rawPath != null) {
+ rawPath.substring(rawPath.lastIndexOf("/"))
+ } else {
+ ""
+ }
+ }
+ val inputStream = context.contentResolver.openInputStream(extra)
+ val cacheDir = context.getExternalFilesDir(null)
+ if (cacheDir != null && !cacheDir.exists()) {
+ cacheDir.mkdirs()
+ }
+ var path = ""
+ inputStream?.use { input ->
+ val newFile = File(cacheDir?.path + "/" + name);
+ FileOutputStream(newFile).use { output ->
+ val buffer = ByteArray(1024)
+ var read: Int = input.read(buffer)
+ while (read != -1) {
+ output.write(buffer, 0, read)
+ read = input.read(buffer)
+ }
+ }
+ path = newFile.path
+ }
+
+ return path
+ }
+
+ companion object {
+ const val CONTENT_URI_SCHEME = "content"
+ }
+
+}
\ 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 2b8ffecc1a..002c3d3d9b 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
@@ -33,7 +33,7 @@ import com.anytypeio.anytype.domain.page.Undo
interface BlockRepository {
- suspend fun uploadFile(command: Command.UploadFile): Hash
+ suspend fun uploadFile(command: Command.UploadFile): Id
suspend fun downloadFile(command: Command.DownloadFile): String
suspend fun move(command: Command.Move): Payload
diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/device/FileSharer.kt b/domain/src/main/java/com/anytypeio/anytype/domain/device/FileSharer.kt
new file mode 100644
index 0000000000..898b5832f4
--- /dev/null
+++ b/domain/src/main/java/com/anytypeio/anytype/domain/device/FileSharer.kt
@@ -0,0 +1,6 @@
+package com.anytypeio.anytype.domain.device
+
+interface FileSharer {
+ suspend fun getPath(uri: String) : String?
+ suspend fun clear()
+}
\ No newline at end of file
diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/media/UploadFile.kt b/domain/src/main/java/com/anytypeio/anytype/domain/media/UploadFile.kt
new file mode 100644
index 0000000000..a268e763ea
--- /dev/null
+++ b/domain/src/main/java/com/anytypeio/anytype/domain/media/UploadFile.kt
@@ -0,0 +1,30 @@
+package com.anytypeio.anytype.domain.media
+
+import com.anytypeio.anytype.core_models.Block
+import com.anytypeio.anytype.core_models.Command
+import com.anytypeio.anytype.core_models.Id
+import com.anytypeio.anytype.core_models.primitives.SpaceId
+import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
+import com.anytypeio.anytype.domain.base.ResultInteractor
+import com.anytypeio.anytype.domain.block.repo.BlockRepository
+import javax.inject.Inject
+
+class UploadFile @Inject constructor(
+ private val repo: BlockRepository,
+ private val dispatchers: AppCoroutineDispatchers
+) : ResultInteractor(dispatchers.io) {
+
+ override suspend fun doWork(params: Params) : Id = repo.uploadFile(
+ command = Command.UploadFile(
+ path = params.path,
+ type = params.type,
+ space = params.space
+ )
+ )
+
+ data class Params(
+ val path: String,
+ val space: SpaceId,
+ val type: Block.Content.File.Type = Block.Content.File.Type.FILE,
+ )
+}
\ No newline at end of file
diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/objects/CreatePrefilledNote.kt b/domain/src/main/java/com/anytypeio/anytype/domain/objects/CreatePrefilledNote.kt
index f6967aa83e..8bb58f7176 100644
--- a/domain/src/main/java/com/anytypeio/anytype/domain/objects/CreatePrefilledNote.kt
+++ b/domain/src/main/java/com/anytypeio/anytype/domain/objects/CreatePrefilledNote.kt
@@ -40,6 +40,19 @@ class CreatePrefilledNote @Inject constructor(
target = NO_VALUE
)
)
+ params.attachments.forEach { attachment ->
+ repo.create(
+ command = Command.Create(
+ context = obj.id,
+ prototype = Block.Prototype.Link(
+ target = attachment,
+ cardStyle = Block.Content.Link.CardStyle.CARD
+ ),
+ position = Position.NONE,
+ target = NO_VALUE
+ )
+ )
+ }
return obj.id
}
@@ -50,6 +63,7 @@ class CreatePrefilledNote @Inject constructor(
val space: Id,
val text: String,
val details: Struct,
- val customType: TypeKey? = null
+ val customType: TypeKey? = null,
+ val attachments: List = emptyList()
)
}
\ No newline at end of file
diff --git a/localization/src/main/res/values/strings.xml b/localization/src/main/res/values/strings.xml
index d5d44deb87..729c9d714a 100644
--- a/localization/src/main/res/values/strings.xml
+++ b/localization/src/main/res/values/strings.xml
@@ -193,7 +193,7 @@
Upload a picture
Upload a file
Upload an audio
- Add a web bookmark
+ Add bookmark
Bulleted list item
Numbered list item
Toggle block
@@ -1192,6 +1192,10 @@
Save as
Note
Bookmark
+ Image
+ File
+ Images
+ Files
Data
Add to Anytype
New object is added to the space \'%1$s\'
@@ -1255,5 +1259,4 @@
Create option
Edit option
-
\ No newline at end of file
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 d50f95b584..d60d460f4a 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
@@ -34,6 +34,8 @@ import com.anytypeio.anytype.core_utils.tools.ThreadInfo
import com.anytypeio.anytype.middleware.BuildConfig
import com.anytypeio.anytype.middleware.auth.toAccountSetup
import com.anytypeio.anytype.middleware.const.Constants
+import com.anytypeio.anytype.middleware.mappers.MBFile
+import com.anytypeio.anytype.middleware.mappers.MBFileType
import com.anytypeio.anytype.middleware.mappers.MDVFilter
import com.anytypeio.anytype.middleware.mappers.MNetworkMode
import com.anytypeio.anytype.middleware.mappers.MRelationFormat
@@ -821,7 +823,8 @@ class Middleware @Inject constructor(
val type = command.type.toMiddlewareModel()
val request = Rpc.File.Upload.Request(
localPath = command.path,
- type = type
+ type = type,
+ spaceId = command.space?.id.orEmpty()
)
if (BuildConfig.DEBUG) logRequest(request)
val response = service.fileUpload(request)
diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/MiddlewareFactory.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/MiddlewareFactory.kt
index 3d38b0464f..026148abaf 100644
--- a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/MiddlewareFactory.kt
+++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/MiddlewareFactory.kt
@@ -46,7 +46,8 @@ class MiddlewareFactory {
is Block.Prototype.File -> {
val file = MBFile(
state = prototype.state.toMiddlewareModel(),
- type = prototype.type.toMiddlewareModel()
+ type = prototype.type.toMiddlewareModel(),
+ targetObjectId = prototype.targetObjectId.orEmpty()
)
MBlock(file_ = file)
}
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 1a672104e7..110cac58ce 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
@@ -3109,6 +3109,7 @@ class EditorViewModel(
ObjectType.Layout.NOTE,
ObjectType.Layout.TODO,
ObjectType.Layout.FILE,
+ ObjectType.Layout.IMAGE,
ObjectType.Layout.BOOKMARK -> {
proceedWithOpeningObject(target = target)
}
diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/main/MainViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/main/MainViewModel.kt
index bfb8279532..910a12105c 100644
--- a/presentation/src/main/java/com/anytypeio/anytype/presentation/main/MainViewModel.kt
+++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/main/MainViewModel.kt
@@ -164,13 +164,49 @@ class MainViewModel(
}
}
- fun onIntentShare(data: String) {
+ fun onIntentTextShare(data: String) {
viewModelScope.launch {
checkAuthorizationStatus(Unit).process(
failure = { e -> Timber.e(e, "Error while checking auth status") },
success = { status ->
if (status == AuthStatus.AUTHORIZED) {
- commands.emit(Command.AddToAnytype(data))
+ commands.emit(Command.Sharing.Text(data))
+ }
+ }
+ )
+ }
+ }
+
+ fun onIntentMultipleFilesShare(uris: List) {
+ Timber.d("onIntentFileShare: $uris")
+ viewModelScope.launch {
+ checkAuthorizationStatus(Unit).process(
+ failure = { e -> Timber.e(e, "Error while checking auth status") },
+ success = { status ->
+ if (status == AuthStatus.AUTHORIZED) {
+ if (uris.size == 1) {
+ commands.emit(Command.Sharing.File(uris.first()))
+ } else {
+ commands.emit(Command.Sharing.Files(uris))
+ }
+ }
+ }
+ )
+ }
+ }
+
+ fun onIntentMultipleImageShare(uris: List) {
+ Timber.d("onIntentImageShare: $uris")
+ viewModelScope.launch {
+ checkAuthorizationStatus(Unit).process(
+ failure = { e -> Timber.e(e, "Error while checking auth status") },
+ success = { status ->
+ if (status == AuthStatus.AUTHORIZED) {
+ if (uris.size == 1) {
+ commands.emit(Command.Sharing.Image(uris.first()))
+ } else {
+ commands.emit(Command.Sharing.Images(uris))
+ }
}
}
)
@@ -182,6 +218,12 @@ class MainViewModel(
object LogoutDueToAccountDeletion : Command()
class OpenCreateNewType(val type: Id) : Command()
data class Error(val msg: String) : Command()
- data class AddToAnytype(val data: String): Command()
+ sealed class Sharing : Command() {
+ data class Text(val data: String) : Sharing()
+ data class Image(val uri: String): Sharing()
+ data class Images(val uris: List): Sharing()
+ data class File(val uri: String): Sharing()
+ data class Files(val uris: List): Sharing()
+ }
}
}
\ No newline at end of file
diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/sharing/AddToAnytypeViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/sharing/AddToAnytypeViewModel.kt
index 302c06af3d..72ef2778ce 100644
--- a/presentation/src/main/java/com/anytypeio/anytype/presentation/sharing/AddToAnytypeViewModel.kt
+++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/sharing/AddToAnytypeViewModel.kt
@@ -11,14 +11,21 @@ import com.anytypeio.anytype.analytics.base.EventsDictionary.CLICK_ONBOARDING_TO
import com.anytypeio.anytype.analytics.base.EventsPropertiesKey
import com.anytypeio.anytype.analytics.event.EventAnalytics
import com.anytypeio.anytype.analytics.props.Props
+import com.anytypeio.anytype.core_models.Block
+import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.MarketplaceObjectTypeIds
import com.anytypeio.anytype.core_models.NO_VALUE
import com.anytypeio.anytype.core_models.ObjectOrigin
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.Relations
+import com.anytypeio.anytype.core_models.ext.EMPTY_STRING_VALUE
+import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.core_utils.ext.msg
import com.anytypeio.anytype.domain.account.AwaitAccountStartManager
import com.anytypeio.anytype.domain.base.fold
+import com.anytypeio.anytype.domain.base.onSuccess
+import com.anytypeio.anytype.domain.device.FileSharer
+import com.anytypeio.anytype.domain.media.UploadFile
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.objects.CreateBookmarkObject
import com.anytypeio.anytype.domain.objects.CreatePrefilledNote
@@ -31,6 +38,7 @@ import com.anytypeio.anytype.presentation.spaces.SpaceGradientProvider
import com.anytypeio.anytype.presentation.spaces.SpaceIconView
import com.anytypeio.anytype.presentation.spaces.spaceIcon
import javax.inject.Inject
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -48,7 +56,9 @@ class AddToAnytypeViewModel(
private val getSpaceViews: GetSpaceViews,
private val urlBuilder: UrlBuilder,
private val awaitAccountStartManager: AwaitAccountStartManager,
- private val analytics: Analytics
+ private val analytics: Analytics,
+ private val uploadFile: UploadFile,
+ private val fileSharer: FileSharer
) : BaseViewModel() {
private val selectedSpaceId = MutableStateFlow(NO_VALUE)
@@ -108,6 +118,74 @@ class AddToAnytypeViewModel(
}
}
+ fun onShareMedia(uris: List) {
+ viewModelScope.launch(Dispatchers.IO) {
+ val targetSpaceView = spaceViews.value.firstOrNull { view ->
+ view.isSelected
+ }
+ val targetSpaceId = targetSpaceView?.obj?.targetSpaceId!!
+ val paths = uris.mapNotNull { uri ->
+ fileSharer.getPath(uri)
+ }
+ val files = mutableListOf()
+ paths.forEach { path ->
+ uploadFile.async(
+ UploadFile.Params(
+ path = path,
+ space = SpaceId(targetSpaceId),
+ // Temporary workaround to fix issue on the MW side.
+ type = Block.Content.File.Type.NONE
+ )
+ ).onSuccess { obj ->
+ files.add(obj)
+ }
+ }
+ if (files.size == 1) {
+ if (targetSpaceId == spaceManager.get()) {
+ navigation.emit(OpenObjectNavigation.OpenEditor(files.first()))
+ } else {
+ with(commands) {
+ emit(Command.ObjectAddToSpaceToast(targetSpaceView.obj.name))
+ emit(Command.Dismiss)
+ }
+ }
+ } else {
+ val startTime = System.currentTimeMillis()
+ createPrefilledNote.async(
+ CreatePrefilledNote.Params(
+ text = EMPTY_STRING_VALUE,
+ space = targetSpaceId,
+ details = mapOf(
+ Relations.ORIGIN to ObjectOrigin.SHARING_EXTENSION.code.toDouble()
+ ),
+ attachments = files
+ )
+ ).fold(
+ onSuccess = { result ->
+ sendAnalyticsObjectCreateEvent(
+ analytics = analytics,
+ objType = MarketplaceObjectTypeIds.NOTE,
+ route = EventsDictionary.Routes.sharingExtension,
+ startTime = startTime
+ )
+ if (targetSpaceId == spaceManager.get()) {
+ navigation.emit(OpenObjectNavigation.OpenEditor(result))
+ } else {
+ with(commands) {
+ emit(Command.ObjectAddToSpaceToast(targetSpaceView.obj.name))
+ emit(Command.Dismiss)
+ }
+ }
+ },
+ onFailure = {
+ Timber.d(it, "Error while creating note")
+ sendToast("Error while creating note: ${it.msg()}")
+ }
+ )
+ }
+ }
+ }
+
fun onCreateBookmark(url: String) {
viewModelScope.launch {
val targetSpaceView = spaceViews.value.firstOrNull { view ->
@@ -225,7 +303,9 @@ class AddToAnytypeViewModel(
private val getSpaceViews: GetSpaceViews,
private val urlBuilder: UrlBuilder,
private val awaitAccountStartManager: AwaitAccountStartManager,
- private val analytics: Analytics
+ private val analytics: Analytics,
+ private val uploadFile: UploadFile,
+ private val fileSharer: FileSharer
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun create(modelClass: Class): T {
@@ -236,7 +316,9 @@ class AddToAnytypeViewModel(
getSpaceViews = getSpaceViews,
urlBuilder = urlBuilder,
awaitAccountStartManager = awaitAccountStartManager,
- analytics = analytics
+ analytics = analytics,
+ uploadFile = uploadFile,
+ fileSharer = fileSharer
) as T
}
}
diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/util/CopyFileToCache.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/util/CopyFileToCache.kt
index 45e60a8b2c..e28d72505c 100644
--- a/presentation/src/main/java/com/anytypeio/anytype/presentation/util/CopyFileToCache.kt
+++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/util/CopyFileToCache.kt
@@ -308,7 +308,7 @@ private fun Context.deleteTemporaryFolder() {
/**
* Return /storage/emulated/0/Android/data/package/files/$TEMPORARY_DIRECTORY_NAME directory
*/
-private fun Context.getExternalFilesDirTemp(): File? = getExternalFilesDir(TEMPORARY_DIRECTORY_NAME)
+fun Context.getExternalFilesDirTemp(): File? = getExternalFilesDir(TEMPORARY_DIRECTORY_NAME)
/**
* Return /storage/emulated/0/Android/data/io.anytype.app/files/networkModeConfig directory