Benoit Marty
4 months ago
committed by
Benoit Marty
21 changed files with 771 additions and 6 deletions
@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
|
||||
/* |
||||
* Copyright (c) 2023 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
plugins { |
||||
id("io.element.android-library") |
||||
id("kotlin-parcelize") |
||||
} |
||||
|
||||
android { |
||||
namespace = "io.element.android.features.share.api" |
||||
} |
||||
|
||||
dependencies { |
||||
implementation(projects.libraries.architecture) |
||||
implementation(projects.libraries.matrix.api) |
||||
} |
@ -0,0 +1,41 @@
@@ -0,0 +1,41 @@
|
||||
/* |
||||
* Copyright (c) 2024 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.share.api |
||||
|
||||
import android.content.Intent |
||||
import com.bumble.appyx.core.modality.BuildContext |
||||
import com.bumble.appyx.core.node.Node |
||||
import com.bumble.appyx.core.plugin.Plugin |
||||
import io.element.android.libraries.architecture.FeatureEntryPoint |
||||
import io.element.android.libraries.architecture.NodeInputs |
||||
import io.element.android.libraries.matrix.api.core.RoomId |
||||
|
||||
interface ShareEntryPoint : FeatureEntryPoint { |
||||
data class Params(val intent: Intent) : NodeInputs |
||||
|
||||
fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder |
||||
|
||||
interface Callback : Plugin { |
||||
fun onDone(roomIds: List<RoomId>) |
||||
} |
||||
|
||||
interface NodeBuilder { |
||||
fun params(params: Params): NodeBuilder |
||||
fun callback(callback: Callback): NodeBuilder |
||||
fun build(): Node |
||||
} |
||||
} |
@ -0,0 +1,69 @@
@@ -0,0 +1,69 @@
|
||||
/* |
||||
* Copyright (c) 2023 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
plugins { |
||||
id("io.element.android-compose-library") |
||||
alias(libs.plugins.ksp) |
||||
alias(libs.plugins.anvil) |
||||
id("kotlin-parcelize") |
||||
} |
||||
|
||||
android { |
||||
namespace = "io.element.android.features.share.impl" |
||||
|
||||
testOptions { |
||||
unitTests { |
||||
isIncludeAndroidResources = true |
||||
} |
||||
} |
||||
} |
||||
|
||||
anvil { |
||||
generateDaggerFactories.set(true) |
||||
} |
||||
|
||||
dependencies { |
||||
anvil(projects.anvilcodegen) |
||||
implementation(projects.anvilannotations) |
||||
|
||||
implementation(projects.appconfig) |
||||
implementation(projects.libraries.androidutils) |
||||
implementation(projects.libraries.core) |
||||
implementation(projects.libraries.androidutils) |
||||
implementation(projects.libraries.architecture) |
||||
implementation(projects.libraries.matrix.api) |
||||
implementation(projects.libraries.matrixui) |
||||
implementation(projects.libraries.designsystem) |
||||
implementation(projects.libraries.mediaupload.api) |
||||
implementation(projects.libraries.roomselect.api) |
||||
implementation(projects.libraries.uiStrings) |
||||
implementation(projects.libraries.testtags) |
||||
api(libs.statemachine) |
||||
api(projects.features.share.api) |
||||
|
||||
testImplementation(libs.test.junit) |
||||
testImplementation(libs.coroutines.test) |
||||
testImplementation(libs.molecule.runtime) |
||||
testImplementation(libs.test.truth) |
||||
testImplementation(libs.test.turbine) |
||||
testImplementation(libs.test.robolectric) |
||||
testImplementation(libs.androidx.compose.ui.test.junit) |
||||
testImplementation(projects.libraries.matrix.test) |
||||
testImplementation(projects.tests.testutils) |
||||
testReleaseImplementation(libs.androidx.compose.ui.test.manifest) |
||||
|
||||
ksp(libs.showkase.processor) |
||||
} |
@ -0,0 +1,49 @@
@@ -0,0 +1,49 @@
|
||||
/* |
||||
* Copyright (c) 2024 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.share.impl |
||||
|
||||
import com.bumble.appyx.core.modality.BuildContext |
||||
import com.bumble.appyx.core.node.Node |
||||
import com.bumble.appyx.core.plugin.Plugin |
||||
import com.squareup.anvil.annotations.ContributesBinding |
||||
import io.element.android.features.share.api.ShareEntryPoint |
||||
import io.element.android.libraries.architecture.createNode |
||||
import io.element.android.libraries.di.SessionScope |
||||
import javax.inject.Inject |
||||
|
||||
@ContributesBinding(SessionScope::class) |
||||
class DefaultShareEntryPoint @Inject constructor() : ShareEntryPoint { |
||||
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): ShareEntryPoint.NodeBuilder { |
||||
val plugins = ArrayList<Plugin>() |
||||
|
||||
return object : ShareEntryPoint.NodeBuilder { |
||||
override fun params(params: ShareEntryPoint.Params): ShareEntryPoint.NodeBuilder { |
||||
plugins += ShareNode.Inputs(intent = params.intent) |
||||
return this |
||||
} |
||||
|
||||
override fun callback(callback: ShareEntryPoint.Callback): ShareEntryPoint.NodeBuilder { |
||||
plugins += callback |
||||
return this |
||||
} |
||||
|
||||
override fun build(): Node { |
||||
return parentNode.createNode<ShareNode>(buildContext, plugins) |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
|
||||
/* |
||||
* Copyright (c) 2024 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.share.impl |
||||
|
||||
sealed interface ShareEvents { |
||||
data object ClearError : ShareEvents |
||||
} |
@ -0,0 +1,126 @@
@@ -0,0 +1,126 @@
|
||||
/* |
||||
* Copyright (c) 2024 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.share.impl |
||||
|
||||
import android.content.ComponentName |
||||
import android.content.Context |
||||
import android.content.Intent |
||||
import android.content.pm.PackageManager |
||||
import android.content.pm.ResolveInfo |
||||
import android.net.Uri |
||||
import com.squareup.anvil.annotations.ContributesBinding |
||||
import io.element.android.libraries.androidutils.compat.getParcelableArrayListExtraCompat |
||||
import io.element.android.libraries.androidutils.compat.getParcelableExtraCompat |
||||
import io.element.android.libraries.androidutils.compat.queryIntentActivitiesCompat |
||||
import io.element.android.libraries.core.mimetype.MimeTypes |
||||
import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeAny |
||||
import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeApplication |
||||
import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeAudio |
||||
import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeFile |
||||
import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeImage |
||||
import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeText |
||||
import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeVideo |
||||
import io.element.android.libraries.di.AppScope |
||||
import io.element.android.libraries.di.ApplicationContext |
||||
import javax.inject.Inject |
||||
|
||||
interface ShareIntentHandler { |
||||
suspend fun handleIncomingShareIntent( |
||||
intent: Intent, |
||||
onFile: suspend (List<DefaultShareIntentHandler.FileToShare>) -> Boolean, |
||||
onPlainText: suspend (String) -> Boolean, |
||||
): Boolean |
||||
} |
||||
|
||||
@ContributesBinding(AppScope::class) |
||||
class DefaultShareIntentHandler @Inject constructor( |
||||
@ApplicationContext private val context: Context, |
||||
) : ShareIntentHandler { |
||||
data class FileToShare( |
||||
val uri: Uri, |
||||
val mimeType: String, |
||||
) |
||||
|
||||
/** |
||||
* This methods aims to handle incoming share intents. |
||||
* |
||||
* @return true if it can handle the intent data, false otherwise |
||||
*/ |
||||
override suspend fun handleIncomingShareIntent( |
||||
intent: Intent, |
||||
onFile: suspend (List<FileToShare>) -> Boolean, |
||||
onPlainText: suspend (String) -> Boolean, |
||||
): Boolean { |
||||
val type = intent.resolveType(context) ?: return false |
||||
return when { |
||||
type == MimeTypes.PlainText -> handlePlainText(intent, onPlainText) |
||||
type.isMimeTypeImage() || |
||||
type.isMimeTypeVideo() || |
||||
type.isMimeTypeAudio() || |
||||
type.isMimeTypeApplication() || |
||||
type.isMimeTypeFile() || |
||||
type.isMimeTypeText() || |
||||
type.isMimeTypeAny() -> onFile(getIncomingFiles(intent, type)) |
||||
else -> false |
||||
} |
||||
} |
||||
|
||||
private suspend fun handlePlainText(intent: Intent, onPlainText: suspend (String) -> Boolean): Boolean { |
||||
val content = intent.getCharSequenceExtra(Intent.EXTRA_TEXT)?.toString() |
||||
return if (content?.isNotEmpty() == true) { |
||||
onPlainText(content) |
||||
} else { |
||||
false |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Use this function to retrieve files which are shared from another application or internally |
||||
* by using android.intent.action.SEND or android.intent.action.SEND_MULTIPLE actions. |
||||
*/ |
||||
private fun getIncomingFiles(data: Intent, type: String): List<FileToShare> { |
||||
val uriList = mutableListOf<Uri>() |
||||
if (data.action == Intent.ACTION_SEND) { |
||||
data.getParcelableExtraCompat<Uri>(Intent.EXTRA_STREAM)?.let { uriList.add(it) } |
||||
} else if (data.action == Intent.ACTION_SEND_MULTIPLE) { |
||||
val extraUriList: List<Uri>? = data.getParcelableArrayListExtraCompat(Intent.EXTRA_STREAM) |
||||
extraUriList?.let { uriList.addAll(it) } |
||||
} |
||||
val resInfoList: List<ResolveInfo> = context.packageManager.queryIntentActivitiesCompat(data, PackageManager.MATCH_DEFAULT_ONLY) |
||||
uriList.forEach { |
||||
for (resolveInfo in resInfoList) { |
||||
val packageName: String = resolveInfo.activityInfo.packageName |
||||
// Replace implicit intent by an explicit to fix crash on some devices like Xiaomi. |
||||
// see https://juejin.cn/post/7031736325422186510 |
||||
try { |
||||
context.grantUriPermission(packageName, it, Intent.FLAG_GRANT_READ_URI_PERMISSION) |
||||
} catch (e: Exception) { |
||||
continue |
||||
} |
||||
data.action = null |
||||
data.component = ComponentName(packageName, resolveInfo.activityInfo.name) |
||||
break |
||||
} |
||||
} |
||||
return uriList.map { uri -> |
||||
FileToShare( |
||||
uri = uri, |
||||
mimeType = type |
||||
) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,101 @@
@@ -0,0 +1,101 @@
|
||||
/* |
||||
* Copyright (c) 20244 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.share.impl |
||||
|
||||
import android.content.Intent |
||||
import android.os.Parcelable |
||||
import androidx.compose.foundation.layout.Box |
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.ui.Modifier |
||||
import com.bumble.appyx.core.composable.Children |
||||
import com.bumble.appyx.core.modality.BuildContext |
||||
import com.bumble.appyx.core.navigation.model.permanent.PermanentNavModel |
||||
import com.bumble.appyx.core.node.Node |
||||
import com.bumble.appyx.core.node.ParentNode |
||||
import com.bumble.appyx.core.plugin.Plugin |
||||
import dagger.assisted.Assisted |
||||
import dagger.assisted.AssistedInject |
||||
import io.element.android.anvilannotations.ContributesNode |
||||
import io.element.android.features.share.api.ShareEntryPoint |
||||
import io.element.android.libraries.architecture.NodeInputs |
||||
import io.element.android.libraries.architecture.inputs |
||||
import io.element.android.libraries.di.SessionScope |
||||
import io.element.android.libraries.matrix.api.core.RoomId |
||||
import io.element.android.libraries.roomselect.api.RoomSelectEntryPoint |
||||
import io.element.android.libraries.roomselect.api.RoomSelectMode |
||||
import kotlinx.parcelize.Parcelize |
||||
|
||||
@ContributesNode(SessionScope::class) |
||||
class ShareNode @AssistedInject constructor( |
||||
@Assisted buildContext: BuildContext, |
||||
@Assisted plugins: List<Plugin>, |
||||
presenterFactory: SharePresenter.Factory, |
||||
private val roomSelectEntryPoint: RoomSelectEntryPoint, |
||||
) : ParentNode<ShareNode.NavTarget>( |
||||
navModel = PermanentNavModel( |
||||
navTargets = setOf(NavTarget), |
||||
savedStateMap = buildContext.savedStateMap, |
||||
), |
||||
buildContext = buildContext, |
||||
plugins = plugins, |
||||
) { |
||||
@Parcelize |
||||
object NavTarget : Parcelable |
||||
|
||||
data class Inputs(val intent: Intent) : NodeInputs |
||||
|
||||
private val inputs = inputs<Inputs>() |
||||
private val presenter = presenterFactory.create(inputs.intent) |
||||
private val callbacks = plugins.filterIsInstance<ShareEntryPoint.Callback>() |
||||
|
||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { |
||||
val callback = object : RoomSelectEntryPoint.Callback { |
||||
override fun onRoomSelected(roomIds: List<RoomId>) { |
||||
presenter.onRoomSelected(roomIds) |
||||
} |
||||
|
||||
override fun onCancel() { |
||||
navigateUp() |
||||
} |
||||
} |
||||
|
||||
return roomSelectEntryPoint.nodeBuilder(this, buildContext) |
||||
.callback(callback) |
||||
.params(RoomSelectEntryPoint.Params(mode = RoomSelectMode.Share)) |
||||
.build() |
||||
} |
||||
|
||||
@Composable |
||||
override fun View(modifier: Modifier) { |
||||
Box(modifier = modifier) { |
||||
// Will render to room select screen |
||||
Children( |
||||
navModel = navModel, |
||||
) |
||||
|
||||
val state = presenter.present() |
||||
ShareView( |
||||
state = state, |
||||
onShareSuccess = ::onShareSuccess, |
||||
) |
||||
} |
||||
} |
||||
|
||||
private fun onShareSuccess(roomIds: List<RoomId>) { |
||||
callbacks.forEach { it.onDone(roomIds) } |
||||
} |
||||
} |
@ -0,0 +1,112 @@
@@ -0,0 +1,112 @@
|
||||
/* |
||||
* Copyright (c) 2024 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.share.impl |
||||
|
||||
import android.content.Intent |
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.runtime.MutableState |
||||
import androidx.compose.runtime.mutableStateOf |
||||
import dagger.assisted.Assisted |
||||
import dagger.assisted.AssistedFactory |
||||
import dagger.assisted.AssistedInject |
||||
import io.element.android.libraries.architecture.AsyncAction |
||||
import io.element.android.libraries.architecture.Presenter |
||||
import io.element.android.libraries.architecture.runCatchingUpdatingState |
||||
import io.element.android.libraries.core.bool.orFalse |
||||
import io.element.android.libraries.matrix.api.MatrixClient |
||||
import io.element.android.libraries.matrix.api.core.RoomId |
||||
import io.element.android.libraries.mediaupload.api.MediaPreProcessor |
||||
import io.element.android.libraries.mediaupload.api.MediaSender |
||||
import kotlinx.coroutines.CoroutineScope |
||||
import kotlinx.coroutines.launch |
||||
|
||||
class SharePresenter @AssistedInject constructor( |
||||
@Assisted private val intent: Intent, |
||||
private val appCoroutineScope: CoroutineScope, |
||||
private val shareIntentHandler: ShareIntentHandler, |
||||
private val matrixClient: MatrixClient, |
||||
private val mediaPreProcessor: MediaPreProcessor, |
||||
) : Presenter<ShareState> { |
||||
@AssistedFactory |
||||
interface Factory { |
||||
fun create(intent: Intent): SharePresenter |
||||
} |
||||
|
||||
private val shareActionState: MutableState<AsyncAction<List<RoomId>>> = mutableStateOf(AsyncAction.Uninitialized) |
||||
|
||||
fun onRoomSelected(roomIds: List<RoomId>) { |
||||
appCoroutineScope.share(intent, roomIds, shareActionState) |
||||
} |
||||
|
||||
@Composable |
||||
override fun present(): ShareState { |
||||
fun handleEvents(event: ShareEvents) { |
||||
when (event) { |
||||
ShareEvents.ClearError -> shareActionState.value = AsyncAction.Uninitialized |
||||
} |
||||
} |
||||
|
||||
return ShareState( |
||||
shareAction = shareActionState.value, |
||||
eventSink = { handleEvents(it) } |
||||
) |
||||
} |
||||
|
||||
private fun CoroutineScope.share( |
||||
intent: Intent, |
||||
roomIds: List<RoomId>, |
||||
shareActionState: MutableState<AsyncAction<List<RoomId>>>, |
||||
) = launch { |
||||
suspend { |
||||
val result = shareIntentHandler.handleIncomingShareIntent( |
||||
intent, |
||||
onFile = { filesToShare -> |
||||
roomIds |
||||
.map { roomId -> |
||||
val room = matrixClient.getRoom(roomId) ?: return@map false |
||||
val mediaSender = MediaSender(preProcessor = mediaPreProcessor, room = room) |
||||
filesToShare |
||||
.map { fileToShare -> |
||||
mediaSender.sendMedia( |
||||
uri = fileToShare.uri, |
||||
mimeType = fileToShare.mimeType, |
||||
compressIfPossible = true, |
||||
).isSuccess |
||||
} |
||||
.all { it } |
||||
} |
||||
.all { it } |
||||
}, |
||||
onPlainText = { text -> |
||||
roomIds |
||||
.map { roomId -> |
||||
matrixClient.getRoom(roomId)?.sendMessage( |
||||
body = text, |
||||
htmlBody = null, |
||||
mentions = emptyList(), |
||||
)?.isSuccess.orFalse() |
||||
} |
||||
.all { it } |
||||
} |
||||
) |
||||
if (!result) { |
||||
throw Exception("Failed to handle incoming share intent") |
||||
} |
||||
roomIds |
||||
}.runCatchingUpdatingState(shareActionState) |
||||
} |
||||
} |
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
/* |
||||
* Copyright (c) 2024 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.share.impl |
||||
|
||||
import io.element.android.libraries.architecture.AsyncAction |
||||
import io.element.android.libraries.matrix.api.core.RoomId |
||||
|
||||
data class ShareState( |
||||
val shareAction: AsyncAction<List<RoomId>>, |
||||
val eventSink: (ShareEvents) -> Unit |
||||
) |
@ -0,0 +1,47 @@
@@ -0,0 +1,47 @@
|
||||
/* |
||||
* Copyright (c) 2024 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.share.impl |
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider |
||||
import io.element.android.libraries.architecture.AsyncAction |
||||
import io.element.android.libraries.matrix.api.core.RoomId |
||||
|
||||
open class ShareStateProvider : PreviewParameterProvider<ShareState> { |
||||
override val values: Sequence<ShareState> |
||||
get() = sequenceOf( |
||||
aShareState(), |
||||
aShareState( |
||||
shareAction = AsyncAction.Loading, |
||||
), |
||||
aShareState( |
||||
shareAction = AsyncAction.Success( |
||||
listOf(RoomId("!room2:domain")), |
||||
) |
||||
), |
||||
aShareState( |
||||
shareAction = AsyncAction.Failure(Throwable("error")), |
||||
), |
||||
) |
||||
} |
||||
|
||||
fun aShareState( |
||||
shareAction: AsyncAction<List<RoomId>> = AsyncAction.Uninitialized, |
||||
eventSink: (ShareEvents) -> Unit = {} |
||||
) = ShareState( |
||||
shareAction = shareAction, |
||||
eventSink = eventSink |
||||
) |
@ -0,0 +1,49 @@
@@ -0,0 +1,49 @@
|
||||
/* |
||||
* Copyright (c) 2024 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.share.impl |
||||
|
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.ui.tooling.preview.PreviewParameter |
||||
import io.element.android.libraries.designsystem.components.async.AsyncActionView |
||||
import io.element.android.libraries.designsystem.preview.ElementPreview |
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight |
||||
import io.element.android.libraries.matrix.api.core.RoomId |
||||
|
||||
@Composable |
||||
fun ShareView( |
||||
state: ShareState, |
||||
onShareSuccess: (List<RoomId>) -> Unit, |
||||
) { |
||||
AsyncActionView( |
||||
async = state.shareAction, |
||||
onSuccess = { |
||||
onShareSuccess(it) |
||||
}, |
||||
onErrorDismiss = { |
||||
state.eventSink(ShareEvents.ClearError) |
||||
}, |
||||
) |
||||
} |
||||
|
||||
@PreviewsDayNight |
||||
@Composable |
||||
internal fun ShareViewPreview(@PreviewParameter(ShareStateProvider::class) state: ShareState) = ElementPreview { |
||||
ShareView( |
||||
state = state, |
||||
onShareSuccess = {} |
||||
) |
||||
} |
Loading…
Reference in new issue