Add long click support on timeline item
This commit is contained in:
@@ -1,10 +1,11 @@
|
|||||||
@file:OptIn(ExperimentalMaterial3Api::class)
|
@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
|
||||||
|
|
||||||
package io.element.android.x.features.messages
|
package io.element.android.x.features.messages
|
||||||
|
|
||||||
import Avatar
|
import Avatar
|
||||||
import androidx.compose.foundation.BorderStroke
|
import androidx.compose.foundation.BorderStroke
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
@@ -12,14 +13,14 @@ import androidx.compose.foundation.lazy.LazyListState
|
|||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
|
import androidx.compose.material.ModalBottomSheetValue
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
|
import androidx.compose.material.rememberModalBottomSheetState
|
||||||
import androidx.compose.material.ripple.rememberRipple
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Alignment.Companion.End
|
import androidx.compose.ui.Alignment.Companion.End
|
||||||
import androidx.compose.ui.Alignment.Companion.Start
|
import androidx.compose.ui.Alignment.Companion.Start
|
||||||
@@ -49,6 +50,8 @@ import io.element.android.x.features.messages.model.content.MessagesTimelineItem
|
|||||||
import io.element.android.x.features.messages.textcomposer.MessageComposerViewModel
|
import io.element.android.x.features.messages.textcomposer.MessageComposerViewModel
|
||||||
import io.element.android.x.features.messages.textcomposer.MessageComposerViewState
|
import io.element.android.x.features.messages.textcomposer.MessageComposerViewState
|
||||||
import io.element.android.x.textcomposer.TextComposer
|
import io.element.android.x.textcomposer.TextComposer
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
private val BUBBLE_RADIUS = 16.dp
|
private val BUBBLE_RADIUS = 16.dp
|
||||||
private val COMPOSER_HEIGHT = 112.dp
|
private val COMPOSER_HEIGHT = 112.dp
|
||||||
@@ -61,6 +64,10 @@ fun MessagesScreen(
|
|||||||
val viewModel: MessagesViewModel = mavericksViewModel(argsFactory = { roomId })
|
val viewModel: MessagesViewModel = mavericksViewModel(argsFactory = { roomId })
|
||||||
val composerViewModel: MessageComposerViewModel = mavericksViewModel(argsFactory = { roomId })
|
val composerViewModel: MessageComposerViewModel = mavericksViewModel(argsFactory = { roomId })
|
||||||
LogCompositions(tag = "MessagesScreen", msg = "Root")
|
LogCompositions(tag = "MessagesScreen", msg = "Root")
|
||||||
|
val actionsSheetState = rememberModalBottomSheetState(
|
||||||
|
initialValue = ModalBottomSheetValue.Hidden,
|
||||||
|
)
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val roomTitle by viewModel.collectAsState(MessagesViewState::roomName)
|
val roomTitle by viewModel.collectAsState(MessagesViewState::roomName)
|
||||||
val roomAvatar by viewModel.collectAsState(MessagesViewState::roomAvatar)
|
val roomAvatar by viewModel.collectAsState(MessagesViewState::roomAvatar)
|
||||||
val timelineItems by viewModel.collectAsState(MessagesViewState::timelineItems)
|
val timelineItems by viewModel.collectAsState(MessagesViewState::timelineItems)
|
||||||
@@ -84,6 +91,21 @@ fun MessagesScreen(
|
|||||||
onComposerTextChange = composerViewModel::updateText,
|
onComposerTextChange = composerViewModel::updateText,
|
||||||
composerCanSendMessage = composerCanSendMessage,
|
composerCanSendMessage = composerCanSendMessage,
|
||||||
composerText = composerText,
|
composerText = composerText,
|
||||||
|
onClick = {
|
||||||
|
Timber.v("onClick on timeline item: ${it.id}")
|
||||||
|
},
|
||||||
|
onLongClick = {
|
||||||
|
viewModel.computeActionsSheetState(it)
|
||||||
|
coroutineScope.launch {
|
||||||
|
actionsSheetState.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
val itemActionsSheetState by viewModel.collectAsState(prop1 = MessagesViewState::itemActionsSheetState)
|
||||||
|
TimelineItemActionsScreen(
|
||||||
|
sheetState = actionsSheetState,
|
||||||
|
actionsSheetState = itemActionsSheetState(),
|
||||||
|
onActionClicked = viewModel::handleItemAction
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,6 +118,8 @@ fun MessagesContent(
|
|||||||
onReachedLoadMore: () -> Unit,
|
onReachedLoadMore: () -> Unit,
|
||||||
onBackPressed: () -> Unit,
|
onBackPressed: () -> Unit,
|
||||||
onSendMessage: (String) -> Unit,
|
onSendMessage: (String) -> Unit,
|
||||||
|
onClick: (MessagesTimelineItemState.MessageEvent) -> Unit,
|
||||||
|
onLongClick: ((MessagesTimelineItemState.MessageEvent)) -> Unit,
|
||||||
composerFullScreen: Boolean,
|
composerFullScreen: Boolean,
|
||||||
onComposerFullScreenChange: () -> Unit,
|
onComposerFullScreenChange: () -> Unit,
|
||||||
onComposerTextChange: (CharSequence) -> Unit,
|
onComposerTextChange: (CharSequence) -> Unit,
|
||||||
@@ -145,7 +169,9 @@ fun MessagesContent(
|
|||||||
timelineItems = timelineItems,
|
timelineItems = timelineItems,
|
||||||
hasMoreToLoad = hasMoreToLoad,
|
hasMoreToLoad = hasMoreToLoad,
|
||||||
onReachedLoadMore = onReachedLoadMore,
|
onReachedLoadMore = onReachedLoadMore,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f),
|
||||||
|
onClick = onClick,
|
||||||
|
onLongClick = onLongClick
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
TextComposer(
|
TextComposer(
|
||||||
@@ -175,6 +201,8 @@ fun TimelineItems(
|
|||||||
lazyListState: LazyListState,
|
lazyListState: LazyListState,
|
||||||
timelineItems: List<MessagesTimelineItemState>,
|
timelineItems: List<MessagesTimelineItemState>,
|
||||||
hasMoreToLoad: Boolean,
|
hasMoreToLoad: Boolean,
|
||||||
|
onClick: (MessagesTimelineItemState.MessageEvent) -> Unit,
|
||||||
|
onLongClick: ((MessagesTimelineItemState.MessageEvent)) -> Unit,
|
||||||
onReachedLoadMore: () -> Unit,
|
onReachedLoadMore: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
@@ -186,7 +214,11 @@ fun TimelineItems(
|
|||||||
reverseLayout = true
|
reverseLayout = true
|
||||||
) {
|
) {
|
||||||
itemsIndexed(timelineItems) { index, timelineItem ->
|
itemsIndexed(timelineItems) { index, timelineItem ->
|
||||||
TimelineItemRow(timelineItem = timelineItem)
|
TimelineItemRow(
|
||||||
|
timelineItem = timelineItem,
|
||||||
|
onClick = onClick,
|
||||||
|
onLongClick = onLongClick
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (hasMoreToLoad) {
|
if (hasMoreToLoad) {
|
||||||
item {
|
item {
|
||||||
@@ -199,17 +231,25 @@ fun TimelineItems(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TimelineItemRow(
|
fun TimelineItemRow(
|
||||||
timelineItem: MessagesTimelineItemState
|
timelineItem: MessagesTimelineItemState,
|
||||||
|
onClick: (MessagesTimelineItemState.MessageEvent) -> Unit,
|
||||||
|
onLongClick: (MessagesTimelineItemState.MessageEvent) -> Unit,
|
||||||
) {
|
) {
|
||||||
when (timelineItem) {
|
when (timelineItem) {
|
||||||
is MessagesTimelineItemState.Virtual -> return
|
is MessagesTimelineItemState.Virtual -> return
|
||||||
is MessagesTimelineItemState.MessageEvent -> MessageEventRow(messageEvent = timelineItem)
|
is MessagesTimelineItemState.MessageEvent -> MessageEventRow(
|
||||||
|
messageEvent = timelineItem,
|
||||||
|
onClick = { onClick(timelineItem) },
|
||||||
|
onLongClick = { onLongClick(timelineItem) }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MessageEventRow(
|
fun MessageEventRow(
|
||||||
messageEvent: MessagesTimelineItemState.MessageEvent,
|
messageEvent: MessagesTimelineItemState.MessageEvent,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
onLongClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val (parentAlignment, contentAlignment) = if (messageEvent.isMine) {
|
val (parentAlignment, contentAlignment) = if (messageEvent.isMine) {
|
||||||
@@ -241,6 +281,8 @@ fun MessageEventRow(
|
|||||||
MessageEventBubble(
|
MessageEventBubble(
|
||||||
groupPosition = messageEvent.groupPosition,
|
groupPosition = messageEvent.groupPosition,
|
||||||
isMine = messageEvent.isMine,
|
isMine = messageEvent.isMine,
|
||||||
|
onClick = onClick,
|
||||||
|
onLongClick = onLongClick,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.zIndex(-1f)
|
.zIndex(-1f)
|
||||||
) {
|
) {
|
||||||
@@ -304,11 +346,14 @@ private fun MessageSenderInformation(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun MessageEventBubble(
|
fun MessageEventBubble(
|
||||||
groupPosition: MessagesItemGroupPosition,
|
groupPosition: MessagesItemGroupPosition,
|
||||||
isMine: Boolean,
|
isMine: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
onLongClick: () -> Unit,
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
fun bubbleShape(): Shape {
|
fun bubbleShape(): Shape {
|
||||||
@@ -360,8 +405,9 @@ fun MessageEventBubble(
|
|||||||
.widthIn(min = 80.dp)
|
.widthIn(min = 80.dp)
|
||||||
.offsetForItem()
|
.offsetForItem()
|
||||||
.clip(bubbleShape)
|
.clip(bubbleShape)
|
||||||
.clickable(
|
.combinedClickable(
|
||||||
onClick = { },
|
onClick = onClick,
|
||||||
|
onLongClick = onLongClick,
|
||||||
indication = rememberRipple(),
|
indication = rememberRipple(),
|
||||||
interactionSource = remember { MutableInteractionSource() }
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import com.airbnb.mvrx.MavericksViewModelFactory
|
|||||||
import com.airbnb.mvrx.ViewModelContext
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
import io.element.android.x.designsystem.components.avatar.AvatarData
|
import io.element.android.x.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.x.designsystem.components.avatar.AvatarSize
|
import io.element.android.x.designsystem.components.avatar.AvatarSize
|
||||||
|
import io.element.android.x.features.messages.model.MessagesItemAction
|
||||||
|
import io.element.android.x.features.messages.model.MessagesItemActionsSheetState
|
||||||
|
import io.element.android.x.features.messages.model.MessagesTimelineItemState
|
||||||
import io.element.android.x.features.messages.model.MessagesViewState
|
import io.element.android.x.features.messages.model.MessagesViewState
|
||||||
import io.element.android.x.matrix.MatrixClient
|
import io.element.android.x.matrix.MatrixClient
|
||||||
import io.element.android.x.matrix.MatrixInstance
|
import io.element.android.x.matrix.MatrixInstance
|
||||||
@@ -16,6 +19,7 @@ import kotlinx.coroutines.flow.launchIn
|
|||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
private const val PAGINATION_COUNT = 50
|
private const val PAGINATION_COUNT = 50
|
||||||
|
|
||||||
@@ -66,6 +70,28 @@ class MessagesViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun handleItemAction(action: MessagesItemAction) {
|
||||||
|
viewModelScope.launch(Dispatchers.Default) {
|
||||||
|
val currentState = awaitState()
|
||||||
|
Timber.v("Handle $action for ${currentState.itemActionsSheetState}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun computeActionsSheetState(messagesTimelineItemState: MessagesTimelineItemState.MessageEvent) {
|
||||||
|
suspend {
|
||||||
|
val actions = listOf(
|
||||||
|
MessagesItemAction.Forward,
|
||||||
|
MessagesItemAction.Copy,
|
||||||
|
)
|
||||||
|
MessagesItemActionsSheetState(
|
||||||
|
targetItem = messagesTimelineItemState,
|
||||||
|
actions = actions
|
||||||
|
)
|
||||||
|
}.execute(Dispatchers.Default) {
|
||||||
|
copy(itemActionsSheetState = it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleInit() {
|
private fun handleInit() {
|
||||||
timeline.initialize()
|
timeline.initialize()
|
||||||
room.syncUpdateFlow()
|
room.syncUpdateFlow()
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
@file:OptIn(ExperimentalMaterialApi::class)
|
||||||
|
|
||||||
|
package io.element.android.x.features.messages.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import io.element.android.x.designsystem.components.VectorIcon
|
||||||
|
import io.element.android.x.features.messages.model.MessagesItemAction
|
||||||
|
import io.element.android.x.features.messages.model.MessagesItemActionsSheetState
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TimelineItemActionsScreen(
|
||||||
|
sheetState: ModalBottomSheetState,
|
||||||
|
actionsSheetState: MessagesItemActionsSheetState?,
|
||||||
|
onActionClicked: (MessagesItemAction) -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
ModalBottomSheetLayout(
|
||||||
|
modifier = modifier,
|
||||||
|
sheetState = sheetState,
|
||||||
|
sheetContent = {
|
||||||
|
SheetContent(
|
||||||
|
actionsSheetState = actionsSheetState,
|
||||||
|
onActionClicked = onActionClicked
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SheetContent(
|
||||||
|
actionsSheetState: MessagesItemActionsSheetState?,
|
||||||
|
onActionClicked: (MessagesItemAction) -> Unit,
|
||||||
|
) {
|
||||||
|
if (actionsSheetState == null || actionsSheetState.actions.isEmpty()) {
|
||||||
|
// Crashes if sheetContent size is zero
|
||||||
|
Box(modifier = Modifier.size(1.dp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
LazyColumn {
|
||||||
|
items(actionsSheetState.actions) {
|
||||||
|
ListItem(
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
onActionClicked(it)
|
||||||
|
},
|
||||||
|
text = { Text(it.title) },
|
||||||
|
icon = { VectorIcon(it.icon) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package io.element.android.x.features.messages.model
|
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import io.element.android.x.designsystem.VectorIcons
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
sealed class MessagesItemAction(val title: String, @DrawableRes val icon: Int) {
|
||||||
|
object Forward : MessagesItemAction("Forward", VectorIcons.ArrowForward)
|
||||||
|
object Copy : MessagesItemAction("Copy", VectorIcons.Copy)
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package io.element.android.x.features.messages.model
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
data class MessagesItemActionsSheetState(
|
||||||
|
val targetItem: MessagesTimelineItemState.MessageEvent,
|
||||||
|
val actions: List<MessagesItemAction>
|
||||||
|
)
|
||||||
@@ -6,7 +6,6 @@ import androidx.compose.runtime.Stable
|
|||||||
data class MessagesItemReactionState(
|
data class MessagesItemReactionState(
|
||||||
val reactions: List<AggregatedReaction>
|
val reactions: List<AggregatedReaction>
|
||||||
)
|
)
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
data class AggregatedReaction(
|
data class AggregatedReaction(
|
||||||
val key: String,
|
val key: String,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ data class MessagesViewState(
|
|||||||
val roomAvatar: AvatarData? = null,
|
val roomAvatar: AvatarData? = null,
|
||||||
val timelineItems: Async<List<MessagesTimelineItemState>> = Uninitialized,
|
val timelineItems: Async<List<MessagesTimelineItemState>> = Uninitialized,
|
||||||
val hasMoreToLoad: Boolean = true,
|
val hasMoreToLoad: Boolean = true,
|
||||||
|
val itemActionsSheetState: Async<MessagesItemActionsSheetState> = Uninitialized
|
||||||
) : MavericksState {
|
) : MavericksState {
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package io.element.android.x.designsystem
|
||||||
|
|
||||||
|
import io.element.android.x.libraries.designsystem.R
|
||||||
|
|
||||||
|
object VectorIcons {
|
||||||
|
val Copy = R.drawable.ic_content_copy
|
||||||
|
val ArrowForward = R.drawable.ic_content_arrow_forward
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package io.element.android.x.designsystem.components
|
||||||
|
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.LocalContentColor
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun VectorIcon(
|
||||||
|
resourceId: Int,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
tint: Color = LocalContentColor.current,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = resourceId),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = modifier,
|
||||||
|
tint = tint
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<vector android:autoMirrored="true" android:height="24dp"
|
||||||
|
android:tint="#000000" android:viewportHeight="24"
|
||||||
|
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M12,4l-1.41,1.41L16.17,11H4v2h12.17l-5.58,5.59L12,20l8,-8z"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<vector android:height="24dp" android:tint="#000000"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"/>
|
||||||
|
</vector>
|
||||||
@@ -17,6 +17,7 @@ dependencies {
|
|||||||
implementation(platform("androidx.compose:compose-bom:2022.10.00"))
|
implementation(platform("androidx.compose:compose-bom:2022.10.00"))
|
||||||
|
|
||||||
implementation("androidx.compose.ui:ui")
|
implementation("androidx.compose.ui:ui")
|
||||||
|
implementation("androidx.compose.material:material")
|
||||||
implementation("androidx.compose.material3:material3")
|
implementation("androidx.compose.material3:material3")
|
||||||
implementation("androidx.compose.ui:ui-tooling-preview")
|
implementation("androidx.compose.ui:ui-tooling-preview")
|
||||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.5.1")
|
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.5.1")
|
||||||
|
|||||||
Reference in New Issue
Block a user