Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,12 @@
## stream-chat-android-compose
### 🐞 Fixed
- Fix unsent messages should be placed as the last ones in the message list. [#5959](https://github.com/GetStream/stream-chat-android/pull/5959)
- Fix poll creation screen not persisting its state after an orientation change. [#5958](https://github.com/GetStream/stream-chat-android/pull/5958)

### ⬆️ Improved

### ✅ Added
- Add `CreatePollScreen` composable to expose the poll creation functionality outside of attachment picker factories. [#5958](https://github.com/GetStream/stream-chat-android/pull/5958)

### ⚠️ Changed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ internal class LocationPickerTabFactory(
imageVector = Icons.Rounded.ShareLocation,
contentDescription = "Share Location",
tint = when {
isSelected -> ChatTheme.colors.primaryAccent
isEnabled -> ChatTheme.colors.textLowEmphasis
else -> ChatTheme.colors.disabled
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2373,6 +2373,10 @@ public final class io/getstream/chat/android/compose/ui/messages/attachments/pol
public final fun getLambda-1$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2;
}

public final class io/getstream/chat/android/compose/ui/messages/attachments/poll/CreatePollScreenKt {
public static final fun CreatePollScreen (Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)V
}

public final class io/getstream/chat/android/compose/ui/messages/attachments/poll/PollCreationDiscardDialogKt {
public static final fun PollCreationDiscardDialog (ZLkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;II)V
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
Expand Down Expand Up @@ -101,8 +101,7 @@ public fun AttachmentsPicker(
.indexOfFirst { it.isPickerTabEnabled(attachmentsPickerViewModel.channel) }
.takeIf { it >= 0 }
?: 0
var selectedTabIndex by remember { mutableIntStateOf(defaultTabIndex) }
var selectedAttachmentsPickerMode: AttachmentsPickerMode? by remember { mutableStateOf(null) }
var selectedTabIndex by rememberSaveable { mutableIntStateOf(defaultTabIndex) }

Box(
modifier = Modifier
Expand All @@ -121,15 +120,15 @@ public fun AttachmentsPicker(
onClick = {},
interactionSource = null,
),
shape = if (selectedAttachmentsPickerMode?.isFullContent == true) {
shape = if (attachmentsPickerViewModel.attachmentsPickerMode.isFullContent) {
RoundedCornerShape(0.dp)
} else {
shape
},
colors = CardDefaults.cardColors(containerColor = ChatTheme.attachmentPickerTheme.backgroundSecondary),
) {
Column {
if (selectedAttachmentsPickerMode == null || selectedAttachmentsPickerMode?.isFullContent == false) {
if (!attachmentsPickerViewModel.attachmentsPickerMode.isFullContent) {
AttachmentPickerOptions(
hasPickedAttachments = attachmentsPickerViewModel.hasPickedAttachments,
tabFactories = allowedFactories,
Expand All @@ -138,7 +137,6 @@ public fun AttachmentsPicker(
onTabClick = { index, attachmentPickerMode ->
onTabClick.invoke(index, attachmentPickerMode)
selectedTabIndex = index
selectedAttachmentsPickerMode = attachmentPickerMode
attachmentsPickerViewModel.changeAttachmentPickerMode(attachmentPickerMode) { false }
},
onSendAttachmentsClick = {
Expand All @@ -149,7 +147,7 @@ public fun AttachmentsPicker(

Surface(
modifier = Modifier.fillMaxSize(),
shape = if (selectedAttachmentsPickerMode?.isFullContent == true) {
shape = if (attachmentsPickerViewModel.attachmentsPickerMode.isFullContent) {
RoundedCornerShape(0.dp)
} else {
shape
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,52 +17,22 @@
package io.getstream.chat.android.compose.ui.messages.attachments.factory

import android.content.res.Configuration
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastAny
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.Poll
import io.getstream.chat.android.compose.ui.messages.attachments.poll.PollCreationDiscardDialog
import io.getstream.chat.android.compose.ui.messages.attachments.poll.PollCreationHeader
import io.getstream.chat.android.compose.ui.messages.attachments.poll.PollOptionItem
import io.getstream.chat.android.compose.ui.messages.attachments.poll.PollOptionList
import io.getstream.chat.android.compose.ui.messages.attachments.poll.PollQuestionInput
import io.getstream.chat.android.compose.ui.messages.attachments.poll.PollSwitchItem
import io.getstream.chat.android.compose.ui.messages.attachments.poll.PollSwitchList
import io.getstream.chat.android.compose.ui.messages.attachments.poll.pollConfigFrom
import io.getstream.chat.android.compose.ui.messages.attachments.poll.CreatePollScreen
import io.getstream.chat.android.compose.ui.theme.ChatTheme
import io.getstream.chat.android.compose.util.extensions.isPollEnabled
import io.getstream.chat.android.models.Channel
import io.getstream.chat.android.ui.common.state.messages.composer.AttachmentMetaData
import kotlinx.coroutines.launch

/**
* Holds the information required to add support for "poll" tab in the attachment picker.
Expand Down Expand Up @@ -90,6 +60,7 @@ public class AttachmentsPickerPollTabFactory : AttachmentsPickerTabFactory {
painter = painterResource(id = R.drawable.stream_compose_ic_poll),
contentDescription = stringResource(id = R.string.stream_compose_poll_option),
tint = when {
isSelected -> ChatTheme.colors.primaryAccent
isEnabled -> ChatTheme.colors.textLowEmphasis
else -> ChatTheme.colors.disabled
},
Expand All @@ -113,118 +84,8 @@ public class AttachmentsPickerPollTabFactory : AttachmentsPickerTabFactory {
onAttachmentItemSelected: (AttachmentPickerItemState) -> Unit,
onAttachmentsSubmitted: (List<AttachmentMetaData>) -> Unit,
) {
val coroutineScope = rememberCoroutineScope()
val questionListLazyState = rememberLazyListState()
val pollSwitchItemFactory = ChatTheme.pollSwitchitemFactory
var optionItemList by remember { mutableStateOf(emptyList<PollOptionItem>()) }
var switchItemList: List<PollSwitchItem> by remember { mutableStateOf(pollSwitchItemFactory.providePollSwitchItemList()) }
var hasError by remember { mutableStateOf(false) }
val nestedScrollConnection = remember {
object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
val delta = -available.y
coroutineScope.launch {
questionListLazyState.scrollBy(delta)
}
return Offset.Zero
}
}
}

Column(
modifier = Modifier
.fillMaxWidth()
.nestedScroll(nestedScrollConnection)
.verticalScroll(rememberScrollState())
.background(ChatTheme.colors.appBackground),
) {
val (question, onQuestionChanged) = rememberSaveable { mutableStateOf("") }
val isEnabled = question.isNotBlank() && optionItemList.any { it.title.isNotBlank() } && !hasError
val hasChanges = question.isNotBlank() || optionItemList.any { it.title.isNotBlank() }
var isShowingDiscardDialog by remember { mutableStateOf(false) }

PollCreationHeader(
modifier = Modifier.fillMaxWidth(),
enabledCreation = isEnabled,
onPollCreateClicked = {
onAttachmentPickerAction.invoke(
AttachmentPickerPollCreation(
pollConfigFrom(
pollQuestion = question,
pollOptions = optionItemList,
pollSwitches = switchItemList,
),
),
)
onAttachmentPickerAction.invoke(AttachmentPickerBack)
},
onBackPressed = {
if (!hasChanges) {
onAttachmentPickerAction.invoke(AttachmentPickerBack)
} else {
isShowingDiscardDialog = true
}
},
)

PollQuestionInput(
question = question,
onQuestionChanged = onQuestionChanged,
)

PollOptionList(
lazyListState = questionListLazyState,
onQuestionsChanged = {
optionItemList = it
hasError = hasError(optionItemList, switchItemList)
},
)

Spacer(modifier = Modifier.height(32.dp))

PollSwitchList(
pollSwitchItems = switchItemList,
onSwitchesChanged = {
switchItemList = it
hasError = hasError(optionItemList, switchItemList)
},
)

BackHandler(enabled = hasChanges) { isShowingDiscardDialog = true }

if (isShowingDiscardDialog) {
PollCreationDiscardDialog(
onCancelClicked = { isShowingDiscardDialog = false },
onDiscardClicked = {
isShowingDiscardDialog = false
onAttachmentPickerAction.invoke(AttachmentPickerBack)
},
)
}
}
}
}

/**
* Checks if there are any errors in the 'options' list, or any errors or missing fields in the 'switches' list.
*/
private fun hasError(
options: List<PollOptionItem>,
switches: List<PollSwitchItem>,
): Boolean {
// Check errors in options
val hasErrorInOptions = options.fastAny { item ->
item.pollOptionError != null
}
// Check errors or missing fields in switches
val hasErrorInSwitches = switches.fastAny { item ->
val hasError = item.pollOptionError != null
val isMissingMandatoryInput = item.enabled &&
item.pollSwitchInput != null &&
item.pollSwitchInput.value.toString().isEmpty()
hasError || isMissingMandatoryInput
CreatePollScreen(onAttachmentPickerAction)
}
return hasErrorInOptions || hasErrorInSwitches
}

@Preview
Expand Down
Loading
Loading