Skip to content

Commit

Permalink
[i192] remove attachment picker limitation in compose (#5104)
Browse files Browse the repository at this point in the history
* [i192] remove attachment picker limitation

* [i192] add docs

* [i192] spotless, api dump, detekt

* [i192] fix docs

* [i192] add CHANGELOG
  • Loading branch information
kanat authored Dec 11, 2023
1 parent 7406f85 commit f651c69
Show file tree
Hide file tree
Showing 13 changed files with 90 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
* `MessagesViewModelFactory.showThreadSeparatorInEmptyThread` was added to control the visibility of the thread separator in empty threads.

### ⬆️ Improved
- Removed attachment picker customization limitation for `AttachmentsPickerTabFactory` non-file implementations. [#5104](https://github.com/GetStream/stream-chat-android/pull/5104)

### ✅ Added

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ This is a very simple way to customize the behavior and connect the `AttachmentP

## Customization

### Customizing the existing UI

You can customize the `AttachmentsPicker` using the following parameters:

```kotlin
Expand Down Expand Up @@ -126,6 +128,8 @@ This snippet will provide the following UI:

As you can see, the `AttachmentsPicker` component now occupies the whole screen.

### Providing a custom tab

Now let's see how to use the `tabFactories` parameter to replace the media capture tab in the picker with a custom one.

We'll need to create a custom `AttachmentsPickerTabFactory` for our custom tab and implement the methods to render the icon and the content of the tab:
Expand All @@ -136,6 +140,12 @@ class AttachmentsPickerCustomTabFactory: AttachmentsPickerTabFactory {
override val attachmentsPickerMode: AttachmentsPickerMode
get() = CustomPickerMode()

override fun isPickerTabEnabled(): Boolean {
// Place your custom logic here to be able to disable the tab if needed.
// Return true if the tab should be enabled.
return true
}

@Composable
override fun PickerTabIcon(isEnabled: Boolean, isSelected: Boolean) {
Icon(
Expand All @@ -156,6 +166,11 @@ class AttachmentsPickerCustomTabFactory: AttachmentsPickerTabFactory {
onAttachmentItemSelected: (AttachmentPickerItemState) -> Unit,
onAttachmentsSubmitted: (List<AttachmentMetaData>) -> Unit,
) {

LaunchedEffect(Unit) {
onAttachmentsChanged(emptyList())
}

Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
Expand All @@ -169,9 +184,37 @@ class AttachmentsPickerCustomTabFactory: AttachmentsPickerTabFactory {
}
}
```
#### Setup initial attachments list
Please take into account that for the `AttachmentsPicker` component to work correctly, you need to call `onAttachmentsChanged()` with a list of attachments to select from when the tab is first rendered.
In case you don't have any attachments to select from, you can simply pass an `emptyList()` as shown below:
```kotlin
LaunchedEffect(Unit) {
onAttachmentsChanged(emptyList())
}
```

Next, we'll remove the factory for the media capture tab, add a factory for our custom tab and pass the resulting factory list to the picker:
#### Attachments selection
The `onAttachmentItemSelected()` is supposed to be used to select attachments from the list you passed to `onAttachmentsChanged()`, but if you don't have any attachments to select from, you can simply ignore this callback.

#### Submitting attachments
Finally, the `onAttachmentsSubmitted()` callback is used to submit the selected attachments to the `MessageComposer` component. This will also dismiss the `AttachmentsPicker`.

```kotlin
val selectedAttachment = AttachmentMetaData(
type = "custom_type",
title = "custom_title",
extraData = hashMapOf(
"custom_key" to "custom_value",
// other custom data
)
)
val selectedAttachments = listOf(selectedAttachment)
onAttachmentsSubmitted(selectedAttachments)
```

### Replacing an existing tab with a custom one

Next, we'll remove the factory for the media capture tab, add a factory for our custom tab and pass the resulting factory list to the picker:
```kotlin
val defaultTabFactories = AttachmentsPickerTabFactories.defaultFactories(takeImageEnabled = false, recordVideoEnabled = false)
val customTabFactories = listOf(AttachmentsPickerCustomTabFactory())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1185,6 +1185,7 @@ public final class io/getstream/chat/android/compose/ui/messages/attachments/fac
public fun PickerTabContent (Ljava/util/List;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)V
public fun PickerTabIcon (ZZLandroidx/compose/runtime/Composer;I)V
public fun getAttachmentsPickerMode ()Lio/getstream/chat/android/compose/state/messages/attachments/AttachmentsPickerMode;
public fun isPickerTabEnabled ()Z
}

public final class io/getstream/chat/android/compose/ui/messages/attachments/factory/AttachmentsPickerImagesTabFactory : io/getstream/chat/android/compose/ui/messages/attachments/factory/AttachmentsPickerTabFactory {
Expand All @@ -1193,6 +1194,7 @@ public final class io/getstream/chat/android/compose/ui/messages/attachments/fac
public fun PickerTabContent (Ljava/util/List;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)V
public fun PickerTabIcon (ZZLandroidx/compose/runtime/Composer;I)V
public fun getAttachmentsPickerMode ()Lio/getstream/chat/android/compose/state/messages/attachments/AttachmentsPickerMode;
public fun isPickerTabEnabled ()Z
}

public final class io/getstream/chat/android/compose/ui/messages/attachments/factory/AttachmentsPickerMediaCaptureTabFactory : io/getstream/chat/android/compose/ui/messages/attachments/factory/AttachmentsPickerTabFactory {
Expand All @@ -1201,6 +1203,7 @@ public final class io/getstream/chat/android/compose/ui/messages/attachments/fac
public fun PickerTabContent (Ljava/util/List;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)V
public fun PickerTabIcon (ZZLandroidx/compose/runtime/Composer;I)V
public fun getAttachmentsPickerMode ()Lio/getstream/chat/android/compose/state/messages/attachments/AttachmentsPickerMode;
public fun isPickerTabEnabled ()Z
}

public final class io/getstream/chat/android/compose/ui/messages/attachments/factory/AttachmentsPickerMediaCaptureTabFactory$PickerMediaMode : java/lang/Enum {
Expand All @@ -1223,6 +1226,11 @@ public abstract interface class io/getstream/chat/android/compose/ui/messages/at
public abstract fun PickerTabContent (Ljava/util/List;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)V
public abstract fun PickerTabIcon (ZZLandroidx/compose/runtime/Composer;I)V
public abstract fun getAttachmentsPickerMode ()Lio/getstream/chat/android/compose/state/messages/attachments/AttachmentsPickerMode;
public abstract fun isPickerTabEnabled ()Z
}

public final class io/getstream/chat/android/compose/ui/messages/attachments/factory/AttachmentsPickerTabFactory$DefaultImpls {
public static fun isPickerTabEnabled (Lio/getstream/chat/android/compose/ui/messages/attachments/factory/AttachmentsPickerTabFactory;)Z
}

public final class io/getstream/chat/android/compose/ui/messages/attachments/factory/ComposableSingletons$MissingPermissionContentKt {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import androidx.compose.material.IconButton
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
Expand Down Expand Up @@ -73,7 +73,8 @@ public fun AttachmentsPicker(
tabFactories: List<AttachmentsPickerTabFactory> = ChatTheme.attachmentsPickerTabFactories,
shape: Shape = ChatTheme.shapes.bottomSheet,
) {
var selectedTabIndex by remember { mutableStateOf(0) }
val defaultTabIndex = tabFactories.indexOfFirst { it.isPickerTabEnabled() }.takeIf { it >= 0 } ?: 0
var selectedTabIndex by remember { mutableIntStateOf(defaultTabIndex) }

Box(
modifier = Modifier
Expand Down Expand Up @@ -155,7 +156,7 @@ private fun AttachmentPickerOptions(
tabFactories.forEachIndexed { index, tabFactory ->

val isSelected = index == tabIndex
val isEnabled = isSelected || (!isSelected && !hasPickedAttachments)
val isEnabled = isSelected || (!hasPickedAttachments && tabFactory.isPickerTabEnabled())

IconButton(
enabled = isEnabled,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ public interface AttachmentsPickerTabFactory {
*/
public val attachmentsPickerMode: AttachmentsPickerMode

/**
* Determines if the picker tab is enabled.
*
* @return True if the tab is enabled, false otherwise.
*/
public fun isPickerTabEnabled(): Boolean = true

/**
* Emits an icon for the tab.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,10 @@ public class StorageHelperWrapper(
Attachment(
upload = fileFromUri,
type = it.type,
name = it.title ?: fileFromUri.name ?: "",
name = it.title ?: fileFromUri?.name ?: "",
fileSize = it.size.toInt(),
mimeType = it.mimeType,
extraData = it.extraData,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.List
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.RectangleShape
Expand Down Expand Up @@ -215,6 +216,11 @@ private object AttachmentsPickerCustomizationSnippet {
override val attachmentsPickerMode: AttachmentsPickerMode
get() = CustomPickerMode()

override fun isPickerTabEnabled(): Boolean {
// Return true if the tab should be enabled
return true
}

@Composable
override fun PickerTabIcon(isEnabled: Boolean, isSelected: Boolean) {
Icon(
Expand All @@ -235,6 +241,11 @@ private object AttachmentsPickerCustomizationSnippet {
onAttachmentItemSelected: (AttachmentPickerItemState) -> Unit,
onAttachmentsSubmitted: (List<AttachmentMetaData>) -> Unit,
) {

LaunchedEffect(Unit) {
onAttachmentsChanged(emptyList())
}

Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -553,17 +553,19 @@ public final class io/getstream/chat/android/ui/common/state/messages/composer/A
public static final field $stable I
public fun <init> ()V
public fun <init> (Landroid/content/Context;Ljava/io/File;)V
public fun <init> (Landroid/net/Uri;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/io/File;)V
public synthetic fun <init> (Landroid/net/Uri;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/io/File;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Landroid/net/Uri;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/io/File;Ljava/util/Map;)V
public synthetic fun <init> (Landroid/net/Uri;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/io/File;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Lio/getstream/chat/android/models/Attachment;)V
public final fun component1 ()Landroid/net/Uri;
public final fun component2 ()Ljava/lang/String;
public final fun component3 ()Ljava/lang/String;
public final fun component4 ()Ljava/lang/String;
public final fun component5 ()Ljava/io/File;
public final fun copy (Landroid/net/Uri;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/io/File;)Lio/getstream/chat/android/ui/common/state/messages/composer/AttachmentMetaData;
public static synthetic fun copy$default (Lio/getstream/chat/android/ui/common/state/messages/composer/AttachmentMetaData;Landroid/net/Uri;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/io/File;ILjava/lang/Object;)Lio/getstream/chat/android/ui/common/state/messages/composer/AttachmentMetaData;
public final fun component6 ()Ljava/util/Map;
public final fun copy (Landroid/net/Uri;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/io/File;Ljava/util/Map;)Lio/getstream/chat/android/ui/common/state/messages/composer/AttachmentMetaData;
public static synthetic fun copy$default (Lio/getstream/chat/android/ui/common/state/messages/composer/AttachmentMetaData;Landroid/net/Uri;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/io/File;Ljava/util/Map;ILjava/lang/Object;)Lio/getstream/chat/android/ui/common/state/messages/composer/AttachmentMetaData;
public fun equals (Ljava/lang/Object;)Z
public final fun getExtraData ()Ljava/util/Map;
public final fun getFile ()Ljava/io/File;
public final fun getMimeType ()Ljava/lang/String;
public final fun getSelectedPosition ()I
Expand All @@ -574,6 +576,7 @@ public final class io/getstream/chat/android/ui/common/state/messages/composer/A
public final fun getVideoLength ()J
public fun hashCode ()I
public final fun isSelected ()Z
public final fun setExtraData (Ljava/util/Map;)V
public final fun setFile (Ljava/io/File;)V
public final fun setMimeType (Ljava/lang/String;)V
public final fun setSelected (Z)V
Expand Down
1 change: 1 addition & 0 deletions stream-chat-android-ui-common/detekt-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<ID>MagicNumber:DurationFormatter.kt$DurationFormatter$1000</ID>
<ID>ReturnCount:AudioRecordingController.kt$AudioRecordingController$public fun completeRecording()</ID>
<ID>ReturnCount:AudioRecordingController.kt$AudioRecordingController$public fun toggleRecordingPlayback()</ID>
<ID>ReturnCount:StorageHelper.kt$StorageHelper$public fun getCachedFileFromUri( context: Context, attachmentMetaData: AttachmentMetaData, ): File?</ID>
<ID>TooGenericExceptionCaught:DefaultStreamMediaRecorder.kt$DefaultStreamMediaRecorder$e: Exception</ID>
<ID>TooGenericExceptionCaught:DefaultStreamMediaRecorder.kt$DefaultStreamMediaRecorder$e: Throwable</ID>
<ID>TooGenericExceptionCaught:DefaultStreamMediaRecorder.kt$DefaultStreamMediaRecorder$exception: Exception</ID>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,7 @@ public class MessageComposerController(
public fun addSelectedAttachments(attachments: List<Attachment>) {
logger.d { "[addSelectedAttachments] attachments: $attachments" }
val newAttachments = (selectedAttachments.value + attachments).distinctBy {
if (it.name != null) {
if (it.name != null && it.mimeType?.isNotEmpty() == true) {
it.name
} else {
it
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,9 @@ public class StorageHelper {
public fun getCachedFileFromUri(
context: Context,
attachmentMetaData: AttachmentMetaData,
): File {
): File? {
if (attachmentMetaData.file == null && attachmentMetaData.uri == null) {
throw IllegalStateException(
"Unable to create cache file for attachment: $attachmentMetaData. " +
"Either file or URI cannot be null.",
)
return null
}
if (attachmentMetaData.file != null) {
return attachmentMetaData.file!!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public data class AttachmentMetaData(
var mimeType: String? = null,
var title: String? = null,
var file: File? = null,
var extraData: Map<String, Any> = mapOf(),
) {
var size: Long = 0
var isSelected: Boolean = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ internal fun AttachmentMetaData.toAttachment(context: Context): Attachment {
return Attachment(
upload = fileFromUri,
type = type,
name = title ?: fileFromUri.name ?: "",
name = title ?: fileFromUri?.name ?: "",
fileSize = size.toInt(),
mimeType = mimeType,
title = title,
Expand Down

0 comments on commit f651c69

Please sign in to comment.