Media: implements share action
This commit is contained in:
parent
7386936217
commit
1c01c0a6cc
6 changed files with 57 additions and 12 deletions
|
|
@ -18,25 +18,31 @@ package io.element.android.features.messages.impl.media.local
|
|||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.provider.MediaStore
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.net.toFile
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.ApplicationContext
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.InputStream
|
||||
import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class AndroidLocalMediaActionsHandler @Inject constructor(
|
||||
class AndroidLocalMediaActions @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val coroutineDispatchers: CoroutineDispatchers,
|
||||
) : LocalMediaActionsHandler {
|
||||
private val buildMeta: BuildMeta,
|
||||
) : LocalMediaActions {
|
||||
|
||||
override suspend fun saveOnDisk(localMedia: LocalMedia): Result<Unit> = withContext(coroutineDispatchers.io) {
|
||||
runCatching {
|
||||
|
|
@ -45,11 +51,28 @@ class AndroidLocalMediaActionsHandler @Inject constructor(
|
|||
} else {
|
||||
saveOnDiskUsingExternalStorageApi(localMedia)
|
||||
}
|
||||
}.onSuccess {
|
||||
Timber.v("Save on disk succeed")
|
||||
}.onFailure {
|
||||
Timber.e(it, "Save on disk failed")
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun share(localMedia: LocalMedia): Result<Unit> {
|
||||
TODO("Not yet implemented")
|
||||
override suspend fun share(localMedia: LocalMedia): Result<Unit> = withContext(coroutineDispatchers.io) {
|
||||
runCatching {
|
||||
val authority = "${buildMeta.applicationId}.fileprovider"
|
||||
val uriFromFileProvider = FileProvider.getUriForFile(context, authority, localMedia.toFile())
|
||||
val shareMediaIntent = Intent(Intent.ACTION_VIEW)
|
||||
.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
|
||||
.setDataAndType(uriFromFileProvider, localMedia.mimeType)
|
||||
withContext(coroutineDispatchers.main) {
|
||||
context.startActivity(shareMediaIntent, null)
|
||||
}
|
||||
}.onSuccess {
|
||||
Timber.v("Share media succeed")
|
||||
}.onFailure {
|
||||
Timber.e(it, "Share media failed")
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
|
|
@ -85,4 +108,8 @@ class AndroidLocalMediaActionsHandler @Inject constructor(
|
|||
private fun LocalMedia.openStream(): InputStream? {
|
||||
return context.contentResolver.openInputStream(uri)
|
||||
}
|
||||
|
||||
private fun LocalMedia.toFile(): File {
|
||||
return uri.toFile()
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
package io.element.android.features.messages.impl.media.local
|
||||
|
||||
interface LocalMediaActionsHandler {
|
||||
interface LocalMediaActions {
|
||||
suspend fun saveOnDisk(localMedia: LocalMedia): Result<Unit>
|
||||
suspend fun share(localMedia: LocalMedia): Result<Unit>
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ package io.element.android.features.messages.impl.media.viewer
|
|||
|
||||
sealed interface MediaViewerEvents {
|
||||
object SaveOnDisk: MediaViewerEvents
|
||||
object Share: MediaViewerEvents
|
||||
object RetryLoading : MediaViewerEvents
|
||||
object ClearLoadingError : MediaViewerEvents
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import dagger.assisted.Assisted
|
|||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.features.messages.impl.media.local.LocalMedia
|
||||
import io.element.android.features.messages.impl.media.local.LocalMediaActionsHandler
|
||||
import io.element.android.features.messages.impl.media.local.LocalMediaActions
|
||||
import io.element.android.features.messages.impl.media.local.LocalMediaFactory
|
||||
import io.element.android.features.messages.impl.media.local.createFromMediaFile
|
||||
import io.element.android.libraries.architecture.Async
|
||||
|
|
@ -42,7 +42,7 @@ class MediaViewerPresenter @AssistedInject constructor(
|
|||
@Assisted private val inputs: MediaViewerNode.Inputs,
|
||||
private val localMediaFactory: LocalMediaFactory,
|
||||
private val mediaLoader: MatrixMediaLoader,
|
||||
private val mediaActionsHandler: LocalMediaActionsHandler,
|
||||
private val mediaActionsHandler: LocalMediaActions,
|
||||
) : Presenter<MediaViewerState> {
|
||||
|
||||
@AssistedFactory
|
||||
|
|
@ -72,6 +72,7 @@ class MediaViewerPresenter @AssistedInject constructor(
|
|||
MediaViewerEvents.RetryLoading -> loadMediaTrigger++
|
||||
MediaViewerEvents.ClearLoadingError -> localMedia.value = Async.Uninitialized
|
||||
MediaViewerEvents.SaveOnDisk -> coroutineScope.saveOnDisk(localMedia.value)
|
||||
MediaViewerEvents.Share -> coroutineScope.share(localMedia.value)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -102,11 +103,20 @@ class MediaViewerPresenter @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun CoroutineScope.saveOnDisk(value: Async<LocalMedia>) = launch {
|
||||
when (value) {
|
||||
is Async.Success -> mediaActionsHandler.saveOnDisk(value.state)
|
||||
private fun CoroutineScope.saveOnDisk(localMedia: Async<LocalMedia>) = launch {
|
||||
when (localMedia) {
|
||||
is Async.Success -> mediaActionsHandler.saveOnDisk(localMedia.state)
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun CoroutineScope.share(localMedia: Async<LocalMedia>) = launch {
|
||||
when (localMedia) {
|
||||
is Async.Success -> mediaActionsHandler.share(localMedia.state)
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Download
|
||||
import androidx.compose.material.icons.filled.Save
|
||||
import androidx.compose.material.icons.filled.Share
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
|
|
@ -137,6 +137,13 @@ private fun MediaViewerTopBar(
|
|||
title = {},
|
||||
navigationIcon = { BackButton(onClick = onBackPressed) },
|
||||
actions = {
|
||||
IconButton(
|
||||
onClick = {
|
||||
eventSink(MediaViewerEvents.Share)
|
||||
},
|
||||
) {
|
||||
Icon(imageVector = Icons.Default.Share, contentDescription = stringResource(id = string.action_share))
|
||||
}
|
||||
IconButton(
|
||||
onClick = {
|
||||
eventSink(MediaViewerEvents.SaveOnDisk)
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ kotlin.code.style=official
|
|||
# thereby reducing the size of the R class for that library
|
||||
android.nonTransitiveRClass=true
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.caching=false
|
||||
org.gradle.configureondemand=true
|
||||
org.gradle.parallel=true
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue