diff --git a/docusaurus/docs/Android/ui/guides/pick-files-without-permissions.mdx b/docusaurus/docs/Android/ui/guides/pick-files-without-permissions.mdx new file mode 100644 index 00000000000..61464402326 --- /dev/null +++ b/docusaurus/docs/Android/ui/guides/pick-files-without-permissions.mdx @@ -0,0 +1,81 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# System Media Picker + +Sometimes its not desirable for an app to have the `READ_MEDIA_IMAGES` and `READ_MEDIA_VIDEO` permissions. +In this guide, we will explain how to set up the new `useDefaultSystemMediaPicker` parameter in both `ChatTheme` and `ChatUI`, why it is important, and how to remove permissions from the `AndroidManifest.xml` using `tools:node="remove"`. + +### The importance of System Media Picker + +The `useDefaultSystemMediaPicker` parameter allows you to use the system's default media picker instead of the custom media picker provided by the library. This can be beneficial for several reasons: +- **Consistency**: Provides a consistent user experience by using the familiar system media picker. +- **Permissions**: Reduces the need for additional permissions, as the system media picker handles permissions internally. +- **Simplicity**: Simplifies the implementation by leveraging the built-in functionality of the system media picker. + +### Setting Up System Media Picker + +#### In Compose UI + +To enable the system media picker in `ChatTheme`, set the `useDefaultSystemMediaPicker` parameter to `true` when initializing the theme. + +```kotlin +ChatTheme( + useDefaultSystemMediaPicker = true +) { + // Your composable content +} +``` + +#### In XML-based UI + +There are two options for enabling usage of the system media picker in XML-based UI: `XML attributes` and `StyleTransformer`. + +Let's start with the `XML Attributes` option. +You can enable the system media picker by setting the `streamUiMessageComposerAttachmentsPickerSystemPickerEnabled` attribute to `true` in the `MessageComposerView` XML layout. + +```xml + +``` + +You can also enable the system media picker using the `StyleTransformer` as shown below. + +```kotlin +// Set the property directly in the AttachmentsPickerDialogStyle +TransformStyle.attachmentsPickerStyleTransformer = StyleTransformer { defaultStyle -> + defaultStyle.copy( + useDefaultSystemMediaPicker = true, + ) +} + +// Or set the property in the MessageComposerStyle +TransformStyle.messageComposerStyleTransformer = StyleTransformer { defaultStyle -> + defaultStyle.copy( + attachmentsPickerDialogStyle = defaultStyle.attachmentsPickerDialogStyle.copy( + useDefaultSystemMediaPicker = true, + ), + ) +} +``` + +Please be advised that setting `useDefaultSystemMediaPicker` in `MessageComposerStyle` will override the value set in `AttachmentsPickerDialogStyle`. + +### Removing Permissions from your Project + +Let's remove the permissions from the `AndroidManifest.xml`. + +When using the system media picker, you can remove unnecessary permissions from your `AndroidManifest.xml` to streamline your app's permission requests. +Use the `tools:node="remove"` attribute to remove permissions. + +```xml + + + +``` + +By following these steps you can remove unnecessary permissions from your `AndroidManifest.xml`. \ No newline at end of file diff --git a/stream-chat-android-compose/api/stream-chat-android-compose.api b/stream-chat-android-compose/api/stream-chat-android-compose.api index 7bebbf530fa..ca28f80af5d 100644 --- a/stream-chat-android-compose/api/stream-chat-android-compose.api +++ b/stream-chat-android-compose/api/stream-chat-android-compose.api @@ -1520,11 +1520,21 @@ public final class io/getstream/chat/android/compose/ui/messages/attachments/fac public fun isPickerTabEnabled (Lio/getstream/chat/android/models/Channel;)Z } +public final class io/getstream/chat/android/compose/ui/messages/attachments/factory/AttachmentsPickerSystemTabFactory : io/getstream/chat/android/compose/ui/messages/attachments/factory/AttachmentsPickerTabFactory { + public static final field $stable I + public fun (Ljava/util/List;)V + public fun PickerTabContent (Lkotlin/jvm/functions/Function1;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 (Lio/getstream/chat/android/models/Channel;)Z +} + public final class io/getstream/chat/android/compose/ui/messages/attachments/factory/AttachmentsPickerTabFactories { public static final field $stable I public static final field INSTANCE Lio/getstream/chat/android/compose/ui/messages/attachments/factory/AttachmentsPickerTabFactories; public final fun defaultFactories (ZZZZZ)Ljava/util/List; public static synthetic fun defaultFactories$default (Lio/getstream/chat/android/compose/ui/messages/attachments/factory/AttachmentsPickerTabFactories;ZZZZZILjava/lang/Object;)Ljava/util/List; + public final fun defaultFactoriesWithoutStoragePermissions ()Ljava/util/List; } public abstract interface class io/getstream/chat/android/compose/ui/messages/attachments/factory/AttachmentsPickerTabFactory { @@ -1891,12 +1901,13 @@ public final class io/getstream/chat/android/compose/ui/theme/ChatTheme { public final fun getStreamMediaRecorder (Landroidx/compose/runtime/Composer;I)Lio/getstream/sdk/chat/audio/recording/StreamMediaRecorder; public final fun getTimeProvider (Landroidx/compose/runtime/Composer;I)Lio/getstream/chat/android/ui/common/helper/TimeProvider; public final fun getTypography (Landroidx/compose/runtime/Composer;I)Lio/getstream/chat/android/compose/ui/theme/StreamTypography; + public final fun getUseDefaultSystemMediaPicker (Landroidx/compose/runtime/Composer;I)Z public final fun getVideoThumbnailsEnabled (Landroidx/compose/runtime/Composer;I)Z public final fun isComposerLinkPreviewEnabled (Landroidx/compose/runtime/Composer;I)Z } public final class io/getstream/chat/android/compose/ui/theme/ChatThemeKt { - public static final fun ChatTheme (ZZZLio/getstream/chat/android/compose/ui/theme/StreamColors;Lio/getstream/chat/android/compose/ui/theme/StreamDimens;Lio/getstream/chat/android/compose/ui/theme/StreamTypography;Lio/getstream/chat/android/compose/ui/theme/StreamShapes;Landroidx/compose/material/ripple/RippleTheme;Ljava/util/List;Ljava/util/List;Ljava/util/List;Lio/getstream/chat/android/compose/ui/util/ReactionIconFactory;Lio/getstream/chat/android/compose/ui/theme/ReactionOptionsTheme;Lio/getstream/chat/android/compose/ui/util/PollSwitchItemFactory;ZLio/getstream/chat/android/ui/common/helper/DateFormatter;Lio/getstream/chat/android/ui/common/helper/TimeProvider;Lio/getstream/chat/android/ui/common/utils/ChannelNameFormatter;Lio/getstream/chat/android/compose/ui/util/MessagePreviewFormatter;Lio/getstream/chat/android/compose/ui/util/SearchResultNameFormatter;Lio/getstream/chat/android/compose/ui/util/StreamCoilImageLoaderFactory;Lio/getstream/chat/android/compose/ui/util/MessageAlignmentProvider;Lio/getstream/chat/android/compose/ui/theme/MessageOptionsTheme;Lio/getstream/chat/android/ui/common/state/messages/list/MessageOptionsUserReactionAlignment;Ljava/util/List;ZLio/getstream/chat/android/ui/common/images/resizing/StreamCdnImageResizing;ZLio/getstream/chat/android/compose/ui/theme/MessageTheme;Lio/getstream/chat/android/compose/ui/theme/MessageTheme;Lio/getstream/chat/android/compose/ui/theme/MessageDateSeparatorTheme;Lio/getstream/chat/android/compose/ui/theme/MessageUnreadSeparatorTheme;Lio/getstream/chat/android/compose/ui/theme/MessageComposerTheme;Lio/getstream/chat/android/compose/ui/theme/AttachmentPickerTheme;Lio/getstream/chat/android/compose/ui/util/MessageTextFormatter;Lio/getstream/chat/android/compose/ui/util/QuotedMessageTextFormatter;Lio/getstream/sdk/chat/audio/recording/StreamMediaRecorder;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;IIIIII)V + public static final fun ChatTheme (ZZZZLio/getstream/chat/android/compose/ui/theme/StreamColors;Lio/getstream/chat/android/compose/ui/theme/StreamDimens;Lio/getstream/chat/android/compose/ui/theme/StreamTypography;Lio/getstream/chat/android/compose/ui/theme/StreamShapes;Landroidx/compose/material/ripple/RippleTheme;Ljava/util/List;Ljava/util/List;Ljava/util/List;Lio/getstream/chat/android/compose/ui/util/ReactionIconFactory;Lio/getstream/chat/android/compose/ui/theme/ReactionOptionsTheme;Lio/getstream/chat/android/compose/ui/util/PollSwitchItemFactory;ZLio/getstream/chat/android/ui/common/helper/DateFormatter;Lio/getstream/chat/android/ui/common/helper/TimeProvider;Lio/getstream/chat/android/ui/common/utils/ChannelNameFormatter;Lio/getstream/chat/android/compose/ui/util/MessagePreviewFormatter;Lio/getstream/chat/android/compose/ui/util/SearchResultNameFormatter;Lio/getstream/chat/android/compose/ui/util/StreamCoilImageLoaderFactory;Lio/getstream/chat/android/compose/ui/util/MessageAlignmentProvider;Lio/getstream/chat/android/compose/ui/theme/MessageOptionsTheme;Lio/getstream/chat/android/ui/common/state/messages/list/MessageOptionsUserReactionAlignment;Ljava/util/List;ZLio/getstream/chat/android/ui/common/images/resizing/StreamCdnImageResizing;ZLio/getstream/chat/android/compose/ui/theme/MessageTheme;Lio/getstream/chat/android/compose/ui/theme/MessageTheme;Lio/getstream/chat/android/compose/ui/theme/MessageDateSeparatorTheme;Lio/getstream/chat/android/compose/ui/theme/MessageUnreadSeparatorTheme;Lio/getstream/chat/android/compose/ui/theme/MessageComposerTheme;Lio/getstream/chat/android/compose/ui/theme/AttachmentPickerTheme;Lio/getstream/chat/android/compose/ui/util/MessageTextFormatter;Lio/getstream/chat/android/compose/ui/util/QuotedMessageTextFormatter;Lio/getstream/sdk/chat/audio/recording/StreamMediaRecorder;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;IIIIII)V } public final class io/getstream/chat/android/compose/ui/theme/ComponentSize { @@ -2205,8 +2216,8 @@ public final class io/getstream/chat/android/compose/ui/theme/StreamColors$Compa public final class io/getstream/chat/android/compose/ui/theme/StreamDimens { public static final field $stable I public static final field Companion Lio/getstream/chat/android/compose/ui/theme/StreamDimens$Companion; - public synthetic fun (FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFIILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFLkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFIILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFLkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1-D9Ej5fM ()F public final fun component10-D9Ej5fM ()F public final fun component11-D9Ej5fM ()F @@ -2255,12 +2266,13 @@ public final class io/getstream/chat/android/compose/ui/theme/StreamDimens { public final fun component50-D9Ej5fM ()F public final fun component51-D9Ej5fM ()F public final fun component52-D9Ej5fM ()F + public final fun component53-D9Ej5fM ()F public final fun component6-D9Ej5fM ()F public final fun component7-D9Ej5fM ()F public final fun component8-D9Ej5fM ()F public final fun component9-D9Ej5fM ()F - public final fun copy-EfCTRKU (FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)Lio/getstream/chat/android/compose/ui/theme/StreamDimens; - public static synthetic fun copy-EfCTRKU$default (Lio/getstream/chat/android/compose/ui/theme/StreamDimens;FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFIILjava/lang/Object;)Lio/getstream/chat/android/compose/ui/theme/StreamDimens; + public final fun copy-m-3Q45M (FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)Lio/getstream/chat/android/compose/ui/theme/StreamDimens; + public static synthetic fun copy-m-3Q45M$default (Lio/getstream/chat/android/compose/ui/theme/StreamDimens;FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFIILjava/lang/Object;)Lio/getstream/chat/android/compose/ui/theme/StreamDimens; public fun equals (Ljava/lang/Object;)Z public final fun getAttachmentsContentFileUploadWidth-D9Ej5fM ()F public final fun getAttachmentsContentFileWidth-D9Ej5fM ()F @@ -2278,6 +2290,7 @@ public final class io/getstream/chat/android/compose/ui/theme/StreamDimens { public final fun getAttachmentsContentVideoMaxHeight-D9Ej5fM ()F public final fun getAttachmentsContentVideoWidth-D9Ej5fM ()F public final fun getAttachmentsPickerHeight-D9Ej5fM ()F + public final fun getAttachmentsSystemPickerHeight-D9Ej5fM ()F public final fun getChannelAvatarSize-D9Ej5fM ()F public final fun getChannelItemHorizontalPadding-D9Ej5fM ()F public final fun getChannelItemVerticalPadding-D9Ej5fM ()F diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/MessagesScreen.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/MessagesScreen.kt index ed8d1996f04..8863b5bc22b 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/MessagesScreen.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/MessagesScreen.kt @@ -646,7 +646,11 @@ public fun BoxScope.AttachmentsPickerMenu( var isFullScreenContent by rememberSaveable { mutableStateOf(false) } val screenHeight = LocalConfiguration.current.screenHeightDp val pickerHeight by animateDpAsState( - targetValue = if (isFullScreenContent) screenHeight.dp else ChatTheme.dimens.attachmentsPickerHeight, + targetValue = when { + isFullScreenContent -> screenHeight.dp + ChatTheme.useDefaultSystemMediaPicker -> ChatTheme.dimens.attachmentsSystemPickerHeight + else -> ChatTheme.dimens.attachmentsPickerHeight + }, label = "full sized picker animation", ) diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/attachments/factory/AttachmentsPickerSystemTabFactory.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/attachments/factory/AttachmentsPickerSystemTabFactory.kt new file mode 100644 index 00000000000..667d4cb7870 --- /dev/null +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/attachments/factory/AttachmentsPickerSystemTabFactory.kt @@ -0,0 +1,394 @@ +/* + * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE + * + * 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.getstream.chat.android.compose.ui.messages.attachments.factory + +import android.app.Activity +import android.content.Intent +import android.net.Uri +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.Card +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import com.google.accompanist.permissions.ExperimentalPermissionsApi +import io.getstream.chat.android.compose.R +import io.getstream.chat.android.compose.state.messages.attachments.AttachmentPickerItemState +import io.getstream.chat.android.compose.state.messages.attachments.AttachmentsPickerMode +import io.getstream.chat.android.compose.state.messages.attachments.Files +import io.getstream.chat.android.compose.state.messages.attachments.MediaCapture +import io.getstream.chat.android.compose.state.messages.attachments.Poll +import io.getstream.chat.android.compose.ui.theme.ChatTheme +import io.getstream.chat.android.compose.ui.util.StorageHelperWrapper +import io.getstream.chat.android.ui.common.helper.internal.AttachmentFilter +import io.getstream.chat.android.ui.common.helper.internal.StorageHelper +import io.getstream.chat.android.ui.common.state.messages.composer.AttachmentMetaData + +/** + * Holds the information required to add support for "files" tab in the attachment picker. + */ +public class AttachmentsPickerSystemTabFactory(private val otherFactories: List) : + AttachmentsPickerTabFactory { + + /** + * The attachment picker mode that this factory handles. + */ + override val attachmentsPickerMode: AttachmentsPickerMode + get() = Files + + /** + * Emits a file icon for this tab. + * + * @param isEnabled If the tab is enabled. + * @param isSelected If the tab is selected. + */ + @Composable + override fun PickerTabIcon(isEnabled: Boolean, isSelected: Boolean) { + Icon( + painter = painterResource(id = R.drawable.stream_compose_ic_attachments), + contentDescription = stringResource(id = R.string.stream_compose_attachments), + tint = when { + isSelected -> ChatTheme.colors.primaryAccent + isEnabled -> ChatTheme.colors.textLowEmphasis + else -> ChatTheme.colors.disabled + }, + ) + } + + /** + * Emits content that allows users to pick files in this tab. + * + * @param onAttachmentPickerAction A lambda that will be invoked when an action is happened. + * @param attachments The list of attachments to display. + * @param onAttachmentsChanged Handler to set the loaded list of attachments to display. + * @param onAttachmentItemSelected Handler when the item selection state changes. + * @param onAttachmentsSubmitted Handler to submit the selected attachments to the message composer. + */ + @OptIn(ExperimentalPermissionsApi::class) + @Composable + override fun PickerTabContent( + onAttachmentPickerAction: (AttachmentPickerAction) -> Unit, + attachments: List, + onAttachmentsChanged: (List) -> Unit, + onAttachmentItemSelected: (AttachmentPickerItemState) -> Unit, + onAttachmentsSubmitted: (List) -> Unit, + ) { + val context = LocalContext.current + val attachmentFilter = AttachmentFilter() + val storageHelper: StorageHelperWrapper = remember { + StorageHelperWrapper(context, StorageHelper(), attachmentFilter) + } + + val filePickerLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.StartActivityForResult(), + ) { result -> + // Handle the file URI + if (result.resultCode == Activity.RESULT_OK) { + val uri = result.data?.data + uri?.let { + val attachmentMetadata = storageHelper.getAttachmentsMetadataFromUris(listOf(uri)) + onAttachmentsSubmitted(attachmentMetadata) + } + } + } + + val imagePickerLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.PickVisualMedia(), + ) { uri: Uri? -> + // Handle the image URI + uri?.let { + val attachmentMetadata = storageHelper.getAttachmentsMetadataFromUris(listOf(uri)) + onAttachmentsSubmitted(attachmentMetadata) + } + } + + InnerContent( + InnerContentParams( + attachments = attachments, + otherFactories = otherFactories, + ), + InnerContentActions( + onAttachmentItemSelected = onAttachmentItemSelected, + onAttachmentsChanged = onAttachmentsChanged, + onAttachmentsSubmitted = onAttachmentsSubmitted, + onAttachmentPickerAction = onAttachmentPickerAction, + onFilesClick = { + // Start file picker + val filePickerIntent = Intent(Intent.ACTION_GET_CONTENT).apply { + type = "*/*" // General type to include multiple types + putExtra(Intent.EXTRA_MIME_TYPES, attachmentFilter.getSupportedMimeTypes().toTypedArray()) + addCategory(Intent.CATEGORY_OPENABLE) + } + + filePickerLauncher.launch(filePickerIntent) + }, + onImagesClick = { + // Start photo picker + imagePickerLauncher.launch( + PickVisualMediaRequest( + ActivityResultContracts.PickVisualMedia.ImageAndVideo, + ), + ) + }, + ), + ) + } +} + +@Composable +private fun InnerContent(params: InnerContentParams, actions: InnerContentActions) { + val pollsFactory = remember { + params.otherFactories.firstOrNull { it.attachmentsPickerMode == Poll } + } + val mediaCaptureTabFactory = remember { + params.otherFactories.firstOrNull { it.attachmentsPickerMode == MediaCapture } + } + + var pollSelected by remember { + mutableStateOf(false) + } + var mediaSelected by remember { + mutableStateOf(false) + } + + DialogContent( + DialogContentParams( + pollsFactory = pollsFactory, + mediaCaptureTabFactory = mediaCaptureTabFactory, + mediaSelected = mediaSelected, + pollSelected = pollSelected, + attachments = params.attachments, + ), + DialogContentActions( + onAttachmentPickerAction = actions.onAttachmentPickerAction, + onAttachmentsChanged = actions.onAttachmentsChanged, + onAttachmentItemSelected = actions.onAttachmentItemSelected, + onAttachmentsSubmitted = actions.onAttachmentsSubmitted, + onDismissPollDialog = { pollSelected = false }, + ), + ) + + ButtonRow( + onFilesClick = actions.onFilesClick, + onImagesClick = actions.onImagesClick, + onMediaClick = { mediaSelected = !mediaSelected }, + onPollClick = { pollSelected = !pollSelected }, + ) +} + +@Composable +private fun DialogContent(params: DialogContentParams, actions: DialogContentActions) { + if (params.mediaSelected) { + params.mediaCaptureTabFactory?.PickerTabContent( + onAttachmentPickerAction = actions.onAttachmentPickerAction, + attachments = params.attachments, + onAttachmentsChanged = actions.onAttachmentsChanged, + onAttachmentItemSelected = actions.onAttachmentItemSelected, + onAttachmentsSubmitted = actions.onAttachmentsSubmitted, + ) + } + + if (params.pollSelected) { + Dialog( + properties = DialogProperties( + usePlatformDefaultWidth = false, + ), + onDismissRequest = actions.onDismissPollDialog, + ) { + Box( + modifier = Modifier + .background(ChatTheme.colors.appBackground) + .fillMaxWidth() + .fillMaxHeight(), // Ensure the dialog fills the height + ) { + params.pollsFactory?.PickerTabContent( + onAttachmentPickerAction = actions.onAttachmentPickerAction, + attachments = params.attachments, + onAttachmentsChanged = actions.onAttachmentsChanged, + onAttachmentItemSelected = actions.onAttachmentItemSelected, + onAttachmentsSubmitted = actions.onAttachmentsSubmitted, + ) + } + } + } +} + +@Composable +private fun ButtonRow( + onFilesClick: () -> Unit, + onImagesClick: () -> Unit, + onMediaClick: () -> Unit, + onPollClick: () -> Unit, +) { + val buttons = listOf<@Composable () -> Unit>( + { + RoundedIconButton( + onClick = onFilesClick, + iconPainter = painterResource(id = R.drawable.stream_compose_ic_file_picker), + contentDescription = stringResource(id = R.string.stream_compose_files_option), + text = stringResource(id = R.string.stream_compose_files_option), + ) + }, + { + RoundedIconButton( + onClick = onImagesClick, + iconPainter = painterResource(id = R.drawable.stream_compose_ic_image_picker), + contentDescription = stringResource(id = R.string.stream_compose_images_option), + text = stringResource(id = R.string.stream_compose_images_option), + ) + }, + { + RoundedIconButton( + onClick = onMediaClick, + iconPainter = painterResource(id = R.drawable.stream_compose_ic_media_picker), + contentDescription = stringResource(id = R.string.stream_ui_message_composer_capture_media_take_photo), + text = stringResource(id = R.string.stream_ui_message_composer_capture_media_take_photo), + ) + }, + { + RoundedIconButton( + onClick = onPollClick, + iconPainter = painterResource(id = R.drawable.stream_compose_ic_poll), + contentDescription = stringResource(id = R.string.stream_compose_poll_option), + text = stringResource(id = R.string.stream_compose_poll_option), + ) + }, + ) + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically, + ) { + buttons.forEach { button -> + button() + } + } +} + +@Composable +private fun RoundedIconButton( + onClick: () -> Unit, + iconPainter: Painter, + contentDescription: String, + text: String, + iconTint: Color = ChatTheme.colors.overlayDark, +) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + modifier = Modifier.padding(8.dp), + ) { + Card( + shape = CircleShape, + elevation = 4.dp, + backgroundColor = ChatTheme.colors.barsBackground, + modifier = Modifier + .clip(CircleShape) + .size(72.dp) + .padding(12.dp), + ) { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .clip(CircleShape) + .fillMaxSize() + .clickable(onClick = onClick), + ) { + Icon( + painter = iconPainter, + contentDescription = contentDescription, + tint = iconTint, + modifier = Modifier + .size(48.dp) + .padding(12.dp), + ) + } + } + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = text, + style = ChatTheme.typography.footnote, + color = ChatTheme.colors.textLowEmphasis, + modifier = Modifier.padding(top = 4.dp), + ) + } +} + +// Data classes to combine parameters. + +private data class InnerContentParams( + val otherFactories: List, + val attachments: List, +) + +private data class InnerContentActions( + val onAttachmentPickerAction: (AttachmentPickerAction) -> Unit, + val onAttachmentsChanged: (List) -> Unit, + val onAttachmentItemSelected: (AttachmentPickerItemState) -> Unit, + val onAttachmentsSubmitted: (List) -> Unit, + val onFilesClick: () -> Unit, + val onImagesClick: () -> Unit, +) + +private data class DialogContentActions( + val onAttachmentPickerAction: (AttachmentPickerAction) -> Unit, + val onAttachmentsChanged: (List) -> Unit, + val onAttachmentItemSelected: (AttachmentPickerItemState) -> Unit, + val onAttachmentsSubmitted: (List) -> Unit, + val onDismissPollDialog: () -> Unit, +) + +private data class DialogContentParams( + val pollsFactory: AttachmentsPickerTabFactory?, + val mediaCaptureTabFactory: AttachmentsPickerTabFactory?, + val mediaSelected: Boolean, + val pollSelected: Boolean, + val attachments: List, +) diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/attachments/factory/AttachmentsPickerTabFactories.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/attachments/factory/AttachmentsPickerTabFactories.kt index 767aca44b8a..145c5caacb8 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/attachments/factory/AttachmentsPickerTabFactories.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/attachments/factory/AttachmentsPickerTabFactories.kt @@ -22,6 +22,17 @@ package io.getstream.chat.android.compose.ui.messages.attachments.factory */ public object AttachmentsPickerTabFactories { + public fun defaultFactoriesWithoutStoragePermissions(): List { + val otherFactories = defaultFactories( + imagesTabEnabled = false, + filesTabEnabled = false, + takeImageEnabled = true, + recordVideoEnabled = true, + pollEnabled = true, + ) + return listOf(AttachmentsPickerSystemTabFactory(otherFactories)) + } + /** * Builds the default list of attachment picker tab factories. * diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatTheme.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatTheme.kt index f06278bd038..0c8b2cbce90 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatTheme.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatTheme.kt @@ -77,6 +77,9 @@ private val LocalShapes = compositionLocalOf { private val LocalAttachmentFactories = compositionLocalOf> { error("No attachment factories provided! Make sure to wrap all usages of Stream components in a ChatTheme.") } +private val LocalUseDefaultSystemMediaPicker = compositionLocalOf { + error("No attachment factories provided! Make sure to wrap all usages of Stream components in a ChatTheme.") +} private val LocalAttachmentPreviewHandlers = compositionLocalOf> { error("No attachment preview handlers provided! Make sure to wrap all usages of Stream components in a ChatTheme.") } @@ -232,6 +235,7 @@ public fun ChatTheme( isInDarkMode: Boolean = isSystemInDarkTheme(), autoTranslationEnabled: Boolean = false, isComposerLinkPreviewEnabled: Boolean = false, + useDefaultSystemMediaPicker: Boolean = false, colors: StreamColors = if (isInDarkMode) StreamColors.defaultDarkColors() else StreamColors.defaultColors(), dimens: StreamDimens = StreamDimens.defaultDimens(), typography: StreamTypography = StreamTypography.defaultTypography(), @@ -259,7 +263,12 @@ public fun ChatTheme( messageAlignmentProvider: MessageAlignmentProvider = MessageAlignmentProvider.defaultMessageAlignmentProvider(), messageOptionsTheme: MessageOptionsTheme = MessageOptionsTheme.defaultTheme(), messageOptionsUserReactionAlignment: MessageOptionsUserReactionAlignment = MessageOptionsUserReactionAlignment.END, - attachmentsPickerTabFactories: List = AttachmentsPickerTabFactories.defaultFactories(), + attachmentsPickerTabFactories: List = + if (useDefaultSystemMediaPicker) { + AttachmentsPickerTabFactories.defaultFactoriesWithoutStoragePermissions() + } else { + AttachmentsPickerTabFactories.defaultFactories() + }, videoThumbnailsEnabled: Boolean = true, streamCdnImageResizing: StreamCdnImageResizing = StreamCdnImageResizing.defaultStreamCdnImageResizing(), readCountEnabled: Boolean = true, @@ -313,6 +322,7 @@ public fun ChatTheme( LocalTypography provides typography, LocalShapes provides shapes, LocalRippleTheme provides rippleTheme, + LocalUseDefaultSystemMediaPicker provides useDefaultSystemMediaPicker, LocalAttachmentFactories provides attachmentFactories, LocalAttachmentPreviewHandlers provides attachmentPreviewHandlers, LocalQuotedAttachmentFactories provides quotedAttachmentFactories, @@ -393,6 +403,11 @@ public object ChatTheme { @ReadOnlyComposable get() = LocalShapes.current + public val useDefaultSystemMediaPicker: Boolean + @Composable + @ReadOnlyComposable + get() = LocalUseDefaultSystemMediaPicker.current + /** * Retrieves the current list of [AttachmentFactory] at the call site's position in the hierarchy. */ diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/StreamDimens.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/StreamDimens.kt index 6eaf2c1d0ec..7b98369d93d 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/StreamDimens.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/StreamDimens.kt @@ -131,6 +131,7 @@ public data class StreamDimens( public val groupAvatarInitialsXOffset: Dp, public val groupAvatarInitialsYOffset: Dp, public val attachmentsPickerHeight: Dp, + public val attachmentsSystemPickerHeight: Dp, public val attachmentsContentImageMaxHeight: Dp, public val attachmentsContentGiphyMaxWidth: Dp = attachmentsContentGiphyWidth, public val attachmentsContentGiphyMaxHeight: Dp = attachmentsContentGiphyHeight, @@ -192,6 +193,7 @@ public data class StreamDimens( groupAvatarInitialsXOffset = 1.5.dp, groupAvatarInitialsYOffset = 2.5.dp, attachmentsPickerHeight = 350.dp, + attachmentsSystemPickerHeight = 220.dp, attachmentsContentImageMaxHeight = 600.dp, attachmentsContentVideoMaxHeight = 400.dp, attachmentsContentMediaGridSpacing = 2.dp, diff --git a/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/helper/internal/AttachmentFilter.kt b/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/helper/internal/AttachmentFilter.kt index 4c129a44a29..5b1d62f08f1 100644 --- a/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/helper/internal/AttachmentFilter.kt +++ b/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/helper/internal/AttachmentFilter.kt @@ -21,6 +21,7 @@ import io.getstream.chat.android.client.ChatClient import io.getstream.chat.android.core.internal.InternalStreamChatApi import io.getstream.chat.android.models.AttachmentType import io.getstream.chat.android.ui.common.state.messages.composer.AttachmentMetaData +import io.getstream.log.taggedLogger /** * A filter that is used to filter out attachments that will not be accepted by the backend. @@ -34,6 +35,8 @@ import io.getstream.chat.android.ui.common.state.messages.composer.AttachmentMet public class AttachmentFilter( private val chatClient: ChatClient = ChatClient.instance(), ) { + private val logger by taggedLogger("AttachmentFilter") + /** * Filters out attachments that can be uploaded to the backend according to files * and images upload configurations. @@ -67,6 +70,29 @@ public class AttachmentFilter( } } + /** + * Returns the list of supported MIME types by the server according to the upload config values. + */ + public fun getSupportedMimeTypes(): List { + val default = listOf("*/*") // All files + val fileUploadConfig = chatClient.getAppSettings().app.fileUploadConfig + val imageUploadConfig = chatClient.getAppSettings().app.imageUploadConfig + + // Allowed + val allowedFileMimeTypes = fileUploadConfig.allowedMimeTypes.toTypedArray() + val allowedImageMimeTypes = imageUploadConfig.allowedMimeTypes.toTypedArray() + // Blocked + val blockedFileMimeTypes = fileUploadConfig.blockedMimeTypes.toTypedArray() + val blockedImageMimeTypes = imageUploadConfig.blockedMimeTypes.toTypedArray() + + // Combined + val allowed = allowedFileMimeTypes + allowedImageMimeTypes + val blocked = blockedFileMimeTypes + blockedImageMimeTypes + val result = allowed.filterNot { it in blocked } + logger.d { "Supported MIME types: $result" } + return result.ifEmpty { default } + } + /** * Checks if the attachment is allowed to be uploaded to the server according * to the upload config values. diff --git a/stream-chat-android-ui-common/src/main/res/values/strings.xml b/stream-chat-android-ui-common/src/main/res/values/strings.xml index ba755b851e0..6defa44e131 100644 --- a/stream-chat-android-ui-common/src/main/res/values/strings.xml +++ b/stream-chat-android-ui-common/src/main/res/values/strings.xml @@ -33,6 +33,12 @@ Enable permissions on App Settings Settings + + Files + Media + Capture + Poll + The load failed due to an unknown error. The load failed due to the invalid url. diff --git a/stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/helper/AttachmentFilterTest.kt b/stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/helper/AttachmentFilterTest.kt index 9d55bde0d7f..6f5e37b5ea3 100644 --- a/stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/helper/AttachmentFilterTest.kt +++ b/stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/helper/AttachmentFilterTest.kt @@ -14,14 +14,18 @@ * limitations under the License. */ +@file:OptIn(ExperimentalStreamChatApi::class) + package io.getstream.chat.android.ui.common.helper import io.getstream.chat.android.client.ChatClient +import io.getstream.chat.android.core.ExperimentalStreamChatApi import io.getstream.chat.android.models.FileUploadConfig import io.getstream.chat.android.positiveRandomLong import io.getstream.chat.android.ui.common.helper.internal.AttachmentFilter import io.getstream.chat.android.ui.common.state.messages.composer.AttachmentMetaData import org.amshove.kluent.`should be equal to` +import org.junit.Test import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource @@ -34,6 +38,7 @@ internal class AttachmentFilterTest { private val chatClient: ChatClient = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS) + @OptIn(ExperimentalStreamChatApi::class) @ParameterizedTest @MethodSource("attachmentFilterArguments") fun `Given file and image upload configs When filtering attachments Should return only valid attachments`( @@ -48,6 +53,27 @@ internal class AttachmentFilterTest { filteredAttachments.size `should be equal to` attachmentFilterTestData.expectedAttachmentCount } + @Test + fun `Given upload configs When getting supported MIME types Should return correct MIME types`() { + val fileUploadConfig = fileUploadConfig( + allowedMimeTypes = listOf("application/pdf", "text/plain"), + blockedMimeTypes = listOf("application/zip"), + ) + val imageUploadConfig = fileUploadConfig( + allowedMimeTypes = listOf("image/jpeg", "image/png"), + blockedMimeTypes = listOf("image/gif"), + ) + + whenever(chatClient.getAppSettings().app.fileUploadConfig) doReturn fileUploadConfig + whenever(chatClient.getAppSettings().app.imageUploadConfig) doReturn imageUploadConfig + + val attachmentFilter = AttachmentFilter(chatClient) + + val supportedMimeTypes = attachmentFilter.getSupportedMimeTypes() + + supportedMimeTypes `should be equal to` listOf("application/pdf", "text/plain", "image/jpeg", "image/png") + } + internal data class AttachmentFilterTestData( val fileUploadConfig: FileUploadConfig, val imageUploadConfig: FileUploadConfig, diff --git a/stream-chat-android-ui-components/api/stream-chat-android-ui-components.api b/stream-chat-android-ui-components/api/stream-chat-android-ui-components.api index b61a803307d..5f88a08f7c4 100644 --- a/stream-chat-android-ui-components/api/stream-chat-android-ui-components.api +++ b/stream-chat-android-ui-components/api/stream-chat-android-ui-components.api @@ -1299,45 +1299,47 @@ public abstract interface class io/getstream/chat/android/ui/feature/messages/co } public final class io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/AttachmentsPickerDialogStyle : io/getstream/chat/android/ui/helper/ViewStyle { - public fun (ILio/getstream/chat/android/ui/font/TextStyle;Landroid/graphics/drawable/Drawable;Landroid/content/res/ColorStateList;ZLandroid/graphics/drawable/Drawable;Ljava/lang/String;Landroid/graphics/drawable/Drawable;ZLio/getstream/chat/android/ui/font/TextStyle;ZLandroid/graphics/drawable/Drawable;Ljava/lang/Integer;Ljava/lang/String;Lio/getstream/chat/android/ui/font/TextStyle;ZLandroid/graphics/drawable/Drawable;Ljava/lang/String;Landroid/graphics/drawable/Drawable;Ljava/lang/String;Lio/getstream/chat/android/ui/font/TextStyle;Landroid/graphics/drawable/Drawable;Ljava/lang/String;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;IZLandroid/graphics/drawable/Drawable;ZLandroid/graphics/drawable/Drawable;Ljava/lang/String;Landroid/graphics/drawable/Drawable;Lio/getstream/chat/android/ui/feature/messages/composer/attachment/picker/PickerMediaMode;)V - public final fun component1 ()I - public final fun component10 ()Lio/getstream/chat/android/ui/font/TextStyle; - public final fun component11 ()Z - public final fun component12 ()Landroid/graphics/drawable/Drawable; - public final fun component13 ()Ljava/lang/Integer; - public final fun component14 ()Ljava/lang/String; - public final fun component15 ()Lio/getstream/chat/android/ui/font/TextStyle; - public final fun component16 ()Z - public final fun component17 ()Landroid/graphics/drawable/Drawable; - public final fun component18 ()Ljava/lang/String; - public final fun component19 ()Landroid/graphics/drawable/Drawable; - public final fun component2 ()Lio/getstream/chat/android/ui/font/TextStyle; - public final fun component20 ()Ljava/lang/String; - public final fun component21 ()Lio/getstream/chat/android/ui/font/TextStyle; - public final fun component22 ()Landroid/graphics/drawable/Drawable; - public final fun component23 ()Ljava/lang/String; - public final fun component24 ()Lio/getstream/chat/android/ui/font/TextStyle; + public fun (ZILio/getstream/chat/android/ui/font/TextStyle;Landroid/graphics/drawable/Drawable;Landroid/content/res/ColorStateList;ZLandroid/graphics/drawable/Drawable;Ljava/lang/String;Landroid/graphics/drawable/Drawable;ZLio/getstream/chat/android/ui/font/TextStyle;ZLandroid/graphics/drawable/Drawable;Ljava/lang/Integer;Ljava/lang/String;Lio/getstream/chat/android/ui/font/TextStyle;ZLandroid/graphics/drawable/Drawable;Ljava/lang/String;Landroid/graphics/drawable/Drawable;Ljava/lang/String;Lio/getstream/chat/android/ui/font/TextStyle;Landroid/graphics/drawable/Drawable;Ljava/lang/String;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;IZLandroid/graphics/drawable/Drawable;ZLandroid/graphics/drawable/Drawable;Ljava/lang/String;Landroid/graphics/drawable/Drawable;Lio/getstream/chat/android/ui/feature/messages/composer/attachment/picker/PickerMediaMode;)V + public synthetic fun (ZILio/getstream/chat/android/ui/font/TextStyle;Landroid/graphics/drawable/Drawable;Landroid/content/res/ColorStateList;ZLandroid/graphics/drawable/Drawable;Ljava/lang/String;Landroid/graphics/drawable/Drawable;ZLio/getstream/chat/android/ui/font/TextStyle;ZLandroid/graphics/drawable/Drawable;Ljava/lang/Integer;Ljava/lang/String;Lio/getstream/chat/android/ui/font/TextStyle;ZLandroid/graphics/drawable/Drawable;Ljava/lang/String;Landroid/graphics/drawable/Drawable;Ljava/lang/String;Lio/getstream/chat/android/ui/font/TextStyle;Landroid/graphics/drawable/Drawable;Ljava/lang/String;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;IZLandroid/graphics/drawable/Drawable;ZLandroid/graphics/drawable/Drawable;Ljava/lang/String;Landroid/graphics/drawable/Drawable;Lio/getstream/chat/android/ui/feature/messages/composer/attachment/picker/PickerMediaMode;IILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Z + public final fun component10 ()Z + public final fun component11 ()Lio/getstream/chat/android/ui/font/TextStyle; + public final fun component12 ()Z + public final fun component13 ()Landroid/graphics/drawable/Drawable; + public final fun component14 ()Ljava/lang/Integer; + public final fun component15 ()Ljava/lang/String; + public final fun component16 ()Lio/getstream/chat/android/ui/font/TextStyle; + public final fun component17 ()Z + public final fun component18 ()Landroid/graphics/drawable/Drawable; + public final fun component19 ()Ljava/lang/String; + public final fun component2 ()I + public final fun component20 ()Landroid/graphics/drawable/Drawable; + public final fun component21 ()Ljava/lang/String; + public final fun component22 ()Lio/getstream/chat/android/ui/font/TextStyle; + public final fun component23 ()Landroid/graphics/drawable/Drawable; + public final fun component24 ()Ljava/lang/String; public final fun component25 ()Lio/getstream/chat/android/ui/font/TextStyle; public final fun component26 ()Lio/getstream/chat/android/ui/font/TextStyle; - public final fun component27 ()Landroid/graphics/drawable/Drawable; + public final fun component27 ()Lio/getstream/chat/android/ui/font/TextStyle; public final fun component28 ()Landroid/graphics/drawable/Drawable; - public final fun component29 ()I - public final fun component3 ()Landroid/graphics/drawable/Drawable; - public final fun component30 ()Z - public final fun component31 ()Landroid/graphics/drawable/Drawable; - public final fun component32 ()Z - public final fun component33 ()Landroid/graphics/drawable/Drawable; - public final fun component34 ()Ljava/lang/String; - public final fun component35 ()Landroid/graphics/drawable/Drawable; - public final fun component36 ()Lio/getstream/chat/android/ui/feature/messages/composer/attachment/picker/PickerMediaMode; - public final fun component4 ()Landroid/content/res/ColorStateList; - public final fun component5 ()Z - public final fun component6 ()Landroid/graphics/drawable/Drawable; - public final fun component7 ()Ljava/lang/String; - public final fun component8 ()Landroid/graphics/drawable/Drawable; - public final fun component9 ()Z - public final fun copy (ILio/getstream/chat/android/ui/font/TextStyle;Landroid/graphics/drawable/Drawable;Landroid/content/res/ColorStateList;ZLandroid/graphics/drawable/Drawable;Ljava/lang/String;Landroid/graphics/drawable/Drawable;ZLio/getstream/chat/android/ui/font/TextStyle;ZLandroid/graphics/drawable/Drawable;Ljava/lang/Integer;Ljava/lang/String;Lio/getstream/chat/android/ui/font/TextStyle;ZLandroid/graphics/drawable/Drawable;Ljava/lang/String;Landroid/graphics/drawable/Drawable;Ljava/lang/String;Lio/getstream/chat/android/ui/font/TextStyle;Landroid/graphics/drawable/Drawable;Ljava/lang/String;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;IZLandroid/graphics/drawable/Drawable;ZLandroid/graphics/drawable/Drawable;Ljava/lang/String;Landroid/graphics/drawable/Drawable;Lio/getstream/chat/android/ui/feature/messages/composer/attachment/picker/PickerMediaMode;)Lio/getstream/chat/android/ui/feature/messages/composer/attachment/picker/AttachmentsPickerDialogStyle; - public static synthetic fun copy$default (Lio/getstream/chat/android/ui/feature/messages/composer/attachment/picker/AttachmentsPickerDialogStyle;ILio/getstream/chat/android/ui/font/TextStyle;Landroid/graphics/drawable/Drawable;Landroid/content/res/ColorStateList;ZLandroid/graphics/drawable/Drawable;Ljava/lang/String;Landroid/graphics/drawable/Drawable;ZLio/getstream/chat/android/ui/font/TextStyle;ZLandroid/graphics/drawable/Drawable;Ljava/lang/Integer;Ljava/lang/String;Lio/getstream/chat/android/ui/font/TextStyle;ZLandroid/graphics/drawable/Drawable;Ljava/lang/String;Landroid/graphics/drawable/Drawable;Ljava/lang/String;Lio/getstream/chat/android/ui/font/TextStyle;Landroid/graphics/drawable/Drawable;Ljava/lang/String;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;IZLandroid/graphics/drawable/Drawable;ZLandroid/graphics/drawable/Drawable;Ljava/lang/String;Landroid/graphics/drawable/Drawable;Lio/getstream/chat/android/ui/feature/messages/composer/attachment/picker/PickerMediaMode;IILjava/lang/Object;)Lio/getstream/chat/android/ui/feature/messages/composer/attachment/picker/AttachmentsPickerDialogStyle; + public final fun component29 ()Landroid/graphics/drawable/Drawable; + public final fun component3 ()Lio/getstream/chat/android/ui/font/TextStyle; + public final fun component30 ()I + public final fun component31 ()Z + public final fun component32 ()Landroid/graphics/drawable/Drawable; + public final fun component33 ()Z + public final fun component34 ()Landroid/graphics/drawable/Drawable; + public final fun component35 ()Ljava/lang/String; + public final fun component36 ()Landroid/graphics/drawable/Drawable; + public final fun component37 ()Lio/getstream/chat/android/ui/feature/messages/composer/attachment/picker/PickerMediaMode; + public final fun component4 ()Landroid/graphics/drawable/Drawable; + public final fun component5 ()Landroid/content/res/ColorStateList; + public final fun component6 ()Z + public final fun component7 ()Landroid/graphics/drawable/Drawable; + public final fun component8 ()Ljava/lang/String; + public final fun component9 ()Landroid/graphics/drawable/Drawable; + public final fun copy (ZILio/getstream/chat/android/ui/font/TextStyle;Landroid/graphics/drawable/Drawable;Landroid/content/res/ColorStateList;ZLandroid/graphics/drawable/Drawable;Ljava/lang/String;Landroid/graphics/drawable/Drawable;ZLio/getstream/chat/android/ui/font/TextStyle;ZLandroid/graphics/drawable/Drawable;Ljava/lang/Integer;Ljava/lang/String;Lio/getstream/chat/android/ui/font/TextStyle;ZLandroid/graphics/drawable/Drawable;Ljava/lang/String;Landroid/graphics/drawable/Drawable;Ljava/lang/String;Lio/getstream/chat/android/ui/font/TextStyle;Landroid/graphics/drawable/Drawable;Ljava/lang/String;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;IZLandroid/graphics/drawable/Drawable;ZLandroid/graphics/drawable/Drawable;Ljava/lang/String;Landroid/graphics/drawable/Drawable;Lio/getstream/chat/android/ui/feature/messages/composer/attachment/picker/PickerMediaMode;)Lio/getstream/chat/android/ui/feature/messages/composer/attachment/picker/AttachmentsPickerDialogStyle; + public static synthetic fun copy$default (Lio/getstream/chat/android/ui/feature/messages/composer/attachment/picker/AttachmentsPickerDialogStyle;ZILio/getstream/chat/android/ui/font/TextStyle;Landroid/graphics/drawable/Drawable;Landroid/content/res/ColorStateList;ZLandroid/graphics/drawable/Drawable;Ljava/lang/String;Landroid/graphics/drawable/Drawable;ZLio/getstream/chat/android/ui/font/TextStyle;ZLandroid/graphics/drawable/Drawable;Ljava/lang/Integer;Ljava/lang/String;Lio/getstream/chat/android/ui/font/TextStyle;ZLandroid/graphics/drawable/Drawable;Ljava/lang/String;Landroid/graphics/drawable/Drawable;Ljava/lang/String;Lio/getstream/chat/android/ui/font/TextStyle;Landroid/graphics/drawable/Drawable;Ljava/lang/String;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;IZLandroid/graphics/drawable/Drawable;ZLandroid/graphics/drawable/Drawable;Ljava/lang/String;Landroid/graphics/drawable/Drawable;Lio/getstream/chat/android/ui/feature/messages/composer/attachment/picker/PickerMediaMode;IILjava/lang/Object;)Lio/getstream/chat/android/ui/feature/messages/composer/attachment/picker/AttachmentsPickerDialogStyle; public fun equals (Ljava/lang/Object;)Z public final fun getAllowAccessButtonTextStyle ()Lio/getstream/chat/android/ui/font/TextStyle; public final fun getAllowAccessToCameraButtonText ()Ljava/lang/String; @@ -1370,6 +1372,7 @@ public final class io/getstream/chat/android/ui/feature/messages/composer/attach public final fun getRecentFilesText ()Ljava/lang/String; public final fun getRecentFilesTextStyle ()Lio/getstream/chat/android/ui/font/TextStyle; public final fun getSubmitAttachmentsButtonIconDrawable ()Landroid/graphics/drawable/Drawable; + public final fun getUseDefaultSystemMediaPicker ()Z public final fun getVideoIconDrawable ()Landroid/graphics/drawable/Drawable; public final fun getVideoIconDrawableTint ()Ljava/lang/Integer; public final fun getVideoIconVisible ()Z @@ -1392,6 +1395,7 @@ public final class io/getstream/chat/android/ui/feature/messages/composer/attach public final class io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/factory/AttachmentsPickerTabFactories { public static final field INSTANCE Lio/getstream/chat/android/ui/feature/messages/composer/attachment/picker/factory/AttachmentsPickerTabFactories; public final fun defaultFactories (ZZZZ)Ljava/util/List; + public final fun defaultFactoriesWithoutPermissions (ZZZZ)Ljava/util/List; } public abstract interface class io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/factory/AttachmentsPickerTabFactory { @@ -1429,6 +1433,12 @@ public final class io/getstream/chat/android/ui/feature/messages/composer/attach public fun createTabIcon (Lio/getstream/chat/android/ui/feature/messages/composer/attachment/picker/AttachmentsPickerDialogStyle;)Landroid/graphics/drawable/Drawable; } +public final class io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/factory/system/AttachmentsPickerSystemTabFactory : io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/factory/AttachmentsPickerTabFactory { + public fun (ZZZZ)V + public fun createTabFragment (Lio/getstream/chat/android/ui/feature/messages/composer/attachment/picker/AttachmentsPickerDialogStyle;Lio/getstream/chat/android/ui/feature/messages/composer/attachment/picker/factory/AttachmentsPickerTabListener;)Landroidx/fragment/app/Fragment; + public fun createTabIcon (Lio/getstream/chat/android/ui/feature/messages/composer/attachment/picker/AttachmentsPickerDialogStyle;)Landroid/graphics/drawable/Drawable; +} + public final class io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/CreatePollDialogFragment : androidx/appcompat/app/AppCompatDialogFragment { public static final field Companion Lio/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/CreatePollDialogFragment$Companion; public static final field TAG Ljava/lang/String; diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/MessageComposerViewStyle.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/MessageComposerViewStyle.kt index f653d52a32a..0f991f788df 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/MessageComposerViewStyle.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/MessageComposerViewStyle.kt @@ -1092,7 +1092,10 @@ public data class MessageComposerViewStyle( } @Suppress("MaxLineLength", "LongMethod", "ComplexMethod") - private fun createAttachmentPickerDialogStyle(context: Context, a: TypedArray): AttachmentsPickerDialogStyle { + private fun createAttachmentPickerDialogStyle( + context: Context, + a: TypedArray, + ): AttachmentsPickerDialogStyle { val attachmentsPickerBackgroundColor = a.getColor( R.styleable.MessageComposerView_streamUiMessageComposerAttachmentsPickerBackgroundColor, context.getColorCompat(R.color.stream_ui_white_smoke), @@ -1360,7 +1363,13 @@ public data class MessageComposerViewStyle( PickerMediaMode.PHOTO_AND_VIDEO, ) + val useDefaultSystemMediaPicker = a.getBoolean( + R.styleable.MessageComposerView_streamUiMessageComposerAttachmentsPickerSystemPickerEnabled, + false, + ) + return AttachmentsPickerDialogStyle( + useDefaultSystemMediaPicker = useDefaultSystemMediaPicker, attachmentsPickerBackgroundColor = attachmentsPickerBackgroundColor, allowAccessButtonTextStyle = allowAccessButtonTextStyle, submitAttachmentsButtonIconDrawable = submitAttachmentsButtonIconDrawable, diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/AttachmentsPickerDialogFragment.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/AttachmentsPickerDialogFragment.kt index 16e4242d18b..ef45f4c7d42 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/AttachmentsPickerDialogFragment.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/AttachmentsPickerDialogFragment.kt @@ -22,8 +22,11 @@ import android.view.View import android.view.ViewGroup import android.widget.CheckedTextView import android.widget.FrameLayout +import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.descendants +import androidx.viewpager2.widget.ViewPager2 import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.google.android.material.card.MaterialCardView import io.getstream.chat.android.models.Attachment import io.getstream.chat.android.models.PollConfig import io.getstream.chat.android.ui.R @@ -171,6 +174,21 @@ public class AttachmentsPickerDialogFragment : BottomSheetDialogFragment() { attachmentsPickerTabListener = attachmentsPickerTabListener, ) binding.attachmentPager.isUserInputEnabled = false + setupPagerContainerHeight() + } + + private fun setupPagerContainerHeight() { + val pagerContainer = binding.root.findViewById(R.id.pagerContainer) + val viewPager = binding.root.findViewById(R.id.attachmentPager) + + viewPager.adapter?.let { adapter -> + if (adapter.itemCount == 1) { + val layoutParams = pagerContainer.layoutParams + layoutParams.height = ConstraintLayout.LayoutParams.WRAP_CONTENT + pagerContainer.layoutParams = layoutParams + } + } + pagerContainer.requestLayout() } override fun onDestroyView() { @@ -262,13 +280,23 @@ public class AttachmentsPickerDialogFragment : BottomSheetDialogFragment() { */ public fun newInstance( style: AttachmentsPickerDialogStyle, - attachmentsPickerTabFactories: List = AttachmentsPickerTabFactories - .defaultFactories( - mediaAttachmentsTabEnabled = style.mediaAttachmentsTabEnabled, - fileAttachmentsTabEnabled = style.fileAttachmentsTabEnabled, - cameraAttachmentsTabEnabled = style.cameraAttachmentsTabEnabled, - pollAttachmentsTabEnabled = style.pollAttachmentsTabEnabled, - ), + attachmentsPickerTabFactories: List = + if (style.useDefaultSystemMediaPicker) { + AttachmentsPickerTabFactories + .defaultFactoriesWithoutPermissions( + mediaAttachmentsTabEnabled = style.mediaAttachmentsTabEnabled, + fileAttachmentsTabEnabled = style.fileAttachmentsTabEnabled, + cameraAttachmentsTabEnabled = style.cameraAttachmentsTabEnabled, + pollAttachmentsTabEnabled = style.pollAttachmentsTabEnabled, + ) + } else { + AttachmentsPickerTabFactories.defaultFactories( + mediaAttachmentsTabEnabled = style.mediaAttachmentsTabEnabled, + fileAttachmentsTabEnabled = style.fileAttachmentsTabEnabled, + cameraAttachmentsTabEnabled = style.cameraAttachmentsTabEnabled, + pollAttachmentsTabEnabled = style.pollAttachmentsTabEnabled, + ) + }, ): AttachmentsPickerDialogFragment { return AttachmentsPickerDialogFragment().apply { setStyle(style) diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/AttachmentsPickerDialogStyle.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/AttachmentsPickerDialogStyle.kt index 18d81b87da1..620f4d5765a 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/AttachmentsPickerDialogStyle.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/AttachmentsPickerDialogStyle.kt @@ -25,6 +25,7 @@ import io.getstream.chat.android.ui.helper.ViewStyle /** * Style for [AttachmentsPickerDialogFragment]. * + * @param useDefaultSystemMediaPicker If the system pickers should be used that does not require the `READ_MEDIA` permission. * @param attachmentsPickerBackgroundColor The background color of the picker. * @param allowAccessButtonTextStyle The text style used for all the buttons used to request required permissions. * @param submitAttachmentsButtonIconDrawable The icon for the submit selected attachments button. @@ -61,6 +62,7 @@ import io.getstream.chat.android.ui.helper.ViewStyle * @param pickerMediaMode define which media type will be allowed. */ public data class AttachmentsPickerDialogStyle( + val useDefaultSystemMediaPicker: Boolean = false, @ColorInt val attachmentsPickerBackgroundColor: Int, val allowAccessButtonTextStyle: TextStyle, // Dialog header section diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/factory/AttachmentsPickerTabFactories.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/factory/AttachmentsPickerTabFactories.kt index 0dd865429ca..f7b73f4282b 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/factory/AttachmentsPickerTabFactories.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/factory/AttachmentsPickerTabFactories.kt @@ -20,12 +20,29 @@ import io.getstream.chat.android.ui.feature.messages.composer.attachment.picker. import io.getstream.chat.android.ui.feature.messages.composer.attachment.picker.factory.file.AttachmentsPickerFileTabFactory import io.getstream.chat.android.ui.feature.messages.composer.attachment.picker.factory.media.AttachmentsPickerMediaTabFactory import io.getstream.chat.android.ui.feature.messages.composer.attachment.picker.factory.poll.AttachmentsPickerPollTabFactory +import io.getstream.chat.android.ui.feature.messages.composer.attachment.picker.factory.system.AttachmentsPickerSystemTabFactory /** * Provides the default list of tab factories for the attachment picker. */ public object AttachmentsPickerTabFactories { + public fun defaultFactoriesWithoutPermissions( + mediaAttachmentsTabEnabled: Boolean, + fileAttachmentsTabEnabled: Boolean, + cameraAttachmentsTabEnabled: Boolean, + pollAttachmentsTabEnabled: Boolean, + ): List { + return listOf( + AttachmentsPickerSystemTabFactory( + mediaAttachmentsTabEnabled, + fileAttachmentsTabEnabled, + cameraAttachmentsTabEnabled, + pollAttachmentsTabEnabled, + ), + ) + } + /** * Creates a list of factories for the tabs that will be displayed in the attachment picker. * diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/factory/camera/internal/CameraAttachmentFragment.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/factory/camera/internal/CameraAttachmentFragment.kt index e43cf71472d..02dca235120 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/factory/camera/internal/CameraAttachmentFragment.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/factory/camera/internal/CameraAttachmentFragment.kt @@ -145,7 +145,7 @@ internal class CameraAttachmentFragment : Fragment() { _binding = null } - private object LauncherRequestsKeys { + internal object LauncherRequestsKeys { const val CAPTURE_MEDIA = "capture_media_request_key" } @@ -165,7 +165,7 @@ internal class CameraAttachmentFragment : Fragment() { /** * Map [PickerMediaMode] into [CaptureMediaContract.Mode] */ - private val PickerMediaMode.mode: CaptureMediaContract.Mode + internal val PickerMediaMode.mode: CaptureMediaContract.Mode get() = when (this) { PickerMediaMode.PHOTO -> CaptureMediaContract.Mode.PHOTO PickerMediaMode.VIDEO -> CaptureMediaContract.Mode.VIDEO diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/factory/system/AttachmentsPickerSystemTabFactory.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/factory/system/AttachmentsPickerSystemTabFactory.kt new file mode 100644 index 00000000000..912084e0877 --- /dev/null +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/factory/system/AttachmentsPickerSystemTabFactory.kt @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE + * + * 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.getstream.chat.android.ui.feature.messages.composer.attachment.picker.factory.system + +import android.graphics.drawable.Drawable +import androidx.fragment.app.Fragment +import io.getstream.chat.android.ui.feature.messages.composer.attachment.picker.AttachmentsPickerDialogStyle +import io.getstream.chat.android.ui.feature.messages.composer.attachment.picker.factory.AttachmentsPickerTabFactory +import io.getstream.chat.android.ui.feature.messages.composer.attachment.picker.factory.AttachmentsPickerTabListener +import io.getstream.chat.android.ui.feature.messages.composer.attachment.picker.factory.system.internal.AttachmentsPickerSystemConfig +import io.getstream.chat.android.ui.feature.messages.composer.attachment.picker.factory.system.internal.AttachmentsPickerSystemFragment + +/** + * An attachment factory that creates a tab with the few icons and uses system pickers instead. + */ +public class AttachmentsPickerSystemTabFactory( + private val mediaAttachmentsTabEnabled: Boolean, + private val fileAttachmentsTabEnabled: Boolean, + private val cameraAttachmentsTabEnabled: Boolean, + private val pollAttachmentsTabEnabled: Boolean, +) : AttachmentsPickerTabFactory { + + /** + * Create the tab icon. + * @param style The style of the dialog. + */ + override fun createTabIcon(style: AttachmentsPickerDialogStyle): Drawable { + return style.submitAttachmentsButtonIconDrawable + } + + /** + * Create the tab fragment. + * @param style The style of the dialog. + * @param attachmentsPickerTabListener The listener for the tab. + */ + override fun createTabFragment( + style: AttachmentsPickerDialogStyle, + attachmentsPickerTabListener: AttachmentsPickerTabListener, + ): Fragment { + return AttachmentsPickerSystemFragment.newInstance( + style, + attachmentsPickerTabListener, + AttachmentsPickerSystemConfig( + mediaAttachmentsTabEnabled, + fileAttachmentsTabEnabled, + cameraAttachmentsTabEnabled, + pollAttachmentsTabEnabled, + ), + ) + } +} diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/factory/system/internal/AttachmentsPickerSystemFragment.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/factory/system/internal/AttachmentsPickerSystemFragment.kt new file mode 100644 index 00000000000..e397a202554 --- /dev/null +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/factory/system/internal/AttachmentsPickerSystemFragment.kt @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE + * + * 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.getstream.chat.android.ui.feature.messages.composer.attachment.picker.factory.system.internal + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts +import androidx.fragment.app.Fragment +import io.getstream.chat.android.models.PollConfig +import io.getstream.chat.android.ui.common.contract.internal.CaptureMediaContract +import io.getstream.chat.android.ui.common.helper.internal.AttachmentFilter +import io.getstream.chat.android.ui.common.helper.internal.StorageHelper +import io.getstream.chat.android.ui.common.state.messages.composer.AttachmentMetaData +import io.getstream.chat.android.ui.databinding.StreamUiFragmentAttachmentSystemPickerBinding +import io.getstream.chat.android.ui.feature.messages.composer.attachment.picker.AttachmentsPickerDialogStyle +import io.getstream.chat.android.ui.feature.messages.composer.attachment.picker.factory.AttachmentsPickerTabListener +import io.getstream.chat.android.ui.feature.messages.composer.attachment.picker.factory.camera.internal.CameraAttachmentFragment.Companion.mode +import io.getstream.chat.android.ui.feature.messages.composer.attachment.picker.factory.camera.internal.CameraAttachmentFragment.LauncherRequestsKeys +import io.getstream.chat.android.ui.feature.messages.composer.attachment.picker.poll.CreatePollDialogFragment +import io.getstream.chat.android.ui.utils.extensions.getFragmentManager +import io.getstream.chat.android.ui.utils.extensions.streamThemeInflater +import java.io.File + +internal class AttachmentsPickerSystemFragment : Fragment() { + + private lateinit var config: AttachmentsPickerSystemConfig + private var _binding: StreamUiFragmentAttachmentSystemPickerBinding? = null + private val binding get() = _binding!! + + private lateinit var style: AttachmentsPickerDialogStyle + + /** + * A listener invoked when attachments are selected in the attachment tab. + */ + private var attachmentsPickerTabListener: AttachmentsPickerTabListener? = null + private val storageHelper = StorageHelper() + private val attachmentFilter = AttachmentFilter() + private val filePickerLauncher = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + val uri = result.data?.data + if (uri != null) { + val attachmentMetaData = storageHelper.getAttachmentsFromUriList(requireContext(), listOf(uri)) + attachmentsPickerTabListener?.onSelectedAttachmentsChanged(attachmentMetaData) + } + attachmentsPickerTabListener?.onSelectedAttachmentsSubmitted() + } + + private val imagePickerLauncher = + registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri: Uri? -> + if (uri != null) { + val attachmentMetaData = storageHelper.getAttachmentsFromUriList(requireContext(), listOf(uri)) + attachmentsPickerTabListener?.onSelectedAttachmentsChanged(attachmentMetaData) + } + attachmentsPickerTabListener?.onSelectedAttachmentsSubmitted() + } + + private var captureMedia: ActivityResultLauncher? = null + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + _binding = + StreamUiFragmentAttachmentSystemPickerBinding.inflate( + requireContext().streamThemeInflater, + container, + false, + ) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupViews() + } + + private fun setupViews() { + // Adjust visibility of the tabs based on the enabled flags + if (!config.fileAttachmentsTabEnabled) { + binding.buttonFiles.visibility = View.GONE + binding.textFiles.visibility = View.GONE + } + + if (!config.mediaAttachmentsTabEnabled) { + binding.buttonMedia.visibility = View.GONE + binding.textMedia.visibility = View.GONE + } + + if (!config.cameraAttachmentsTabEnabled) { + binding.buttonCapture.visibility = View.GONE + binding.textCapture.visibility = View.GONE + } + if (!config.pollAttachmentsTabEnabled) { + binding.buttonPolls.visibility = View.GONE + binding.textPoll.visibility = View.GONE + } + + // Setup listeners and actions + binding.buttonFiles.setOnClickListener { + val supportedMimeTypes = attachmentFilter.getSupportedMimeTypes() + val filePickerIntent = Intent(Intent.ACTION_GET_CONTENT).apply { + type = "*/*" // General type to include multiple types + putExtra(Intent.EXTRA_MIME_TYPES, attachmentFilter.getSupportedMimeTypes().toTypedArray()) + addCategory(Intent.CATEGORY_OPENABLE) + } + + filePickerLauncher.launch(filePickerIntent) + } + binding.buttonMedia.setOnClickListener { + imagePickerLauncher.launch( + PickVisualMediaRequest( + mediaType = ActivityResultContracts.PickVisualMedia.ImageAndVideo, + ), + ) + } + captureMedia = activity?.activityResultRegistry + ?.register( + LauncherRequestsKeys.CAPTURE_MEDIA, + CaptureMediaContract(style.pickerMediaMode.mode), + ) { file: File? -> + val result: List = if (file == null) { + emptyList() + } else { + listOf(AttachmentMetaData(requireContext(), file)) + } + + attachmentsPickerTabListener?.onSelectedAttachmentsChanged(result) + attachmentsPickerTabListener?.onSelectedAttachmentsSubmitted() + } + captureMedia?.let { + binding.buttonCapture.setOnClickListener { + captureMedia?.launch(Unit) + } + } + + binding.buttonPolls.setOnClickListener { + context.getFragmentManager()?.let { + CreatePollDialogFragment.newInstance(object : CreatePollDialogFragment.CreatePollDialogListener { + override fun onCreatePoll(pollConfig: PollConfig) { + attachmentsPickerTabListener?.onPollSubmitted(pollConfig) + } + + override fun onDismiss() { + attachmentsPickerTabListener?.onPollSubmitted(null) + } + }) + .show(it, CreatePollDialogFragment.TAG) + } + } + } + + /** + * Sets the listener invoked when attachments are selected in the attachment tab. + * + * @param attachmentsPickerTabListener The listener invoked when attachments are selected in the tab. + */ + fun setAttachmentsPickerTabListener(attachmentsPickerTabListener: AttachmentsPickerTabListener) { + this.attachmentsPickerTabListener = attachmentsPickerTabListener + } + + /** + * Set the style. + * + * @param style Style for the dialog. + */ + fun setStyle(style: AttachmentsPickerDialogStyle) { + this.style = style + } + + companion object { + + fun newInstance( + style: AttachmentsPickerDialogStyle, + attachmentsPickerTabListener: AttachmentsPickerTabListener, + config: AttachmentsPickerSystemConfig, + ): Fragment = AttachmentsPickerSystemFragment().apply { + this.style = style + this.attachmentsPickerTabListener = attachmentsPickerTabListener + this.config = config + } + } +} + +internal data class AttachmentsPickerSystemConfig( + val mediaAttachmentsTabEnabled: Boolean, + val fileAttachmentsTabEnabled: Boolean, + val cameraAttachmentsTabEnabled: Boolean, + val pollAttachmentsTabEnabled: Boolean, +) diff --git a/stream-chat-android-ui-components/src/main/res/layout/stream_ui_fragment_attachment_system_picker.xml b/stream-chat-android-ui-components/src/main/res/layout/stream_ui_fragment_attachment_system_picker.xml new file mode 100644 index 00000000000..7faae5b23e5 --- /dev/null +++ b/stream-chat-android-ui-components/src/main/res/layout/stream_ui_fragment_attachment_system_picker.xml @@ -0,0 +1,189 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/stream-chat-android-ui-components/src/main/res/values/attrs_message_composer_view.xml b/stream-chat-android-ui-components/src/main/res/values/attrs_message_composer_view.xml index fcdafdb7854..253c738d00f 100644 --- a/stream-chat-android-ui-components/src/main/res/values/attrs_message_composer_view.xml +++ b/stream-chat-android-ui-components/src/main/res/values/attrs_message_composer_view.xml @@ -314,6 +314,7 @@ +