diff --git a/README.md b/README.md
index e8ab712..eca059c 100644
--- a/README.md
+++ b/README.md
@@ -92,6 +92,12 @@ Discord Compose follows the principles of Clean Architecture with Android Archit
|
|
+
+ Chat |
+
+
+ |
+
## 📷 Screenshots (Light theme)
@@ -113,7 +119,7 @@ Discord Compose follows the principles of Clean Architecture with Android Archit
Invite |
Password Manager Dialog |
Create Server |
-Friends |
+ Friends |
|
@@ -121,6 +127,12 @@ Discord Compose follows the principles of Clean Architecture with Android Archit
|
|
+
+ Chat |
+
+
+ |
+
[Back to top]
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 7378134..e7fd11b 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -117,6 +117,7 @@ dependencies {
/* Image Loading */
implementation(Lib.Android.COIL_COMPOSE)
+ implementation(Lib.Android.ACCOMPANIST_COIL)
/*DI*/
implementation(Lib.Di.hiltAndroid)
diff --git a/app/src/main/java/dev/baseio/discordjetpackcompose/MainActivity.kt b/app/src/main/java/dev/baseio/discordjetpackcompose/MainActivity.kt
index 3dc7f70..e98bc38 100644
--- a/app/src/main/java/dev/baseio/discordjetpackcompose/MainActivity.kt
+++ b/app/src/main/java/dev/baseio/discordjetpackcompose/MainActivity.kt
@@ -1,10 +1,6 @@
package dev.baseio.discordjetpackcompose
-import android.os.Build
import android.os.Bundle
-import android.view.WindowInsets
-import android.view.WindowInsetsController
-import android.view.WindowManager
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.runtime.LaunchedEffect
diff --git a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/DashboardRoute.kt b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/DashboardRoute.kt
index 07efc14..7b145a6 100644
--- a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/DashboardRoute.kt
+++ b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/DashboardRoute.kt
@@ -14,6 +14,7 @@ import dev.baseio.discordjetpackcompose.ui.routes.dashboard.friends.FriendsScree
import dev.baseio.discordjetpackcompose.ui.routes.dashboard.invite.InviteScreen
import dev.baseio.discordjetpackcompose.ui.routes.dashboard.main.HomeScreen
import dev.baseio.discordjetpackcompose.ui.routes.dashboard.main.dasboard.DashboardScreen
+import dev.baseio.discordjetpackcompose.ui.routes.dashboard.userSettings.UserSettings
fun NavGraphBuilder.dashboardRoute(
composeNavigator: ComposeNavigator,
@@ -34,6 +35,9 @@ fun NavGraphBuilder.dashboardRoute(
composable(DiscordScreen.CreateServer.name) {
CreateServer(composeNavigator)
}
+ composable(DiscordScreen.UserSettings.name) {
+ UserSettings(composeNavigator = composeNavigator)
+ }
}
}
@@ -62,5 +66,8 @@ fun NavGraphBuilder.setupDashboardBottomNavScreens(
composable(DiscordScreen.Friends.route) {
FriendsScreen(composeNavigator = composeNavigator)
}
+ composable(DiscordScreen.UserSettings.name) {
+ UserSettings(composeNavigator = composeNavigator)
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/friends/FriendsScreen.kt b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/friends/FriendsScreen.kt
index f1ce63c..63cccd3 100644
--- a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/friends/FriendsScreen.kt
+++ b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/friends/FriendsScreen.kt
@@ -16,6 +16,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -139,10 +140,10 @@ fun FriendsScreen(
}
@Composable
-fun Header(title: Int, size: Int) {
+fun Header(title: Int, size: Int, style: TextStyle = DirectMessageListTypography.h5) {
Text(
text = stringResource(title, size),
- style = DirectMessageListTypography.h5,
+ style = style,
color = DiscordColorProvider.colors.onSurface.copy(alpha = ContentAlpha.medium)
)
}
\ No newline at end of file
diff --git a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/HomeScreen.kt b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/HomeScreen.kt
index a7e539c..22871f2 100644
--- a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/HomeScreen.kt
+++ b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/HomeScreen.kt
@@ -13,13 +13,7 @@ import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
-import androidx.compose.material.ContentAlpha
-import androidx.compose.material.ExperimentalMaterialApi
-import androidx.compose.material.FractionalThreshold
-import androidx.compose.material.ModalBottomSheetState
-import androidx.compose.material.SwipeableState
-import androidx.compose.material.rememberSwipeableState
-import androidx.compose.material.swipeable
+import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
@@ -45,10 +39,12 @@ import dev.baseio.discordjetpackcompose.entities.ChatUserEntity
import dev.baseio.discordjetpackcompose.entities.server.ServerEntity
import dev.baseio.discordjetpackcompose.navigator.ComposeNavigator
import dev.baseio.discordjetpackcompose.navigator.DiscordScreen
+import dev.baseio.discordjetpackcompose.ui.routes.dashboard.main.chatscreen.ChannelMemberScreen
import dev.baseio.discordjetpackcompose.ui.routes.dashboard.main.chatscreen.ChatScreen
import dev.baseio.discordjetpackcompose.ui.routes.dashboard.main.dasboard.getFakeChatUserList
import dev.baseio.discordjetpackcompose.ui.routes.dashboard.main.dasboard.getFakeServerList
import dev.baseio.discordjetpackcompose.ui.theme.DiscordColorProvider
+import dev.baseio.discordjetpackcompose.ui.theme.channel_member_bg
import dev.baseio.discordjetpackcompose.viewmodels.DashboardScreenViewModel
import kotlinx.coroutines.CoroutineScope
import kotlin.math.roundToInt
@@ -131,9 +127,9 @@ fun HomeScreen(
val leftDrawerModifier by remember(drawerOnTop, isAnyItemSelectedInServers) {
mutableStateOf(
- swipeableModifier
- .zIndex(if (drawerOnTop == DrawerTypes.LEFT) 1f else 0f)
- .alpha(if (drawerOnTop == DrawerTypes.LEFT) 1f else 0f)
+ swipeableModifier
+ .zIndex(if (drawerOnTop == DrawerTypes.LEFT) 1f else 0f)
+ .alpha(if (drawerOnTop == DrawerTypes.LEFT) 1f else 0f)
)
}
@@ -190,13 +186,16 @@ fun HomeScreen(
openServerInfoBottomSheet = { coroutineScope.launch { sheetState.show() } },
viewModel = viewModel
)
- Box(
+ ChannelMemberScreen(
modifier = rightDrawerModifier
.fillMaxHeight()
.fillMaxWidth(0.85f)
- .background(Color.Cyan)
- .align(Alignment.CenterEnd)
- ) {}
+ .background(channel_member_bg)
+ .align(Alignment.CenterEnd),
+ onInviteButtonClicked = {
+ composeNavigator.navigate(DiscordScreen.Invite.route)
+ }
+ )
val centerScreenZIndex by remember {
derivedStateOf {
diff --git a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/chatscreen/ChannelMemberScreen.kt b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/chatscreen/ChannelMemberScreen.kt
new file mode 100644
index 0000000..63c592c
--- /dev/null
+++ b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/chatscreen/ChannelMemberScreen.kt
@@ -0,0 +1,271 @@
+package dev.baseio.discordjetpackcompose.ui.routes.dashboard.main.chatscreen
+
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.Divider
+import androidx.compose.material.Icon
+import androidx.compose.material.Text
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Notifications
+import androidx.compose.material.icons.filled.PushPin
+import androidx.compose.material.icons.filled.Settings
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.hilt.navigation.compose.hiltViewModel
+import coil.compose.AsyncImage
+import coil.request.ImageRequest
+import dev.baseio.discordjetpackcompose.R
+import dev.baseio.discordjetpackcompose.entities.ChatUserEntity
+import dev.baseio.discordjetpackcompose.ui.routes.dashboard.components.OnlineIndicator
+import dev.baseio.discordjetpackcompose.ui.routes.dashboard.friends.Header
+import dev.baseio.discordjetpackcompose.ui.theme.*
+import dev.baseio.discordjetpackcompose.viewmodels.FriendsViewModel
+
+@Composable
+fun ChannelMemberScreen(
+ modifier: Modifier,
+ onInviteButtonClicked: () -> Unit,
+ viewModel: FriendsViewModel = hiltViewModel()
+) {
+
+ val friendsList by viewModel.friendsList.collectAsState()
+ val friends by friendsList.collectAsState(initial = emptyList())
+
+ val onlineList = friends.filter {
+ it.isOnline
+ }
+
+ val offlineList = friends.filter {
+ !it.isOnline
+ }
+
+ Column(
+ modifier = modifier,
+ horizontalAlignment = Alignment.Start,
+ verticalArrangement = Arrangement.Top
+ ) {
+ ChannelHeaderText()
+ Divider(
+ color = channel_member_secondary_bg,
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(1.dp)
+ .background(create_server_screen)
+ )
+ ChannelMemberActions()
+ InviteMembers(onInviteButtonClicked)
+ ChannelMembersList(offlineList, onlineList)
+ }
+}
+
+@Composable
+fun ChannelHeaderText() {
+ Text(
+ text = stringResource(id = R.string.channel_header), modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp),
+ style = Typography.h5.copy(
+ fontWeight = FontWeight.SemiBold
+ ),
+ color = DiscordColorProvider.colors.onSurface
+ )
+}
+
+@Composable
+fun ChannelMemberActions() {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 8.dp)
+ .background(channel_member_bg),
+ horizontalArrangement = Arrangement.SpaceEvenly,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ ActionIcons(
+ label = stringResource(id = R.string.threads),
+ painterResource = R.drawable.ic_hashtag_solid,
+ icon = null
+ )
+ ActionIcons(Icons.Filled.PushPin, stringResource(id = R.string.pins), null)
+ ActionIcons(Icons.Filled.Notifications, stringResource(id = R.string.notifications), null)
+ ActionIcons(Icons.Filled.Settings, stringResource(id = R.string.settings), null)
+ }
+}
+
+@Composable
+fun ActionIcons(
+ icon: ImageVector?,
+ label: String,
+ painterResource: Int?
+) {
+ if (icon != null) {
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ Icon(
+ icon,
+ contentDescription = label,
+ modifier = Modifier.size(18.dp),
+ tint = channel_member_action_icon
+ )
+ Spacer(modifier = Modifier.height(4.dp))
+ ActionIconLabel(label = label)
+ }
+ } else if (painterResource != null) {
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ Icon(
+ painterResource(id = painterResource),
+ contentDescription = label,
+ modifier = Modifier.size(18.dp),
+ tint = channel_member_action_icon
+ )
+ Spacer(modifier = Modifier.height(4.dp))
+ ActionIconLabel(label = label)
+ }
+ }
+}
+
+@Composable
+fun InviteMembers(onInviteButtonClicked: () -> Unit) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(channel_member_secondary_bg)
+ .clickable {
+ onInviteButtonClicked()
+ }
+ .padding(16.dp),
+
+ horizontalArrangement = Arrangement.Start,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Icon(
+ painterResource(id = R.drawable.ic_baseline_person_add_alt_1_24),
+ contentDescription = stringResource(id = R.string.invite_members),
+ modifier = Modifier
+ .size(32.dp)
+ .clip(CircleShape)
+ .background(channel_member_bg)
+ .padding(6.dp),
+ tint = channel_member_action_icon
+ )
+ Spacer(modifier = Modifier.width(16.dp))
+ Text(
+ text = stringResource(id = R.string.invite_members),
+ color = DiscordColorProvider.colors.onSurface,
+ style = Typography.subtitle2.copy(
+ fontWeight = FontWeight.SemiBold,
+ fontSize = 14.sp
+ )
+ )
+
+ }
+}
+
+
+@Composable
+fun ActionIconLabel(label: String) {
+ Text(
+ text = label,
+ color = channel_member_action_label,
+ style = Typography.subtitle2.copy(
+ fontWeight = FontWeight.Medium,
+ fontSize = 12.sp
+ )
+ )
+}
+
+@Composable
+fun ChannelMembersList(
+ offlineMembersList: List,
+ onlineMembersList: List
+) {
+ LazyColumn(modifier = Modifier
+ .background(channel_member_secondary_bg)
+ .padding(8.dp)) {
+ item {
+ Header(title = R.string.online, onlineMembersList.size)
+ }
+ items(onlineMembersList) { friend ->
+ FriendComponent(chatUserEntity = friend)
+ }
+ item {
+ Spacer(modifier = Modifier.height(10.dp))
+ }
+ item {
+ Header(
+ title = R.string.offline,
+ offlineMembersList.size,
+ style = DirectMessageListTypography.h5.copy(
+ fontSize = 14.sp
+ )
+ )
+ }
+ items(offlineMembersList) { friend ->
+ FriendComponent(chatUserEntity = friend)
+ }
+ item {
+ Spacer(modifier = Modifier.height(40.dp))
+ }
+ }
+}
+
+@Composable
+fun FriendComponent(chatUserEntity: ChatUserEntity) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 8.dp),
+ horizontalArrangement = Arrangement.Start,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Box(
+ contentAlignment = Alignment.BottomEnd,
+ modifier = Modifier.padding(8.dp),
+ ) {
+ AsyncImage(
+ model = ImageRequest.Builder(LocalContext.current)
+ .data(chatUserEntity.profileImage)
+ .placeholder(R.drawable.light_app_logo)
+ .crossfade(true)
+ .build(),
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ modifier = Modifier
+ .width(32.dp)
+ .aspectRatio(1f)
+ .clip(CircleShape),
+ )
+ OnlineIndicator(isOnline = chatUserEntity.isOnline)
+ }
+ Spacer(modifier = Modifier.width(16.dp))
+ Text(
+ text = chatUserEntity.username,
+ style = DirectMessageListTypography.h6.copy(
+ fontWeight =
+ FontWeight.Normal,
+ fontSize = 14.sp
+
+ ),
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ color = DiscordColorProvider.colors.onSurface
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/chatscreen/ChatMesageActions.kt b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/chatscreen/ChatMesageActions.kt
new file mode 100644
index 0000000..00077bb
--- /dev/null
+++ b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/chatscreen/ChatMesageActions.kt
@@ -0,0 +1,171 @@
+package dev.baseio.discordjetpackcompose.ui.routes.dashboard.main.chatscreen
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.ModalBottomSheetLayout
+import androidx.compose.material.ModalBottomSheetState
+import androidx.compose.material.Text
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Delete
+import androidx.compose.material.icons.filled.Edit
+import androidx.compose.material.icons.filled.FileCopy
+import androidx.compose.material.icons.filled.InsertEmoticon
+import androidx.compose.material.icons.filled.MarkAsUnread
+import androidx.compose.material.icons.filled.Person
+import androidx.compose.material.icons.filled.Pin
+import androidx.compose.material.icons.filled.Reply
+import androidx.compose.material.icons.filled.Share
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import dev.baseio.discordjetpackcompose.ui.theme.DiscordColorProvider
+import dev.baseio.discordjetpackcompose.ui.theme.MessageTypography
+import dev.baseio.discordjetpackcompose.ui.utils.Drawables
+
+@OptIn(ExperimentalMaterialApi::class)
+@Composable
+fun MessageActionsBottomSheet(
+ sheetState: ModalBottomSheetState,
+ replyAction: () -> Unit = {}
+) {
+ ModalBottomSheetLayout(
+ modifier = Modifier,
+ sheetState = sheetState,
+ sheetContent = {
+ MessageActionBottomSheetContent(
+ replyAction = replyAction
+ )
+ },
+ sheetShape = RoundedCornerShape(0),
+ sheetBackgroundColor = DiscordColorProvider.colors.surface,
+ sheetContentColor = DiscordColorProvider.colors.onPrimary,
+ scrimColor = Color.Black.copy(alpha = 0.32f),
+ content = {}
+ )
+}
+
+@Composable
+fun MessageActionBottomSheetContent(
+ replyAction: () -> Unit = {}
+) {
+ Column(
+ modifier = Modifier
+ .wrapContentHeight()
+ .fillMaxWidth()
+ ) {
+ MessageActionsEmojisList()
+ MessageActionsList(
+ replyAction = replyAction
+ )
+ }
+}
+
+@Composable
+fun MessageActionsEmojisList() {
+ Row(
+ modifier = Modifier.padding(start = 16.dp, top = 16.dp, bottom = 8.dp)
+ ) {
+ EmojiActionButton(emojiId = Drawables.ic_emoji_1)
+ EmojiActionButton(emojiId = Drawables.ic_emoji_2)
+ EmojiActionButton(emojiId = Drawables.ic_emoji_3)
+ EmojiActionButton(emojiId = Drawables.ic_emoji_4)
+ EmojiActionButton(emojiId = Drawables.ic_emoji_5)
+ IconButton(
+ modifier = Modifier
+ .background(
+ color = DiscordColorProvider.colors.chatEditor,
+ shape = CircleShape
+ ),
+ onClick = {}
+ ) {
+ Icon(
+ Icons.Default.InsertEmoticon,
+ contentDescription = null,
+ tint = DiscordColorProvider.colors.brand
+ )
+ }
+ }
+}
+
+@Composable
+fun MessageActionsList(
+ replyAction: () -> Unit = {}
+) {
+ Column(
+ modifier = Modifier.padding(top = 16.dp, bottom = 48.dp)
+ ) {
+ MessageActionItem(Icons.Default.Edit, "Edit")
+ MessageActionItem(Icons.Default.Reply, "Reply", action = replyAction)
+ MessageActionItem(Icons.Default.FileCopy, "Copy Text")
+ MessageActionItem(Icons.Default.Delete, "Delete")
+ MessageActionItem(Icons.Default.Person, "Profile")
+ MessageActionItem(Icons.Default.Pin, "Pin")
+ MessageActionItem(Icons.Default.Share, "Share")
+ MessageActionItem(Icons.Default.MarkAsUnread, "Mark Unread")
+ MessageActionItem(Icons.Default.FileCopy, "Copy ID")
+ }
+}
+
+@Composable
+fun MessageActionItem(
+ imageVector: ImageVector,
+ text: String,
+ action: () -> Unit = {}
+) {
+ Row(
+ modifier = Modifier
+ .clickable { action() },
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Icon(
+ modifier = Modifier.padding(start = 16.dp, top = 12.dp, bottom = 16.dp),
+ imageVector = imageVector,
+ contentDescription = null,
+ tint = DiscordColorProvider.colors.brand
+ )
+ Text(
+ modifier = Modifier
+ .padding(start = 32.dp, top = 16.dp, bottom = 16.dp)
+ .fillMaxWidth(),
+ style = MessageTypography.caption,
+ text = text,
+ color = DiscordColorProvider.colors.onSurface
+ )
+ }
+}
+
+@Composable
+private fun EmojiActionButton(
+ modifier: Modifier = Modifier,
+ emojiId: Int
+) {
+ IconButton(
+ modifier = modifier
+ .padding(end = 8.dp)
+ .background(
+ color = DiscordColorProvider.colors.chatEditor,
+ shape = CircleShape
+ ),
+ onClick = {}
+ ) {
+ Icon(
+ painterResource(id = emojiId),
+ contentDescription = null,
+ tint = Color.Unspecified
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/chatscreen/ChatMessageEditor.kt b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/chatscreen/ChatMessageEditor.kt
index fb1a6f5..319e255 100644
--- a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/chatscreen/ChatMessageEditor.kt
+++ b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/chatscreen/ChatMessageEditor.kt
@@ -3,8 +3,10 @@ package dev.baseio.discordjetpackcompose.ui.routes.dashboard.main.chatscreen
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
@@ -16,8 +18,10 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.ArrowForwardIos
import androidx.compose.material.icons.filled.CardGiftcard
+import androidx.compose.material.icons.filled.Clear
import androidx.compose.material.icons.filled.EmojiEmotions
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@@ -32,7 +36,9 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
+import dev.baseio.discordjetpackcompose.custom.MentionsPatterns
import dev.baseio.discordjetpackcompose.custom.MentionsTextField
+import dev.baseio.discordjetpackcompose.custom.extractSpans
import dev.baseio.discordjetpackcompose.ui.theme.DiscordColorProvider
import dev.baseio.discordjetpackcompose.ui.theme.MessageTypography
import dev.baseio.discordjetpackcompose.ui.utils.Drawables
@@ -42,18 +48,22 @@ import dev.baseio.discordjetpackcompose.viewmodels.ChatScreenViewModel
fun ChatMessageEditor(
modifier: Modifier = Modifier,
userName: State,
+ isReplyMode: MutableState,
viewModel: ChatScreenViewModel
) {
+ // UiState of the ChatScreen
+ val uiState by viewModel.uiState.collectAsState()
+
var mentionText by remember {
mutableStateOf(TextFieldValue())
}
- val messageText by viewModel.message.collectAsState()
- var showExtraButtons by remember { mutableStateOf(value = messageText.isEmpty()) }
+
+ var showExtraButtons by remember { mutableStateOf(value = uiState.message.isEmpty()) }
Row(
modifier = modifier
- .height(48.dp)
- .padding(start = 8.dp, end = 8.dp),
+ .height(56.dp)
+ .padding(start = 8.dp, top = 8.dp, end = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
AnimatedVisibility(visible = showExtraButtons.not()) {
@@ -75,25 +85,45 @@ fun ChatMessageEditor(
MessageEditorBar(
modifier = Modifier
.weight(2f),
- search = messageText,
+ search = uiState.message,
userName = userName,
mentionText = mentionText,
onValueChange = {
mentionText = it
showExtraButtons = false
- viewModel.message.value = it.text
+ viewModel.updateMessage(it.text)
}
)
- AnimatedVisibility(visible = messageText.isNotEmpty()) {
+ AnimatedVisibility(visible = uiState.message.isNotEmpty()) {
SendMessageButton(
modifier = Modifier
.padding(start = 8.dp)
.weight(1f),
- viewModel = viewModel,
- message = mentionText.text,
+ messageToSend = mentionText.text,
onSent = {
+ var url: String? = null
+ val linksList =
+ extractSpans(
+ text = mentionText.text,
+ patterns = listOf(
+ MentionsPatterns.urlPattern,
+ MentionsPatterns.hashTagPattern,
+ MentionsPatterns.mentionTagPattern
+ )
+ )
+ if (linksList.isNotEmpty() && linksList.first().tag == MentionsPatterns.URL_TAG) {
+ url = linksList.first().spanText
+ }
+ viewModel.sendMessage(
+ messageToSend = mentionText.text,
+ messageToReply = uiState.messageAction,
+ isReply = isReplyMode.value,
+ url = url
+ )
mentionText = TextFieldValue()
+ isReplyMode.value = false
+ viewModel.resetMessageAction()
}
)
}
@@ -164,8 +194,7 @@ private fun MessageEditor(
@Composable
private fun SendMessageButton(
modifier: Modifier = Modifier,
- viewModel: ChatScreenViewModel,
- message: String,
+ messageToSend: String,
onSent: () -> Unit
) {
IconButton(
@@ -176,15 +205,14 @@ private fun SendMessageButton(
shape = CircleShape
),
onClick = {
- viewModel.sendMessage(message)
onSent()
},
- enabled = message.isNotEmpty()
+ enabled = messageToSend.isNotEmpty()
) {
Icon(
painterResource(id = Drawables.ic_send_rounded),
contentDescription = null,
- tint = DiscordColorProvider.colors.brand
+ tint = Color.White
)
}
}
@@ -299,4 +327,36 @@ fun ChatPlaceHolder(
innerTextField()
}
}
+}
+
+@Composable
+fun ReplyBanner(
+ modifier: Modifier = Modifier,
+ userName: State,
+ onCloseClicked: () -> Unit
+) {
+ Row(
+ modifier = modifier
+ .fillMaxWidth()
+ .padding(0.dp)
+ .background(DiscordColorProvider.colors.chatEditor),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Start
+ ) {
+ Icon(
+ modifier = Modifier
+ .padding(8.dp)
+ .clickable { onCloseClicked() },
+ imageVector = Icons.Default.Clear,
+ contentDescription = null,
+ tint = Color(0xFFbabbbf)
+ )
+ Text(
+ modifier = Modifier.padding(8.dp),
+ text = "Replying to ${userName.value}",
+ style = MessageTypography.subtitle2.copy(
+ color = DiscordColorProvider.colors.textSecondary
+ )
+ )
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/chatscreen/ChatMessageItem.kt b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/chatscreen/ChatMessageItem.kt
index 5ec3f2c..78f5aba 100644
--- a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/chatscreen/ChatMessageItem.kt
+++ b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/chatscreen/ChatMessageItem.kt
@@ -1,48 +1,206 @@
package dev.baseio.discordjetpackcompose.ui.routes.dashboard.main.chatscreen
import android.annotation.SuppressLint
+import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.gestures.detectTapGestures
+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.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.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi
-import androidx.compose.material.ListItem
+import androidx.compose.material.ModalBottomSheetState
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
+import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalUriHandler
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import coil.compose.rememberAsyncImagePainter
import coil.request.ImageRequest
import coil.transform.RoundedCornersTransformation
+import dev.baseio.discordjetpackcompose.custom.MentionsPatterns.URL_TAG
+import dev.baseio.discordjetpackcompose.custom.MentionsPatterns.hashTagPattern
+import dev.baseio.discordjetpackcompose.custom.MentionsPatterns.mentionTagPattern
+import dev.baseio.discordjetpackcompose.custom.MentionsPatterns.urlPattern
+import dev.baseio.discordjetpackcompose.custom.extractSpans
import dev.baseio.discordjetpackcompose.entities.message.DiscordMessageEntity
import dev.baseio.discordjetpackcompose.ui.theme.DiscordColorProvider
import dev.baseio.discordjetpackcompose.ui.theme.MessageTypography
+import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.util.Calendar
-@OptIn(ExperimentalMaterialApi::class)
+@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class)
@Composable
-fun ChatMessageItem(message: DiscordMessageEntity) {
- ListItem(
- modifier = Modifier.padding(0.dp),
- icon = {
- ImageBox(
- Modifier.size(48.dp),
- imageUrl = "https://images.unsplash.com/photo-1464863979621-258859e62245?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=686&q=80"
+fun ChatMessageItem(
+ message: DiscordMessageEntity,
+ position: Int,
+ onItemLongPressed: (Int) -> Unit,
+ bottomSheetState: ModalBottomSheetState
+) {
+
+ val coroutineScope = rememberCoroutineScope()
+ Column(
+ modifier = Modifier.combinedClickable(
+ onClick = {},
+ onLongClick = {
+ coroutineScope.launch {
+ onItemLongPressed(position)
+ bottomSheetState.show()
+ }
+ }
+ )
+ ) {
+ if (message.replyToMessage.isNotEmpty()) {
+ MessageItemReplyHeader(
+ replyToMessage = message.replyToMessage
)
- },
- text = {
+ }
+ ChatListItem(
+ modifier = Modifier
+ .padding(0.dp),
+ imageSize = 55.dp,
+ message = message
+ )
+ if (message.metaImageUrl.isEmpty().not()
+ && message.metaTitle.isEmpty().not()
+ && message.metaTitle.isEmpty().not()
+ ) {
+ UrlPreviewItem(
+ url = message.metaUrl,
+ imageUrl = message.metaImageUrl,
+ title = message.metaTitle,
+ desc = message.metaDesc
+ )
+ }
+ }
+}
+
+@Composable
+fun ChatListItem(
+ modifier: Modifier,
+ imageSize: Dp,
+ message: DiscordMessageEntity
+) {
+ Row(
+ modifier = modifier
+ .fillMaxWidth()
+ .padding(end = 16.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ ImageBox(
+ Modifier
+ .size(imageSize)
+ .padding(8.dp),
+ imageUrl = "https://images.unsplash.com/photo-1464863979621-258859e62245?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=686&q=80"
+ )
+ Column(
+ horizontalAlignment = Alignment.Start,
+ modifier = Modifier.padding(start = 8.dp)
+ ) {
ChatTitle(message)
- },
- secondaryText = {
- ChatBody(message)
+ Spacer(modifier = Modifier.height(4.dp))
+ ChatBody(message = message)
}
- )
+ }
+}
+
+@Composable
+fun UrlPreviewItem(
+ url: String?,
+ imageUrl: String?,
+ title: String?,
+ desc: String?
+) {
+ val uriHandler = LocalUriHandler.current
+ Box(
+ modifier = Modifier
+ .padding(top = 2.dp, start = 64.dp, end = 16.dp)
+ .clickable { uriHandler.openUri(url ?: "https://www.google.com") }
+ .clip(
+ RoundedCornerShape(4.dp)
+ )
+ .background(
+ color = DiscordColorProvider.colors.appBarColor.copy(alpha = 0.8f)
+ )
+ ) {
+ Row(
+ modifier = Modifier
+ .padding(4.dp)
+ .fillMaxSize(),
+ verticalAlignment = Alignment.Top
+ ) {
+ MetadataTitleAndDesc(
+ title = title ?: "",
+ desc = desc ?: "",
+ modifier = Modifier.weight(4f)
+ )
+ imageUrl?.let { safeImageUrl ->
+ ImageBox(
+ Modifier
+ .height(56.dp)
+ .padding(8.dp)
+ .weight(1f),
+ imageUrl = safeImageUrl
+ )
+ }
+ }
+ }
+}
+
+@Composable
+fun MetadataTitleAndDesc(
+ title: String,
+ desc: String,
+ modifier: Modifier
+) {
+ Column(
+ modifier = modifier
+ ) {
+ Text(
+ text = title,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ style = MessageTypography.subtitle2.copy(
+ color = DiscordColorProvider.colors.linkColor,
+ fontWeight = FontWeight.SemiBold
+ ),
+ modifier = Modifier.padding(4.dp)
+ )
+ Text(
+ text = desc,
+ maxLines = 10,
+ overflow = TextOverflow.Ellipsis,
+ style = MessageTypography.subtitle2.copy(
+ color = DiscordColorProvider.colors.textSecondary
+ ),
+ modifier = Modifier.padding(4.dp)
+ )
+ }
}
@Composable
@@ -56,8 +214,7 @@ fun ImageBox(
}.build()
)
Image(
- modifier = Modifier
- .size(40.dp)
+ modifier = modifier
.clip(CircleShape),
painter = painter,
contentDescription = null
@@ -65,13 +222,55 @@ fun ImageBox(
}
@Composable
-fun ChatBody(message: DiscordMessageEntity) {
+fun ChatBody(
+ message: DiscordMessageEntity
+) {
Column {
+ val linksList = extractSpans(
+ message.message, listOf(urlPattern, hashTagPattern, mentionTagPattern)
+ )
+ val uriHandler = LocalUriHandler.current
+ val layoutResult = remember { mutableStateOf(null) }
+
+ val annotatedString = buildAnnotatedString {
+ append(message.message)
+ linksList.forEach {
+ addStyle(
+ style = SpanStyle(
+ color = DiscordColorProvider.colors.linkColor,
+ textDecoration = TextDecoration.Underline
+ ),
+ start = it.start,
+ end = it.end
+ )
+ addStringAnnotation(
+ tag = it.tag,
+ annotation = it.spanText,
+ start = it.start,
+ end = it.end
+ )
+ }
+ }
Text(
- message.message,
+ text = annotatedString,
style = MessageTypography.subtitle2.copy(
color = DiscordColorProvider.colors.textSecondary
- )
+ ),
+ modifier = Modifier.pointerInput(Unit) {
+ detectTapGestures { offsetPosition ->
+ layoutResult.value?.let {
+ val position = it.getOffsetForPosition(offsetPosition)
+ annotatedString.getStringAnnotations(position, position).firstOrNull()
+ ?.let { result ->
+ when (result.tag) {
+ URL_TAG -> {
+ uriHandler.openUri(result.item)
+ }
+ }
+ }
+ }
+ }
+ },
)
}
}
@@ -84,13 +283,14 @@ fun ChatTitle(message: DiscordMessageEntity) {
style = MessageTypography.h1.copy(
color = DiscordColorProvider.colors.textPrimary
),
- modifier = Modifier.padding(end = 8.dp)
+ modifier = Modifier.padding(top = 0.dp, bottom = 0.dp, end = 8.dp)
)
Text(
message.createdDate.calendar().formattedFullDateTime(),
style = MessageTypography.overline.copy(
color = DiscordColorProvider.colors.textSecondary.copy(alpha = 0.8f)
- )
+ ),
+ modifier = Modifier.padding(top = 0.dp, bottom = 0.dp)
)
}
}
diff --git a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/chatscreen/ChatMessages.kt b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/chatscreen/ChatMessages.kt
index 30bca4c..b708057 100644
--- a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/chatscreen/ChatMessages.kt
+++ b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/chatscreen/ChatMessages.kt
@@ -6,6 +6,8 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.Divider
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.ModalBottomSheetState
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
@@ -23,41 +25,59 @@ import dev.baseio.discordjetpackcompose.ui.theme.DiscordColorProvider
import dev.baseio.discordjetpackcompose.ui.theme.MessageTypography
import dev.baseio.discordjetpackcompose.viewmodels.ChatScreenViewModel
-@OptIn(ExperimentalFoundationApi::class)
+@OptIn(
+ ExperimentalFoundationApi::class,
+ ExperimentalMaterialApi::class
+)
@Composable
fun ChatMessages(
modifier: Modifier = Modifier,
userName: State,
+ bottomSheetState: ModalBottomSheetState,
viewModel: ChatScreenViewModel
) {
val flowState by viewModel.chatMessagesFlow.collectAsState()
val messages = flowState?.collectAsLazyPagingItems()
val listState = rememberLazyListState()
- LazyColumn(state = listState, reverseLayout = true, modifier = modifier.padding(top = 24.dp)) {
+
+ LazyColumn(
+ state = listState,
+ reverseLayout = true,
+ modifier = modifier.padding(top = 24.dp)
+ ) {
var lastDrawnMessage: String? = null
+
messages?.let { safeMessages ->
for (messageIndex in 0 until safeMessages.itemCount) {
- val message = safeMessages.peek(messageIndex)!!
- item {
- ChatMessageItem(
- message = message
- )
- }
+ val message = safeMessages.peek(messageIndex)
- // Add chat date header
- lastDrawnMessage = message.createdDate.calendar().formattedFullDate()
- if (!isLastMessage(messageIndex, messages)) {
- val nextMessageMonth =
- messages.peek(messageIndex + 1)?.createdDate?.calendar()?.formattedFullDate()
- if (nextMessageMonth != lastDrawnMessage) {
+ message?.let { safeMessage ->
+ item {
+ ChatMessageItem(
+ message = safeMessage,
+ position = messageIndex,
+ onItemLongPressed = {
+ viewModel.updateMessageAction(safeMessage.message)
+ },
+ bottomSheetState = bottomSheetState
+ )
+ }
+
+ // Add chat date header
+ lastDrawnMessage = safeMessage.createdDate.calendar().formattedFullDate()
+ if (!isLastMessage(messageIndex, safeMessages)) {
+ val nextMessageMonth =
+ safeMessages.peek(messageIndex + 1)?.createdDate?.calendar()?.formattedFullDate()
+ if (nextMessageMonth != lastDrawnMessage) {
+ stickyHeader {
+ ChatHeader(safeMessage.createdDate)
+ }
+ }
+ } else {
stickyHeader {
- ChatHeader(message.createdDate)
+ ChatHeader(safeMessage.createdDate)
}
}
- } else {
- stickyHeader {
- ChatHeader(message.createdDate)
- }
}
}
}
@@ -78,7 +98,10 @@ private fun isLastMessage(
@Composable
private fun ChatHeader(createdDate: Long) {
- Row(Modifier.padding(start = 8.dp, end = 8.dp), verticalAlignment = Alignment.CenterVertically) {
+ Row(
+ Modifier.padding(start = 8.dp, end = 8.dp, top = 16.dp, bottom = 16.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
Divider(
modifier = Modifier.weight(2f),
color = DiscordColorProvider.colors.textSecondary.copy(alpha = 0.8f),
diff --git a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/chatscreen/ChatReply.kt b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/chatscreen/ChatReply.kt
new file mode 100644
index 0000000..717a9bc
--- /dev/null
+++ b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/chatscreen/ChatReply.kt
@@ -0,0 +1,140 @@
+package dev.baseio.discordjetpackcompose.ui.routes.dashboard.main.chatscreen
+
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.text.InlineTextContent
+import androidx.compose.foundation.text.appendInlineContent
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.StrokeCap
+import androidx.compose.ui.graphics.StrokeJoin
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.text.Placeholder
+import androidx.compose.ui.text.PlaceholderVerticalAlign
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import dev.baseio.discordjetpackcompose.ui.theme.DiscordColorProvider
+import dev.baseio.discordjetpackcompose.ui.theme.MessageTypography
+
+@Composable
+fun MessageItemReplyHeader(
+ replyToMessage: String
+) {
+ ChatListReplyItem(
+ modifier = Modifier
+ .padding(0.dp),
+ replyToMessage = replyToMessage
+ )
+}
+
+@Composable
+fun ReplyLine() {
+ Canvas(
+ modifier = Modifier
+ .size(40.dp)
+ .padding(0.dp)
+ ) {
+ val width = size.width
+ val height = size.height
+ val path = Path().apply {
+ moveTo(
+ x = width.times(1.5f),
+ y = height.times(.5f)
+ )
+ cubicTo(
+ x1 = width.times(0.55f),
+ y1 = height.times(.5f),
+
+ x2 = width.times(.7f),
+ y2 = height.times(.5f),
+
+ x3 = width.times(.7f),
+ y3 = height.times(1.1f)
+ )
+ }
+ drawPath(
+ path = path,
+ color = Color(0xFF838383),
+ style = Stroke(
+ width = 6f,
+ cap = StrokeCap.Round,
+ join = StrokeJoin.Miter
+ )
+ )
+ }
+}
+
+@Composable
+fun ChatReplyTitle(
+ toUserName: String,
+ message: String
+) {
+ val annotatedString = buildAnnotatedString {
+ appendInlineContent(id = "imageId")
+ pushStyle(
+ SpanStyle(
+ fontWeight = FontWeight.Medium,
+ color = DiscordColorProvider.colors.textPrimary
+ )
+ )
+ append(" $toUserName ")
+ pushStyle(
+ SpanStyle(
+ fontWeight = FontWeight.Normal,
+ color = DiscordColorProvider.colors.textSecondary.copy(alpha = 0.8f)
+ )
+ )
+ append(message)
+ }
+ val inlineContentMap = mapOf(
+ "imageId" to InlineTextContent(
+ Placeholder(20.sp, 20.sp, PlaceholderVerticalAlign.TextCenter)
+ ) {
+ ImageBox(
+ modifier = Modifier.size(32.dp),
+ imageUrl = "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=687&q=80"
+ )
+ }
+ )
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Text(
+ text = annotatedString,
+ maxLines = 1,
+ overflow = TextOverflow.Clip,
+ inlineContent = inlineContentMap,
+ style = MessageTypography.h1.copy(
+ color = DiscordColorProvider.colors.textPrimary,
+ fontSize = 14.sp
+ ),
+ modifier = Modifier.padding(start = 24.dp, end = 8.dp)
+ )
+ }
+}
+
+@Composable
+fun ChatListReplyItem(
+ modifier: Modifier,
+ replyToMessage: String
+) {
+ Row(
+ modifier = modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ ReplyLine()
+ ChatReplyTitle(
+ toUserName = "Person",
+ message = replyToMessage
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/chatscreen/ChatScreen.kt b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/chatscreen/ChatScreen.kt
index cfb4b84..a9e2b02 100644
--- a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/chatscreen/ChatScreen.kt
+++ b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/chatscreen/ChatScreen.kt
@@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Icon
+import androidx.compose.material.ModalBottomSheetValue.Hidden
import androidx.compose.material.SwipeableState
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons.Filled
@@ -18,10 +19,13 @@ import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.People
import androidx.compose.material.icons.filled.PhoneInTalk
import androidx.compose.material.icons.filled.Videocam
+import androidx.compose.material.rememberModalBottomSheetState
import androidx.compose.material.rememberScaffoldState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.State
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -46,19 +50,22 @@ import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun ChatScreen(
- modifier: Modifier = Modifier,
- composeNavigator: ComposeNavigator,
- viewModel: ChatScreenViewModel = hiltViewModel(),
- focusOpacity: Float,
- userName: State,
- isOnline: State,
- swipeableState: SwipeableState
+ modifier: Modifier = Modifier,
+ composeNavigator: ComposeNavigator,
+ viewModel: ChatScreenViewModel = hiltViewModel(),
+ focusOpacity: Float,
+ userName: State,
+ isOnline: State,
+ swipeableState: SwipeableState
) {
val scaffoldState = rememberScaffoldState()
+ val bottomSheetState = rememberModalBottomSheetState(initialValue = Hidden)
+ val isReplyMode = remember { mutableStateOf(false) }
+ val coroutineScope = rememberCoroutineScope()
- SideEffect {
- viewModel.fetchMessages()
- }
+ SideEffect {
+ viewModel.fetchMessages()
+ }
DiscordScaffold(
modifier = Modifier.clip(RoundedCornerShape(2)),
@@ -82,81 +89,98 @@ fun ChatScreen(
ChatScreenContent(
modifier = Modifier,
viewModel = viewModel,
+ sheetState = bottomSheetState,
+ isReplyMode = isReplyMode,
userName = userName
)
}
}
+ MessageActionsBottomSheet(
+ sheetState = bottomSheetState,
+ replyAction = {
+ isReplyMode.value = true
+ coroutineScope.launch {
+ bottomSheetState.hide()
+ }
+ }
+ )
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun ChatScreenAppBar(
- name: String,
- isOnline: Boolean,
- swipeableState: SwipeableState
+ name: String,
+ isOnline: Boolean,
+ swipeableState: SwipeableState
) {
- val coroutineScope = rememberCoroutineScope()
- DiscordAppBar(
- navigationIcon = {
- CountIndicator(
- count = 142,
- forceCircleShape = false,
- modifier = Modifier.padding(8.dp)
- ) {
- Icon(
- imageVector = Filled.Menu,
- contentDescription = stringResource(string.menu),
- modifier = Modifier
- .padding(start = 8.dp)
- .clickable {
- coroutineScope.launch {
- swipeableState.animateTo(CenterScreenState.RIGHT_ANCHORED)
- }
- },
- )
- }
- },
- title = {
- Row(verticalAlignment = Alignment.CenterVertically) {
- Icon(
- imageVector = Filled.AlternateEmail,
- contentDescription = null,
- tint = Color.Gray
- )
- Text(
- modifier = Modifier
- .padding(start = 8.dp, end = 8.dp)
- .wrapContentWidth(),
- maxLines = 1,
- style = MessageTypography.h2,
- overflow = TextOverflow.Ellipsis,
- text = name,
- color = DiscordColorProvider.colors.onSurface
- )
- OnlineIndicator(
- modifier = Modifier,
- isOnline = isOnline
- )
- }
- },
- actions = {
- Icon(
- imageVector = Filled.PhoneInTalk,
- contentDescription = null,
- modifier = Modifier.padding(end = 16.dp),
- )
- Icon(
- imageVector = Filled.Videocam,
- contentDescription = null,
- modifier = Modifier.padding(end = 16.dp),
- )
- Icon(
- imageVector = Filled.People,
- contentDescription = null,
- modifier = Modifier.padding(end = 16.dp),
- )
- },
- backgroundColor = DiscordColorProvider.colors.chatTopBar,
- elevation = 0.dp
- )
+ val coroutineScope = rememberCoroutineScope()
+ DiscordAppBar(
+ navigationIcon = {
+ CountIndicator(
+ count = 142,
+ forceCircleShape = false,
+ modifier = Modifier.padding(8.dp)
+ ) {
+ Icon(
+ imageVector = Filled.Menu,
+ contentDescription = stringResource(string.menu),
+ modifier = Modifier
+ .padding(start = 8.dp)
+ .clickable {
+ coroutineScope.launch {
+ swipeableState.animateTo(CenterScreenState.RIGHT_ANCHORED)
+ }
+ },
+ )
+ }
+ },
+ title = {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Icon(
+ imageVector = Filled.AlternateEmail,
+ contentDescription = null,
+ tint = Color.Gray
+ )
+ Text(
+ modifier = Modifier
+ .padding(start = 8.dp, end = 8.dp)
+ .wrapContentWidth(),
+ maxLines = 1,
+ style = MessageTypography.h2,
+ overflow = TextOverflow.Ellipsis,
+ text = name,
+ color = DiscordColorProvider.colors.onSurface
+ )
+ OnlineIndicator(
+ modifier = Modifier,
+ isOnline = isOnline
+ )
+ }
+ },
+ actions = {
+ Icon(
+ imageVector = Filled.PhoneInTalk,
+ contentDescription = null,
+ modifier = Modifier.padding(end = 16.dp),
+ )
+ Icon(
+ imageVector = Filled.Videocam,
+ contentDescription = null,
+ modifier = Modifier.padding(end = 16.dp),
+ )
+ Icon(
+ imageVector = Filled.People,
+ contentDescription = null,
+ modifier = Modifier
+ .padding(end = 16.dp)
+ .clickable {
+ coroutineScope.launch {
+ swipeableState.animateTo(CenterScreenState.LEFT_ANCHORED)
+ }
+ },
+ )
+ },
+ backgroundColor = DiscordColorProvider.colors.chatTopBar,
+ elevation = 0.dp
+ )
}
\ No newline at end of file
diff --git a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/chatscreen/ChatScreenContent.kt b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/chatscreen/ChatScreenContent.kt
index dd0e2ab..f2aad2f 100644
--- a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/chatscreen/ChatScreenContent.kt
+++ b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/chatscreen/ChatScreenContent.kt
@@ -2,7 +2,10 @@ package dev.baseio.discordjetpackcompose.ui.routes.dashboard.main.chatscreen
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.ModalBottomSheetState
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.layoutId
@@ -13,10 +16,13 @@ import com.google.accompanist.insets.navigationBarsWithImePadding
import com.google.accompanist.insets.statusBarsPadding
import dev.baseio.discordjetpackcompose.viewmodels.ChatScreenViewModel
+@OptIn(ExperimentalMaterialApi::class)
@Composable
fun ChatScreenContent(
modifier: Modifier = Modifier,
viewModel: ChatScreenViewModel,
+ sheetState: ModalBottomSheetState,
+ isReplyMode: MutableState,
userName: State
) {
BoxWithConstraints(
@@ -31,11 +37,22 @@ fun ChatScreenContent(
.layoutId("chatMessages")
.fillMaxSize(),
userName = userName,
- viewModel = viewModel
+ viewModel = viewModel,
+ bottomSheetState = sheetState
)
+ if (isReplyMode.value) {
+ ReplyBanner(
+ modifier = Modifier.layoutId("chatReplyBar"),
+ userName = userName,
+ onCloseClicked = {
+ isReplyMode.value = false
+ }
+ )
+ }
ChatMessageEditor(
modifier = Modifier.layoutId("chatMessageEditor"),
userName = userName,
+ isReplyMode = isReplyMode,
viewModel = viewModel
)
}
@@ -45,6 +62,7 @@ fun ChatScreenContent(
private fun decoupledConstraints(): ConstraintSet {
return ConstraintSet {
val chatMessages = createRefFor("chatMessages")
+ val chatReplyBar = createRefFor("chatReplyBar")
val chatMessageEditor = createRefFor("chatMessageEditor")
constrain(chatMessages) {
@@ -53,6 +71,11 @@ private fun decoupledConstraints(): ConstraintSet {
end.linkTo(parent.end)
bottom.linkTo(chatMessageEditor.top, margin = 64.dp)
}
+ constrain(chatReplyBar) {
+ start.linkTo(parent.start)
+ end.linkTo(parent.end)
+ bottom.linkTo(chatMessageEditor.top, margin = 0.dp)
+ }
constrain(chatMessageEditor) {
start.linkTo(parent.start)
end.linkTo(parent.end)
diff --git a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/dasboard/DashboardScreen.kt b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/dasboard/DashboardScreen.kt
index 7585b11..ee2db8f 100644
--- a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/dasboard/DashboardScreen.kt
+++ b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/dasboard/DashboardScreen.kt
@@ -183,6 +183,7 @@ fun DashboardScreen(
unreadCount = null,
onClick = {
selectedBottomBarItem = DashboardBottomBarItemType.Profile
+ navController.navigate(DiscordScreen.UserSettings.route)
},
type = DashboardBottomBarItemType.Profile
),
diff --git a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/userSettings/UserSettings.kt b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/userSettings/UserSettings.kt
new file mode 100644
index 0000000..391aa39
--- /dev/null
+++ b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/userSettings/UserSettings.kt
@@ -0,0 +1,89 @@
+package dev.baseio.discordjetpackcompose.ui.routes.dashboard.userSettings
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material.Text
+import androidx.compose.material.rememberScaffoldState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.google.accompanist.systemuicontroller.rememberSystemUiController
+import dev.baseio.discordjetpackcompose.R.string
+import dev.baseio.discordjetpackcompose.navigator.ComposeNavigator
+import dev.baseio.discordjetpackcompose.ui.components.DiscordAppBar
+import dev.baseio.discordjetpackcompose.ui.components.DiscordScaffold
+import dev.baseio.discordjetpackcompose.ui.routes.dashboard.userSettings.components.UserSettingsAppBar
+import dev.baseio.discordjetpackcompose.ui.routes.dashboard.userSettings.components.getAppInfoSettings
+import dev.baseio.discordjetpackcompose.ui.routes.dashboard.userSettings.components.getAppSettings
+import dev.baseio.discordjetpackcompose.ui.routes.dashboard.userSettings.components.getNitroSettingsList
+import dev.baseio.discordjetpackcompose.ui.routes.dashboard.userSettings.components.getUserSettingsList
+import dev.baseio.discordjetpackcompose.ui.theme.DiscordColorProvider
+import dev.baseio.discordjetpackcompose.ui.theme.Typography
+import dev.baseio.discordjetpackcompose.ui.theme.user_settings_bg
+import dev.baseio.discordjetpackcompose.utils.Constants
+
+@Composable
+fun UserSettings(
+ composeNavigator: ComposeNavigator,
+ userProfileImage: String = Constants.MMLogoUrl,
+) {
+
+ val scaffoldState = rememberScaffoldState()
+ val sysUiController = rememberSystemUiController()
+ val colors = DiscordColorProvider.colors
+ val context = LocalContext.current
+
+ SideEffect {
+ sysUiController.setSystemBarsColor(color = colors.discordBackgroundOne)
+ sysUiController.setNavigationBarColor(color = colors.discordBackgroundOne)
+ }
+
+ DiscordScaffold(
+ scaffoldState = scaffoldState,
+ topAppBar = {
+ DiscordAppBar(
+ title = {
+ Text(
+ text = stringResource(string.user_settings_title),
+ style = Typography.h3.copy(
+ fontWeight = FontWeight.Bold,
+ fontSize = 20.sp
+ ),
+ textAlign = TextAlign.Start,
+ )
+ },
+ actions = {
+ UserSettingsAppBar(composeNavigator)
+ },
+ backgroundColor = user_settings_bg,
+ elevation = 0.dp
+ )
+ },
+ navigator = composeNavigator
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(user_settings_bg),
+ verticalArrangement = Arrangement.Top,
+ horizontalAlignment = Alignment.Start
+ ) {
+ UserSettingsList(
+ userSettings = getUserSettingsList(context),
+ nitroSettings = getNitroSettingsList(context),
+ appSettings = getAppSettings(context),
+ appInfo = getAppInfoSettings(context),
+ profileImageUrl = userProfileImage
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/userSettings/UserSettingsList.kt b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/userSettings/UserSettingsList.kt
new file mode 100644
index 0000000..efa300f
--- /dev/null
+++ b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/userSettings/UserSettingsList.kt
@@ -0,0 +1,85 @@
+package dev.baseio.discordjetpackcompose.ui.routes.dashboard.userSettings
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.material.TabRowDefaults.Divider
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import com.google.accompanist.insets.navigationBarsPadding
+import dev.baseio.discordjetpackcompose.R.string
+import dev.baseio.discordjetpackcompose.ui.routes.dashboard.userSettings.components.GetSettingsSubtitle
+import dev.baseio.discordjetpackcompose.ui.routes.dashboard.userSettings.components.GetTopComponent
+import dev.baseio.discordjetpackcompose.ui.routes.dashboard.userSettings.components.models.SettingsEntity
+import dev.baseio.discordjetpackcompose.ui.theme.DiscordColorProvider.colors
+
+@Composable
+fun UserSettingsList(
+ userSettings: List,
+ nitroSettings: List,
+ appSettings: List,
+ appInfo: List,
+ profileImageUrl: String,
+ username: String = "mutual",
+ discordTag: String = "#8976"
+) {
+ val lazyListState = rememberLazyListState()
+
+ LazyColumn(state = lazyListState, modifier = Modifier.background(colors.settingsBackground)) {
+ item {
+ GetTopComponent(lazyListState, profileImageUrl, username, discordTag)
+ }
+
+ item {
+ GetSettingsSubtitle(title = stringResource(string.settings_user_settings))
+ }
+
+ items(userSettings.size) { index ->
+ UserSettingsListItem(settingsEntity = userSettings[index],
+ onItemClick = {})
+ }
+
+ item {
+ Divider(color = Color.DarkGray, thickness = 1.dp, modifier = Modifier.padding(top = 8.dp))
+ GetSettingsSubtitle(title = stringResource(string.settings_nitro_settings))
+ }
+
+ items(nitroSettings.size) { index ->
+ UserSettingsListItem(settingsEntity = nitroSettings[index],
+ onItemClick = {})
+ }
+
+ item {
+ Divider(color = Color.DarkGray, thickness = 1.dp, modifier = Modifier.padding(top = 8.dp))
+ GetSettingsSubtitle(title = stringResource(string.settings_app_settings))
+ }
+
+ items(appSettings.size) { index ->
+ UserSettingsListItem(settingsEntity = appSettings[index],
+ onItemClick = {})
+ }
+
+ item {
+ Divider(color = Color.DarkGray, thickness = 1.dp, modifier = Modifier.padding(top = 8.dp))
+ GetSettingsSubtitle(title = stringResource(string.settings_app_info))
+ }
+
+ items(appInfo.size) { index ->
+ UserSettingsListItem(settingsEntity = appInfo[index],
+ onItemClick = {})
+ }
+
+ item {
+ Spacer(
+ modifier = Modifier
+ .navigationBarsPadding()
+ .padding(vertical = 32.dp)
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/userSettings/UserSettingsListItem.kt b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/userSettings/UserSettingsListItem.kt
new file mode 100644
index 0000000..7c1da58
--- /dev/null
+++ b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/userSettings/UserSettingsListItem.kt
@@ -0,0 +1,71 @@
+package dev.baseio.discordjetpackcompose.ui.routes.dashboard.userSettings
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.Icon
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import dev.baseio.discordjetpackcompose.R
+import dev.baseio.discordjetpackcompose.ui.routes.dashboard.userSettings.components.models.SettingsEntity
+import dev.baseio.discordjetpackcompose.ui.theme.DirectMessageListTypography
+import dev.baseio.discordjetpackcompose.ui.theme.DiscordColorProvider.colors
+import dev.baseio.discordjetpackcompose.ui.theme.discord_settings_icon
+import dev.baseio.discordjetpackcompose.ui.theme.user_settings_text
+import dev.baseio.discordjetpackcompose.ui.utils.clickableWithRipple
+
+@Composable
+fun UserSettingsListItem(
+ settingsEntity: SettingsEntity,
+ onItemClick: () -> Unit
+) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(colors.settingsBackground)
+ .clickableWithRipple(onClick = onItemClick)
+ .padding(top = 16.dp, bottom = 16.dp, end = 16.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Start
+ ) {
+ Icon(
+ painter = painterResource(id = settingsEntity.icon), contentDescription = null,
+ modifier = Modifier.padding(start = 16.dp),
+ tint = discord_settings_icon,
+ )
+ Text(
+ text = settingsEntity.title, style = DirectMessageListTypography.h6,
+ maxLines = 1,
+ color = user_settings_text,
+ modifier = Modifier.padding(start = 24.dp)
+ )
+ settingsEntity.currentStatus?.let { status ->
+ Spacer(modifier = Modifier.weight(1f))
+ Icon(
+ painter = painterResource(id = R.drawable.ic_baseline_circle_24),
+ contentDescription = null,
+ modifier = Modifier
+ .size(14.dp)
+ .align(Alignment.CenterVertically),
+ tint = Color(0xFF3DA45C)
+ )
+ Text(
+ text = status,
+ textAlign = TextAlign.End,
+ modifier = Modifier
+ .padding(start = 14.dp)
+ .align(Alignment.CenterVertically)
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/userSettings/components/SettingsAppBar.kt b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/userSettings/components/SettingsAppBar.kt
new file mode 100644
index 0000000..b9a1ce5
--- /dev/null
+++ b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/userSettings/components/SettingsAppBar.kt
@@ -0,0 +1,55 @@
+package dev.baseio.discordjetpackcompose.ui.routes.dashboard.userSettings.components
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.DropdownMenu
+import androidx.compose.material.DropdownMenuItem
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.Text
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.Icons.Filled
+import androidx.compose.material.icons.filled.Logout
+import androidx.compose.material.icons.filled.MoreVert
+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.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import dev.baseio.discordjetpackcompose.R.string
+import dev.baseio.discordjetpackcompose.navigator.ComposeNavigator
+import dev.baseio.discordjetpackcompose.navigator.DiscordScreen.Login
+
+@Composable
+fun UserSettingsAppBar(composeNavigator: ComposeNavigator) {
+ var displayMenu by remember { mutableStateOf(false) }
+ IconButton(
+ onClick = { composeNavigator.navigateAndClearBackStack(Login.name) }) {
+ Icon(
+ imageVector = Filled.Logout,
+ contentDescription = stringResource(string.logout),
+ modifier = Modifier.padding(start = 8.dp),
+ )
+ }
+
+ IconButton(onClick = { displayMenu = !displayMenu }) {
+ Icon(Icons.Default.MoreVert, null)
+ }
+ DropdownMenu(
+ expanded = displayMenu,
+ onDismissRequest = { displayMenu = false },
+ modifier = Modifier.background(Color.White)
+ ) {
+ DropdownMenuItem(onClick = { }) {
+ Text(
+ text = stringResource(string.debug), color = Color.Black,
+ textAlign = TextAlign.Center
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/userSettings/components/SettingsData.kt b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/userSettings/components/SettingsData.kt
new file mode 100644
index 0000000..7d8928d
--- /dev/null
+++ b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/userSettings/components/SettingsData.kt
@@ -0,0 +1,138 @@
+package dev.baseio.discordjetpackcompose.ui.routes.dashboard.userSettings.components
+
+import android.content.Context
+import dev.baseio.discordjetpackcompose.ui.routes.dashboard.userSettings.components.models.SettingsEntity
+import dev.baseio.discordjetpackcompose.ui.utils.Drawables
+import dev.baseio.discordjetpackcompose.ui.utils.Strings
+
+fun getUserSettingsList(context: Context): List {
+ return listOf(
+
+ SettingsEntity(
+ title = context.getString(Strings.settings_set_status),
+ icon = Drawables.ic_baseline_account_circle_24,
+ currentStatus = "Online"
+ ),
+
+ SettingsEntity(
+ title = context.getString(Strings.settings_my_account),
+ icon = Drawables.ic_baseline_account_box_24,
+ ),
+
+ SettingsEntity(
+ title = context.getString(Strings.settings_user_profile),
+ icon = Drawables.ic_baseline_edit_24,
+ ),
+
+ SettingsEntity(
+ title = context.getString(Strings.settings_privacy),
+ icon = Drawables.ic_baseline_security_24,
+ ),
+
+ SettingsEntity(
+ title = context.getString(Strings.settings_auth_apps),
+ icon = Drawables.ic_baseline_vpn_key_24,
+ ),
+
+ SettingsEntity(
+ title = context.getString(Strings.settings_connections),
+ icon = Drawables.ic_baseline_laptop_24,
+ ),
+
+ SettingsEntity(
+ title = context.getString(Strings.settings_scan_qr),
+ icon = Drawables.ic_baseline_qr_code_24,
+ )
+ )
+}
+
+fun getNitroSettingsList(context: Context): List {
+ return listOf(
+
+ SettingsEntity(
+ title = context.getString(Strings.settings_subscribe_today),
+ icon = Drawables.ic_baseline_subscriptions_24
+ ),
+
+ SettingsEntity(
+ title = context.getString(Strings.settings_boosts),
+ icon = Drawables.ic_baseline_account_box_24,
+ ),
+
+ SettingsEntity(
+ title = context.getString(Strings.settings_nitro_gifting),
+ icon = Drawables.ic_baseline_card_giftcard_24,
+ )
+ )
+}
+
+fun getAppSettings(context: Context): List {
+ return listOf(
+
+ SettingsEntity(
+ title = context.getString(Strings.settings_voice_video),
+ icon = Drawables.ic_baseline_mic_24,
+ ),
+
+ SettingsEntity(
+ title = context.getString(Strings.settings_notification),
+ icon = Drawables.ic_baseline_notification_important_24,
+ ),
+
+ SettingsEntity(
+ title = context.getString(Strings.settings_text_images),
+ icon = Drawables.ic_baseline_image_24,
+ ),
+
+ SettingsEntity(
+ title = context.getString(Strings.settings_appearance),
+ icon = Drawables.ic_baseline_color_lens_24,
+ ),
+
+ SettingsEntity(
+ title = context.getString(Strings.settings_accessibility),
+ icon = Drawables.ic_baseline_accessibility_new_24,
+ ),
+
+ SettingsEntity(
+ title = context.getString(Strings.settings_behavior),
+ icon = Drawables.ic_baseline_settings_applications_24,
+ ),
+
+ SettingsEntity(
+ title = context.getString(Strings.settings_language),
+ icon = Drawables.ic_baseline_language_24,
+ ),
+
+ SettingsEntity(
+ title = context.getString(Strings.settings_activity_status),
+ icon = Drawables.ic_baseline_vpn_key_24,
+ )
+ )
+}
+
+fun getAppInfoSettings(context: Context): List {
+ return listOf(
+
+ SettingsEntity(
+ title = context.getString(Strings.settings_change_log),
+ icon = Drawables.ic_baseline_info_24
+ ),
+
+ SettingsEntity(
+ title = context.getString(Strings.settings_support),
+ icon = Drawables.ic_baseline_contact_support_24,
+ ),
+
+ SettingsEntity(
+ title = context.getString(Strings.settings_debug_logs),
+ icon = Drawables.ic_baseline_info_24,
+ ),
+
+ SettingsEntity(
+ title = context.getString(Strings.settings_acknowledgement),
+ icon = Drawables.ic_baseline_info_24,
+ )
+
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/userSettings/components/SettingsTitle.kt b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/userSettings/components/SettingsTitle.kt
new file mode 100644
index 0000000..fc7f098
--- /dev/null
+++ b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/userSettings/components/SettingsTitle.kt
@@ -0,0 +1,36 @@
+package dev.baseio.discordjetpackcompose.ui.routes.dashboard.userSettings.components
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import dev.baseio.discordjetpackcompose.ui.theme.DiscordColorProvider
+import dev.baseio.discordjetpackcompose.ui.theme.Typography
+import dev.baseio.discordjetpackcompose.ui.theme.user_settings_text
+
+@Composable
+fun GetSettingsSubtitle(title: String) {
+ Box(
+ modifier = Modifier
+ .background(DiscordColorProvider.colors.settingsBackground)
+ .fillMaxWidth()
+ ) {
+ Text(
+ text = title,
+ style = Typography.h3.copy(
+ fontWeight = FontWeight.SemiBold,
+ fontSize = 15.sp,
+ color = user_settings_text
+ ),
+ textAlign = TextAlign.Start,
+ modifier = Modifier.padding(start = 16.dp, top = 16.dp, bottom = 16.dp)
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/userSettings/components/TopBarComponents.kt b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/userSettings/components/TopBarComponents.kt
new file mode 100644
index 0000000..c92c809
--- /dev/null
+++ b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/userSettings/components/TopBarComponents.kt
@@ -0,0 +1,130 @@
+package dev.baseio.discordjetpackcompose.ui.routes.dashboard.userSettings.components
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+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.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+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.graphicsLayer
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import coil.compose.AsyncImage
+import dev.baseio.discordjetpackcompose.R.string
+import dev.baseio.discordjetpackcompose.ui.routes.dashboard.components.OnlineIndicator
+import dev.baseio.discordjetpackcompose.ui.theme.Typography
+import dev.baseio.discordjetpackcompose.ui.theme.user_settings_bg
+import dev.baseio.discordjetpackcompose.ui.utils.rememberCoilImageRequest
+
+@Composable
+fun GetTopComponent(
+ lazyListState: LazyListState,
+ profileImageUrl: String,
+ username: String,
+ discordTag: String
+) {
+ var scrolledY = 0f
+ var previousOffset = 0
+
+ Column(
+ Modifier
+ .fillMaxWidth()
+ .background(user_settings_bg)
+ .graphicsLayer {
+ scrolledY += lazyListState.firstVisibleItemScrollOffset - previousOffset
+ translationY = scrolledY * 0.5f
+ previousOffset = lazyListState.firstVisibleItemScrollOffset
+ }, horizontalAlignment = Alignment.Start, verticalArrangement = Arrangement.Top
+ ) {
+ GetImageWithBanner(profileImageUrl)
+ GetUsername(username,discordTag)
+ Spacer(modifier = Modifier.height(16.dp))
+ }
+}
+
+@Composable
+fun GetImageWithBanner(profileImageUrl: String) {
+ Spacer(
+ modifier = Modifier
+ .height(40.dp)
+ .fillMaxWidth()
+ .background(Color(0xFF00a6d5))
+ )
+ Box(
+ Modifier
+ .fillMaxWidth()
+ .wrapContentHeight()
+ ) {
+ Spacer(
+ modifier = Modifier
+ .height(40.dp)
+ .fillMaxWidth()
+ .background(Color(0xFF00a6d5))
+ )
+ Box(
+ modifier = Modifier
+ .padding(start = 16.dp)
+ .fillMaxWidth()
+ .wrapContentHeight()
+ ) {
+ OnlineIndicator(
+ isOnline = true,
+ indicatorSize = 26.dp
+ ) {
+ AsyncImage(
+ model = rememberCoilImageRequest(data = profileImageUrl),
+ contentDescription = stringResource(string.settings_profile_image),
+ modifier = Modifier
+ .size(84.dp)
+ .clip(CircleShape)
+ .border(4.dp, user_settings_bg, CircleShape)
+ )
+ }
+ }
+ }
+}
+
+@Composable
+fun GetUsername( username: String,
+ discordTag: String) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(start = 16.dp, top = 8.dp)
+ ) {
+ Text(
+ text = username,
+ style = Typography.h3.copy(
+ fontWeight = FontWeight.Bold,
+ fontSize = 22.sp
+ ),
+ textAlign = TextAlign.Start
+ )
+ Text(
+ text = discordTag,
+ style = Typography.h3.copy(
+ fontWeight = FontWeight.SemiBold,
+ fontSize = 22.sp,
+ color = Color.Gray
+ ),
+ textAlign = TextAlign.Start
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/userSettings/components/models/SettingsEntity.kt b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/userSettings/components/models/SettingsEntity.kt
new file mode 100644
index 0000000..93c92d0
--- /dev/null
+++ b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/userSettings/components/models/SettingsEntity.kt
@@ -0,0 +1,8 @@
+package dev.baseio.discordjetpackcompose.ui.routes.dashboard.userSettings.components.models
+
+data class SettingsEntity(
+ val title: String,
+ val icon: Int,
+ val currentStatus: String? = null,
+ val statusIcon: String? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/theme/Color.kt b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/theme/Color.kt
index a167d34..4a0f227 100644
--- a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/theme/Color.kt
+++ b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/theme/Color.kt
@@ -827,4 +827,53 @@ val discord_icon_button_bg
Color(0xFF2f3238)
} else {
Color(0xFFeaeaeb)
- }
\ No newline at end of file
+ }
+
+val user_settings_text
+ @Composable get() = if (isSystemInDarkTheme()) {
+ Color(0xFFBABBBF)
+ } else {
+ Color(0xFF515963)
+ }
+
+val user_settings_bg
+ @Composable get() = if (isSystemInDarkTheme()) {
+ Color(0xFF303136)
+ } else {
+ Color(0xFFFFFFFF)
+ }
+
+val discord_settings_icon
+ @Composable get() = if (isSystemInDarkTheme()) {
+ Color(0xFFBABBBF)
+ } else {
+ Color(0xFF303136)
+ }
+
+val channel_member_action_icon
+ @Composable get() = if (isSystemInDarkTheme()) {
+ Color(0xFFbbbabf)
+ } else {
+ Color(0xFF4e555f)
+ }
+
+val channel_member_action_label
+ @Composable get() = if (isSystemInDarkTheme()) {
+ Color(0xFFc5c4c9)
+ } else {
+ Color(0xFF4e555f)
+ }
+
+val channel_member_bg
+@Composable get() = if(isSystemInDarkTheme()){
+ Color(0xFF302f34)
+}else{
+ Color(0xFFf4f4f4)
+}
+
+val channel_member_secondary_bg
+ @Composable get() = if(isSystemInDarkTheme()){
+ Color(0xFF35383f)
+ }else{
+ Color(0xFFFFFFFF)
+ }
diff --git a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/theme/Theme.kt b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/theme/Theme.kt
index 820d1ab..634bbd9 100644
--- a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/theme/Theme.kt
+++ b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/theme/Theme.kt
@@ -3,10 +3,17 @@ package dev.baseio.discordjetpackcompose.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material.Colors
import androidx.compose.material.MaterialTheme
-import androidx.compose.runtime.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.runtime.structuralEqualityPolicy
import androidx.compose.ui.graphics.Color
-
private val LightColorPalette = DiscordColorPalette(
primary = design_default_color_primary,
primaryVariant = design_default_color_primary_variant,
@@ -35,6 +42,7 @@ private val LightColorPalette = DiscordColorPalette(
tabsBackgroundColor = design_default_color_secondary_background,
tabSelectedColor = design_default_tab_selected_color,
textFieldContentColor = text_field_content_color_light,
+ settingsBackground = Color(0xFFFFFFFF),
isLight = true
)
@@ -66,6 +74,7 @@ private val DarkColorPalette = DiscordColorPalette(
tabsBackgroundColor = Color(0xFF2a2b2f),
tabSelectedColor = Color(0xFF4f535c),
textFieldContentColor = text_field_content_color_Dark,
+ settingsBackground = Color(0xFF363940),
isLight = false
)
@@ -142,6 +151,7 @@ class DiscordColorPalette(
tabsBackgroundColor: Color,
tabSelectedColor: Color,
textFieldContentColor: Color,
+ settingsBackground: Color,
isLight: Boolean = false
) {
var primary by mutableStateOf(primary, structuralEqualityPolicy())
@@ -200,6 +210,8 @@ class DiscordColorPalette(
internal set
var tabSelectedColor by mutableStateOf(tabSelectedColor, structuralEqualityPolicy())
internal set
+ var settingsBackground by mutableStateOf(settingsBackground, structuralEqualityPolicy())
+ internal set
fun update(other: DiscordColorPalette) {
primary = other.primary
diff --git a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/theme/Type.kt b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/theme/Type.kt
index 3fd7bb7..fb2a0cf 100644
--- a/app/src/main/java/dev/baseio/discordjetpackcompose/ui/theme/Type.kt
+++ b/app/src/main/java/dev/baseio/discordjetpackcompose/ui/theme/Type.kt
@@ -100,6 +100,7 @@ val ServerInfoTypography = Typography(
val MessageTypography = Typography(
defaultFontFamily = UniSansFontFamily,
h1 = TextStyle(
+ fontFamily = UniSansFontFamily,
fontWeight = FontWeight.Medium,
fontSize = 18.sp,
),
@@ -119,10 +120,13 @@ val MessageTypography = Typography(
fontSize = 14.sp
),
subtitle1 = TextStyle(
+ fontFamily = UniSansFontFamily,
fontWeight = FontWeight.Medium,
fontSize = 18.sp
),
subtitle2 = TextStyle(
+ fontFamily = UniSansFontFamily,
+ fontWeight = FontWeight.Normal,
fontSize = 14.sp,
)
)
diff --git a/app/src/main/java/dev/baseio/discordjetpackcompose/viewmodels/ChatScreenViewModel.kt b/app/src/main/java/dev/baseio/discordjetpackcompose/viewmodels/ChatScreenViewModel.kt
index d02eb50..2f01f81 100644
--- a/app/src/main/java/dev/baseio/discordjetpackcompose/viewmodels/ChatScreenViewModel.kt
+++ b/app/src/main/java/dev/baseio/discordjetpackcompose/viewmodels/ChatScreenViewModel.kt
@@ -5,42 +5,94 @@ import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
import dagger.hilt.android.lifecycle.HiltViewModel
import dev.baseio.discordjetpackcompose.entities.message.DiscordMessageEntity
+import dev.baseio.discordjetpackcompose.entities.message.DiscordUrlMetaEntity
import dev.baseio.discordjetpackcompose.usecases.chat.FetchMessagesUseCase
+import dev.baseio.discordjetpackcompose.usecases.chat.FetchUrlMetadataUseCase
import dev.baseio.discordjetpackcompose.usecases.chat.SendMessageUseCase
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import java.util.UUID
import javax.inject.Inject
+/**
+ * UI state for the Chat screen
+ */
+data class ChatUiState(
+ val message: String = "",
+ val messageAction: String = "",
+ val loading: Boolean = false,
+)
+
@HiltViewModel
class ChatScreenViewModel @Inject constructor(
private val fetchMessagesUseCase: FetchMessagesUseCase,
- private val sendMessageUseCase: SendMessageUseCase
+ private val sendMessageUseCase: SendMessageUseCase,
+ private val fetchUrlMetadataUseCase: FetchUrlMetadataUseCase
) : ViewModel() {
+ // UI state exposed to the UI
+ private val _uiState = MutableStateFlow(ChatUiState(loading = true))
+ val uiState: StateFlow = _uiState.asStateFlow()
+
var chatMessagesFlow = MutableStateFlow>?>(null)
- var message = MutableStateFlow("")
fun fetchMessages() {
chatMessagesFlow.value = fetchMessagesUseCase.performStreaming("1")
}
- fun sendMessage(search: String) {
- if (search.isNotEmpty()) {
+ fun sendMessage(
+ messageToSend: String,
+ messageToReply: String,
+ isReply: Boolean = false,
+ url: String? = null
+ ) {
+ if (messageToSend.isNotEmpty()) {
viewModelScope.launch {
+ var discordUrlMetaEntity: DiscordUrlMetaEntity? = null
+ url?.let { safeUrl ->
+ discordUrlMetaEntity = fetchUrlMetadataUseCase.perform(safeUrl)
+ }
+
val message = DiscordMessageEntity(
uuid = UUID.randomUUID().toString(),
channelId = "1",
- message = search,
+ message = messageToSend,
userId = UUID.randomUUID().toString(),
createdBy = "Person",
createdDate = System.currentTimeMillis(),
modifiedDate = System.currentTimeMillis(),
+ replyTo = if (isReply) "Person" else "",
+ replyToMessage = messageToReply,
+ metaTitle = discordUrlMetaEntity?.title ?: "",
+ metaDesc = discordUrlMetaEntity?.desc ?: "",
+ metaImageUrl = discordUrlMetaEntity?.image ?: "",
+ metaUrl = discordUrlMetaEntity?.url ?: ""
)
sendMessageUseCase.perform(message)
}
- message.value = ""
+ updateMessage("")
+ }
+ }
+
+ fun updateMessage(message: String) {
+ _uiState.update { chatUiState ->
+ chatUiState.copy(message = message)
+ }
+ }
+
+ fun updateMessageAction(action: String) {
+ _uiState.update { chatUiState ->
+ chatUiState.copy(messageAction = action)
+ }
+ }
+
+ fun resetMessageAction() {
+ _uiState.update { chatUiState ->
+ chatUiState.copy(messageAction = "")
}
}
}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_baseline_accessibility_new_24.xml b/app/src/main/res/drawable/ic_baseline_accessibility_new_24.xml
new file mode 100644
index 0000000..6c0fa6f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_accessibility_new_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_account_box_24.xml b/app/src/main/res/drawable/ic_baseline_account_box_24.xml
new file mode 100644
index 0000000..ff7b180
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_account_box_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_account_circle_24.xml b/app/src/main/res/drawable/ic_baseline_account_circle_24.xml
new file mode 100644
index 0000000..bf86aa3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_account_circle_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_card_giftcard_24.xml b/app/src/main/res/drawable/ic_baseline_card_giftcard_24.xml
new file mode 100644
index 0000000..f20e85a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_card_giftcard_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_circle_24.xml b/app/src/main/res/drawable/ic_baseline_circle_24.xml
new file mode 100644
index 0000000..0c85947
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_circle_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_color_lens_24.xml b/app/src/main/res/drawable/ic_baseline_color_lens_24.xml
new file mode 100644
index 0000000..032d574
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_color_lens_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_contact_support_24.xml b/app/src/main/res/drawable/ic_baseline_contact_support_24.xml
new file mode 100644
index 0000000..2cb0591
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_contact_support_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_edit_24.xml b/app/src/main/res/drawable/ic_baseline_edit_24.xml
new file mode 100644
index 0000000..83871fa
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_edit_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_image_24.xml b/app/src/main/res/drawable/ic_baseline_image_24.xml
new file mode 100644
index 0000000..0559e37
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_image_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_info_24.xml b/app/src/main/res/drawable/ic_baseline_info_24.xml
new file mode 100644
index 0000000..d970aab
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_info_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_language_24.xml b/app/src/main/res/drawable/ic_baseline_language_24.xml
new file mode 100644
index 0000000..2f1cabc
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_language_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_laptop_24.xml b/app/src/main/res/drawable/ic_baseline_laptop_24.xml
new file mode 100644
index 0000000..6284a24
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_laptop_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_mic_24.xml b/app/src/main/res/drawable/ic_baseline_mic_24.xml
new file mode 100644
index 0000000..3aa3c6e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_mic_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_notification_important_24.xml b/app/src/main/res/drawable/ic_baseline_notification_important_24.xml
new file mode 100644
index 0000000..a8c6859
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_notification_important_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_qr_code_24.xml b/app/src/main/res/drawable/ic_baseline_qr_code_24.xml
new file mode 100644
index 0000000..f524890
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_qr_code_24.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_security_24.xml b/app/src/main/res/drawable/ic_baseline_security_24.xml
new file mode 100644
index 0000000..7f40242
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_security_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_settings_applications_24.xml b/app/src/main/res/drawable/ic_baseline_settings_applications_24.xml
new file mode 100644
index 0000000..1aa51ad
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_settings_applications_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_subscriptions_24.xml b/app/src/main/res/drawable/ic_baseline_subscriptions_24.xml
new file mode 100644
index 0000000..92637cc
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_subscriptions_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_vpn_key_24.xml b/app/src/main/res/drawable/ic_baseline_vpn_key_24.xml
new file mode 100644
index 0000000..3ad999e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_vpn_key_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_emoji_1.xml b/app/src/main/res/drawable/ic_emoji_1.xml
new file mode 100644
index 0000000..f5ef0db
--- /dev/null
+++ b/app/src/main/res/drawable/ic_emoji_1.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_emoji_2.xml b/app/src/main/res/drawable/ic_emoji_2.xml
new file mode 100644
index 0000000..3f6f997
--- /dev/null
+++ b/app/src/main/res/drawable/ic_emoji_2.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_emoji_3.xml b/app/src/main/res/drawable/ic_emoji_3.xml
new file mode 100644
index 0000000..8b8f7a5
--- /dev/null
+++ b/app/src/main/res/drawable/ic_emoji_3.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_emoji_4.xml b/app/src/main/res/drawable/ic_emoji_4.xml
new file mode 100644
index 0000000..dc2f3ab
--- /dev/null
+++ b/app/src/main/res/drawable/ic_emoji_4.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_emoji_5.xml b/app/src/main/res/drawable/ic_emoji_5.xml
new file mode 100644
index 0000000..300c0f9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_emoji_5.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_hashtag_solid.xml b/app/src/main/res/drawable/ic_hashtag_solid.xml
new file mode 100644
index 0000000..975cfa5
--- /dev/null
+++ b/app/src/main/res/drawable/ic_hashtag_solid.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 01fcd23..8167c08 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -85,4 +85,40 @@
LAST CHANNEL
SUGGESTIONS
Unknown item type! Please add this type inside the DashboardBottomBarItemType enum class.
+ User Settings
+ logout
+ Debug
+ profile
+ Set Status
+ My Account
+ User Profile
+
+ Authorized Apps
+ Connections
+ Scan QR Code
+ Subscribe Today
+ Boosts
+ Nitro Gifting
+
+ Notification
+
+ Appearance
+ Accessibility
+ Behavior
+ Language
+ Activity Status
+ Change Log
+ Support
+ Upload debug logs to Discord Support
+ Acknowledgement
+ USER SETTINGS
+ NITRO SETTINGS
+ APP SETTINGS
+ APP INFORMATION
+ Notifications
+ Settings
+ Threads
+ Pins
+ Invite Members
+ # welcome
\ No newline at end of file
diff --git a/art/discord_channel_members_dark.png b/art/discord_channel_members_dark.png
new file mode 100644
index 0000000..8f84acc
Binary files /dev/null and b/art/discord_channel_members_dark.png differ
diff --git a/art/discord_channel_members_light.png b/art/discord_channel_members_light.png
new file mode 100644
index 0000000..17d4ba5
Binary files /dev/null and b/art/discord_channel_members_light.png differ
diff --git a/art/discord_chat_dark.png b/art/discord_chat_dark.png
new file mode 100644
index 0000000..9519db8
Binary files /dev/null and b/art/discord_chat_dark.png differ
diff --git a/art/discord_chat_light.png b/art/discord_chat_light.png
new file mode 100644
index 0000000..f1d9b6e
Binary files /dev/null and b/art/discord_chat_light.png differ
diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt
index e0c70d7..9986d39 100644
--- a/buildSrc/src/main/kotlin/Dependencies.kt
+++ b/buildSrc/src/main/kotlin/Dependencies.kt
@@ -60,6 +60,7 @@ object Lib {
"com.google.accompanist:accompanist-insets:${ACCOMPANIST_VERSION}"
const val ACCOMPANIST_INSETS_UI =
"com.google.accompanist:accompanist-insets-ui:${ACCOMPANIST_VERSION}"
+ const val ACCOMPANIST_COIL = "com.google.accompanist:accompanist-coil:0.14.0"
const val MATERIAL_EXTENDED_ICONS = "androidx.compose.material:material-icons-extended:$COMPOSE_VERSION"
const val COMPOSE_JUNIT = "androidx.compose.ui:ui-test-junit4:$COMPOSE_VERSION"
@@ -83,6 +84,11 @@ object Lib {
const val PAGING_COMPOSE = "androidx.paging:paging-compose:1.0.0-alpha14"
}
+ object Jsoup {
+ private const val JSOUP_VERSION = "1.13.1"
+ const val JSOUP = "org.jsoup:jsoup:${JSOUP_VERSION}"
+ }
+
object Room {
private const val roomVersion = "2.4.1"
const val roomRuntime = "androidx.room:room-runtime:$roomVersion"
diff --git a/data/build.gradle.kts b/data/build.gradle.kts
index 9718866..50ba940 100644
--- a/data/build.gradle.kts
+++ b/data/build.gradle.kts
@@ -40,6 +40,8 @@ dependencies {
/* Paging */
implementation(Lib.Paging.PAGING_3)
+ implementation(Lib.Jsoup.JSOUP)
+
/* Room */
api(Lib.Room.roomRuntime)
kapt(Lib.Room.roomCompiler)
diff --git a/data/src/main/java/dev/baseio/discordjetpackcompose/local/model/DBDiscordMessage.kt b/data/src/main/java/dev/baseio/discordjetpackcompose/local/model/DBDiscordMessage.kt
index 550fa09..bd2de2f 100644
--- a/data/src/main/java/dev/baseio/discordjetpackcompose/local/model/DBDiscordMessage.kt
+++ b/data/src/main/java/dev/baseio/discordjetpackcompose/local/model/DBDiscordMessage.kt
@@ -10,7 +10,13 @@ data class DBDiscordMessage(
@ColumnInfo(name = "channelId") val channelId: String,
@ColumnInfo(name = "message") val message: String,
@ColumnInfo(name = "from") val userId: String,
+ @ColumnInfo(name = "replyTo") val replyTo: String,
+ @ColumnInfo(name = "replyToMessage") val replyToMessage: String,
@ColumnInfo(name = "createdBy") val createdBy: String,
@ColumnInfo(name = "createdDate") val createdDate: Long,
@ColumnInfo(name = "modifiedDate") val modifiedDate: Long,
+ @ColumnInfo(name = "metaTitle") val metaTitle: String,
+ @ColumnInfo(name = "metaDesc") val metaDesc: String,
+ @ColumnInfo(name = "metaImageUrl") val metaImageUrl: String,
+ @ColumnInfo(name = "metaUrl") val metaUrl: String
)
\ No newline at end of file
diff --git a/data/src/main/java/dev/baseio/discordjetpackcompose/mappers/DiscordMessageMapper.kt b/data/src/main/java/dev/baseio/discordjetpackcompose/mappers/DiscordMessageMapper.kt
index 3ea7e59..f4b851e 100644
--- a/data/src/main/java/dev/baseio/discordjetpackcompose/mappers/DiscordMessageMapper.kt
+++ b/data/src/main/java/dev/baseio/discordjetpackcompose/mappers/DiscordMessageMapper.kt
@@ -12,9 +12,15 @@ class DiscordMessageMapper @Inject constructor() : EntityMapper()
+
override fun fetchMessages(params: String?): Flow> {
return Pager(PagingConfig(pageSize = 20)) {
discordMessageDao.messagesByDate(params)
@@ -32,4 +36,29 @@ class MessagesRepoImpl @Inject constructor(
params
}
}
+
+ override suspend fun fetchUrlMetadata(url: String?): DiscordUrlMetaEntity? {
+ val discordUrlMetaEntity = DiscordUrlMetaEntity()
+ if (cacheUrlMap.containsKey(url)) return cacheUrlMap[url!!]
+
+ withContext(coroutineMainDispatcherProvider.io) {
+ val con = Jsoup.connect(url)
+ val doc = con.userAgent("Mozilla").get()
+ val ogTags = doc.select("meta[property^=og:]")
+ when {
+ ogTags.size > 0 ->
+ ogTags.forEachIndexed { index, _ ->
+ val tag = ogTags[index]
+ when (tag.attr("property")) {
+ "og:image" -> discordUrlMetaEntity.image = tag.attr("content")
+ "og:description" -> discordUrlMetaEntity.desc = tag.attr("content")
+ "og:url" -> discordUrlMetaEntity.url = tag.attr("content")
+ "og:title" -> discordUrlMetaEntity.title = tag.attr("content")
+ }
+ }
+ }
+ cacheUrlMap[url!!] = discordUrlMetaEntity
+ }
+ return discordUrlMetaEntity
+ }
}
\ No newline at end of file
diff --git a/domain/src/main/java/dev/baseio/discordjetpackcompose/entities/message/DiscordMessageEntity.kt b/domain/src/main/java/dev/baseio/discordjetpackcompose/entities/message/DiscordMessageEntity.kt
index dae6a94..bd943ec 100644
--- a/domain/src/main/java/dev/baseio/discordjetpackcompose/entities/message/DiscordMessageEntity.kt
+++ b/domain/src/main/java/dev/baseio/discordjetpackcompose/entities/message/DiscordMessageEntity.kt
@@ -5,7 +5,13 @@ data class DiscordMessageEntity(
val channelId: String,
val message: String,
val userId: String,
+ val replyTo: String,
+ val replyToMessage: String,
val createdBy: String,
val createdDate: Long,
val modifiedDate: Long,
+ val metaTitle: String,
+ val metaDesc: String,
+ val metaImageUrl: String,
+ val metaUrl: String
)
\ No newline at end of file
diff --git a/domain/src/main/java/dev/baseio/discordjetpackcompose/entities/message/DiscordUrlMetaEntity.kt b/domain/src/main/java/dev/baseio/discordjetpackcompose/entities/message/DiscordUrlMetaEntity.kt
new file mode 100644
index 0000000..fc31a15
--- /dev/null
+++ b/domain/src/main/java/dev/baseio/discordjetpackcompose/entities/message/DiscordUrlMetaEntity.kt
@@ -0,0 +1,8 @@
+package dev.baseio.discordjetpackcompose.entities.message
+
+data class DiscordUrlMetaEntity(
+ var title: String? = null,
+ var desc: String? = null,
+ var image: String? = null,
+ var url: String? = null
+)
\ No newline at end of file
diff --git a/domain/src/main/java/dev/baseio/discordjetpackcompose/repositories/MessagesRepo.kt b/domain/src/main/java/dev/baseio/discordjetpackcompose/repositories/MessagesRepo.kt
index 140a36d..50e6a3d 100644
--- a/domain/src/main/java/dev/baseio/discordjetpackcompose/repositories/MessagesRepo.kt
+++ b/domain/src/main/java/dev/baseio/discordjetpackcompose/repositories/MessagesRepo.kt
@@ -2,9 +2,11 @@ package dev.baseio.discordjetpackcompose.repositories
import androidx.paging.PagingData
import dev.baseio.discordjetpackcompose.entities.message.DiscordMessageEntity
+import dev.baseio.discordjetpackcompose.entities.message.DiscordUrlMetaEntity
import kotlinx.coroutines.flow.Flow
interface MessagesRepo {
fun fetchMessages(params: String?): Flow>
suspend fun sendMessage(params: DiscordMessageEntity): DiscordMessageEntity
+ suspend fun fetchUrlMetadata(url: String?): DiscordUrlMetaEntity?
}
\ No newline at end of file
diff --git a/domain/src/main/java/dev/baseio/discordjetpackcompose/usecases/chat/FetchUrlMetadataUseCase.kt b/domain/src/main/java/dev/baseio/discordjetpackcompose/usecases/chat/FetchUrlMetadataUseCase.kt
new file mode 100644
index 0000000..7ce8f6a
--- /dev/null
+++ b/domain/src/main/java/dev/baseio/discordjetpackcompose/usecases/chat/FetchUrlMetadataUseCase.kt
@@ -0,0 +1,13 @@
+package dev.baseio.discordjetpackcompose.usecases.chat
+
+import dev.baseio.discordjetpackcompose.entities.message.DiscordUrlMetaEntity
+import dev.baseio.discordjetpackcompose.repositories.MessagesRepo
+import dev.baseio.discordjetpackcompose.usecases.BaseUseCase
+import javax.inject.Inject
+
+class FetchUrlMetadataUseCase @Inject constructor(private val messagesRepo: MessagesRepo) :
+ BaseUseCase {
+ override suspend fun perform(params: String): DiscordUrlMetaEntity? {
+ return messagesRepo.fetchUrlMetadata(params)
+ }
+}
diff --git a/navigator/src/main/java/dev/baseio/discordjetpackcompose/navigator/Screens.kt b/navigator/src/main/java/dev/baseio/discordjetpackcompose/navigator/Screens.kt
index 45458d8..fba9663 100644
--- a/navigator/src/main/java/dev/baseio/discordjetpackcompose/navigator/Screens.kt
+++ b/navigator/src/main/java/dev/baseio/discordjetpackcompose/navigator/Screens.kt
@@ -15,6 +15,7 @@ sealed class DiscordScreen(
object Friends : DiscordScreen("friends")
object CreateServer : DiscordScreen("createServer")
object Invite : DiscordScreen("invite")
+ object UserSettings: DiscordScreen("userSettings")
object Home : DiscordScreen("home")
object Search : DiscordScreen("search")
}