Skip to content

Commit 3098541

Browse files
committed
feat(messages): add scrollbar
1 parent 093d241 commit 3098541

File tree

11 files changed

+194
-39
lines changed

11 files changed

+194
-39
lines changed

.idea/jarRepositories.xml

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ dependencies {
6565
def nav_version = "2.5.3"
6666
implementation("androidx.navigation:navigation-compose:$nav_version")
6767
implementation("io.coil-kt:coil-compose:2.3.0")
68-
// system bars customization
68+
69+
// System bars customization
6970
implementation "com.google.accompanist:accompanist-systemuicontroller:0.28.0"
7071
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.chatgptlite.wanted.constants
2+
3+
const val urlToImageAppIcon = "https://res.cloudinary.com/apideck/image/upload/v1672442492/marketplaces/ckhg56iu1mkpc0b66vj7fsj3o/listings/-4-ans_frontend_assets.images.poe.app_icon.png-26-8aa0a2e5f237894d_tbragv.png"
4+
const val urlToImageAuthor = "https://avatars.githubusercontent.com/u/60530946?v=4"
5+
const val urlToAvatarGPT = "https://gptapk.com/wp-content/uploads/2023/02/chatgpt-icon.png"
6+
const val urlToGithub = "https://github.com/lambiengcode"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.chatgptlite.wanted.helpers
2+
3+
import android.content.Context
4+
import android.content.Intent
5+
import android.net.Uri
6+
7+
class UrlLauncher {
8+
9+
fun openUrl(context: Context, url: String) {
10+
val urlIntent = Intent(
11+
Intent.ACTION_VIEW,
12+
Uri.parse(url)
13+
)
14+
context.startActivity(urlIntent)
15+
}
16+
}

app/src/main/java/com/chatgptlite/wanted/ui/common/AppBar.kt

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
11
package com.chatgptlite.wanted.ui.common
22

3-
import androidx.compose.foundation.layout.Box
4-
import androidx.compose.foundation.layout.Row
5-
import androidx.compose.foundation.layout.size
3+
import androidx.compose.foundation.Image
4+
import androidx.compose.foundation.layout.*
5+
import androidx.compose.foundation.shape.RoundedCornerShape
66
import androidx.compose.material.icons.Icons
77
import androidx.compose.material.icons.filled.Menu
88
import androidx.compose.material3.*
99
import androidx.compose.runtime.Composable
1010
import androidx.compose.ui.Alignment
1111
import androidx.compose.ui.Modifier
12+
import androidx.compose.ui.draw.clip
1213
import androidx.compose.ui.graphics.Color
14+
import androidx.compose.ui.graphics.ColorFilter
15+
import androidx.compose.ui.layout.ContentScale
1316
import androidx.compose.ui.text.font.FontWeight
1417
import androidx.compose.ui.text.style.TextAlign
1518
import androidx.compose.ui.unit.dp
1619
import androidx.compose.ui.unit.sp
20+
import coil.compose.rememberAsyncImagePainter
21+
import com.chatgptlite.wanted.constants.urlToAvatarGPT
22+
import com.chatgptlite.wanted.constants.urlToImageAppIcon
1723
import com.chatgptlite.wanted.ui.conversations.ui.theme.ChatGPTLiteTheme
1824
import com.chatgptlite.wanted.ui.theme.BackGroundColor
1925

@@ -25,25 +31,42 @@ fun AppBar(onClickMenu: () -> Unit) {
2531
shadowElevation = 4.dp,
2632
tonalElevation = 0.dp,
2733
) {
28-
CenterAlignedTopAppBar (
34+
CenterAlignedTopAppBar(
2935
title = {
36+
val paddingSizeModifier = Modifier
37+
.padding(start = 16.dp, top = 16.dp, bottom = 16.dp)
38+
.size(32.dp)
3039
Box {
3140
Row(verticalAlignment = Alignment.CenterVertically) {
41+
Image(
42+
painter = rememberAsyncImagePainter(urlToAvatarGPT),
43+
modifier = paddingSizeModifier.then(Modifier.clip(RoundedCornerShape(6.dp))),
44+
contentScale = ContentScale.Crop,
45+
contentDescription = null
46+
)
47+
Spacer(modifier = Modifier.width(8.dp))
3248
Text(
3349
text = "ChatGPT",
3450
textAlign = TextAlign.Center,
35-
fontSize = 18.sp,
51+
fontSize = 16.5.sp,
3652
fontWeight = FontWeight.SemiBold,
37-
color = MaterialTheme.colorScheme.primary,
3853
)
54+
Spacer(modifier = Modifier.width(12.dp))
3955
}
4056
}
4157
},
4258
navigationIcon = {
43-
IconButton(onClick = {
44-
onClickMenu()
45-
}) {
46-
Icon(Icons.Filled.Menu, "backIcon", modifier = Modifier.size(26.dp))
59+
IconButton(
60+
onClick = {
61+
onClickMenu()
62+
},
63+
) {
64+
Icon(
65+
Icons.Filled.Menu,
66+
"backIcon",
67+
modifier = Modifier.size(26.dp),
68+
tint = MaterialTheme.colorScheme.primary,
69+
)
4770
}
4871
},
4972
colors = TopAppBarDefaults.smallTopAppBarColors(

app/src/main/java/com/chatgptlite/wanted/ui/common/AppDrawer.kt

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@ import androidx.compose.foundation.lazy.LazyColumn
88
import androidx.compose.foundation.shape.CircleShape
99
import androidx.compose.foundation.shape.RoundedCornerShape
1010
import androidx.compose.material.icons.Icons
11-
import androidx.compose.material.icons.filled.Edit
12-
import androidx.compose.material.icons.filled.EditNote
13-
import androidx.compose.material.icons.filled.Settings
11+
import androidx.compose.material.icons.filled.*
1412
import androidx.compose.material3.*
1513
import androidx.compose.runtime.Composable
1614
import androidx.compose.ui.Alignment.Companion.CenterStart
@@ -20,22 +18,28 @@ import androidx.compose.ui.draw.clip
2018
import androidx.compose.ui.graphics.Color
2119
import androidx.compose.ui.graphics.vector.ImageVector
2220
import androidx.compose.ui.layout.ContentScale
21+
import androidx.compose.ui.platform.LocalContext
22+
import androidx.compose.ui.platform.LocalUriHandler
2323
import androidx.compose.ui.text.font.FontWeight
2424
import androidx.compose.ui.tooling.preview.Preview
2525
import androidx.compose.ui.unit.dp
2626
import androidx.compose.ui.unit.sp
2727
import coil.compose.rememberAsyncImagePainter
28+
import com.chatgptlite.wanted.constants.urlToGithub
29+
import com.chatgptlite.wanted.constants.urlToImageAppIcon
30+
import com.chatgptlite.wanted.constants.urlToImageAuthor
2831
import com.chatgptlite.wanted.data.fake.fakeConversations
32+
import com.chatgptlite.wanted.helpers.UrlLauncher
2933
import com.chatgptlite.wanted.models.ConversationModel
3034
import com.chatgptlite.wanted.ui.theme.ChatGPTLiteTheme
31-
import com.chatgptlite.wanted.ui.theme.PrimaryColor
3235

3336
@Composable
3437
fun AppDrawer(
3538
onProfileClicked: (String) -> Unit,
36-
onChatClicked: (String) ->
37-
Unit
39+
onChatClicked: (String) -> Unit,
3840
) {
41+
val context = LocalContext.current
42+
3943
ChatGPTLiteTheme() {
4044
Column(
4145
modifier = Modifier
@@ -46,14 +50,16 @@ fun AppDrawer(
4650
DrawerHeader()
4751
DividerItem()
4852
DrawerItemHeader("Chats")
49-
HistoryConversations(conversations = fakeConversations)
53+
HistoryConversations(conversations = fakeConversations, onChatClicked)
5054
DividerItem(modifier = Modifier.padding(horizontal = 28.dp))
5155
DrawerItemHeader("Settings")
52-
ChatItem("Settings", Icons.Filled.Settings, false) { onChatClicked("avc") }
56+
ChatItem("Settings", Icons.Filled.Settings, false) { onChatClicked("Settings") }
5357
ProfileItem(
5458
"lambiengcode (author)",
55-
"https://avatars.githubusercontent.com/u/60530946?v=4"
56-
) { println("onClick my profile") }
59+
urlToImageAuthor,
60+
) {
61+
UrlLauncher().openUrl(context = context, urlToGithub)
62+
}
5763
}
5864
}
5965
}
@@ -62,16 +68,12 @@ fun AppDrawer(
6268
private fun DrawerHeader() {
6369
val paddingSizeModifier = Modifier
6470
.padding(start = 16.dp, top = 16.dp, bottom = 16.dp)
65-
.background(
66-
Color.Transparent,
67-
shape = RoundedCornerShape(6.dp)
68-
)
6971
.size(34.dp)
7072

7173
Row(modifier = Modifier.padding(16.dp), verticalAlignment = CenterVertically) {
7274
Image(
73-
painter = rememberAsyncImagePainter("https://res.cloudinary.com/apideck/image/upload/v1672442492/marketplaces/ckhg56iu1mkpc0b66vj7fsj3o/listings/-4-ans_frontend_assets.images.poe.app_icon.png-26-8aa0a2e5f237894d_tbragv.png"),
74-
modifier = paddingSizeModifier.then(Modifier.clip(CircleShape)),
75+
painter = rememberAsyncImagePainter(urlToImageAppIcon),
76+
modifier = paddingSizeModifier.then(Modifier.clip(RoundedCornerShape(6.dp))),
7577
contentScale = ContentScale.Crop,
7678
contentDescription = null
7779
)
@@ -80,32 +82,37 @@ private fun DrawerHeader() {
8082
"ChatGPT Lite",
8183
fontSize = 15.sp,
8284
fontWeight = FontWeight.Bold,
83-
color = PrimaryColor
85+
color = MaterialTheme.colorScheme.secondary,
8486
)
8587
Text(
8688
"Powered by OpenAI",
8789
fontSize = 11.sp,
88-
fontWeight = FontWeight.Medium,
90+
fontWeight = FontWeight.Normal,
8991
color = Color.White,
9092
)
9193
}
9294
}
9395
}
9496

9597
@Composable
96-
private fun ColumnScope.HistoryConversations(conversations: List<ConversationModel>) {
98+
private fun ColumnScope.HistoryConversations(
99+
conversations: List<ConversationModel>,
100+
onChatClicked: (String) -> Unit
101+
) {
97102
LazyColumn(
98103
Modifier
99104
.fillMaxWidth()
100105
.weight(1f, false)
101-
.padding(horizontal = 16.dp),
106+
.padding(horizontal = 12.dp),
102107
) {
103108
items(conversations.size) { index ->
104109
ChatItem(
105110
text = conversations[index].title,
106-
Icons.Filled.EditNote,
111+
Icons.Filled.Message,
107112
selected = index == 0,
108-
onChatClicked = {},
113+
onChatClicked = {
114+
onChatClicked(conversations[index].id)
115+
},
109116
)
110117
}
111118
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.chatgptlite.wanted.ui.common
2+
3+
import androidx.compose.animation.core.animateFloatAsState
4+
import androidx.compose.animation.core.tween
5+
import androidx.compose.foundation.lazy.LazyListState
6+
import androidx.compose.material3.ExperimentalMaterial3Api
7+
import androidx.compose.material3.MaterialTheme
8+
import androidx.compose.runtime.Composable
9+
import androidx.compose.runtime.getValue
10+
import androidx.compose.ui.Modifier
11+
import androidx.compose.ui.draw.drawWithContent
12+
import androidx.compose.ui.geometry.Offset
13+
import androidx.compose.ui.geometry.Size
14+
import androidx.compose.ui.graphics.Color
15+
import androidx.compose.ui.unit.Dp
16+
import androidx.compose.ui.unit.dp
17+
18+
@Composable
19+
fun Modifier.simpleVerticalScrollbar(
20+
state: LazyListState,
21+
width: Dp = 8.dp,
22+
color: Color
23+
): Modifier {
24+
val targetAlpha = if (state.isScrollInProgress) 1f else 0f
25+
val duration = if (state.isScrollInProgress) 150 else 500
26+
27+
val alpha by animateFloatAsState(
28+
targetValue = targetAlpha,
29+
animationSpec = tween(durationMillis = duration)
30+
)
31+
32+
return drawWithContent {
33+
drawContent()
34+
35+
val firstVisibleElementIndex = state.layoutInfo.visibleItemsInfo.firstOrNull()?.index
36+
val needDrawScrollbar = state.isScrollInProgress || alpha > 0.0f
37+
38+
// Draw scrollbar if scrolling or if the animation is still running and lazy column has content
39+
if (needDrawScrollbar && firstVisibleElementIndex != null) {
40+
val elementHeight = this.size.height / state.layoutInfo.totalItemsCount
41+
val scrollbarOffsetY = firstVisibleElementIndex * elementHeight
42+
val scrollbarHeight = state.layoutInfo.visibleItemsInfo.size * elementHeight
43+
44+
drawRect(
45+
color = color,
46+
topLeft = Offset(this.size.width - width.toPx(), scrollbarOffsetY),
47+
size = Size(width.toPx(), scrollbarHeight),
48+
alpha = alpha
49+
)
50+
}
51+
}
52+
}

app/src/main/java/com/chatgptlite/wanted/ui/conversations/Conversations.kt

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ package com.chatgptlite.wanted.ui.conversations
33
import androidx.compose.foundation.background
44
import androidx.compose.foundation.layout.*
55
import androidx.compose.foundation.lazy.LazyColumn
6+
import androidx.compose.foundation.lazy.rememberLazyListState
7+
import androidx.compose.foundation.rememberScrollState
68
import androidx.compose.foundation.shape.RoundedCornerShape
9+
import androidx.compose.material3.MaterialTheme
710
import androidx.compose.material3.Surface
811
import androidx.compose.material3.Text
912
import androidx.compose.runtime.Composable
@@ -15,6 +18,7 @@ import androidx.compose.ui.unit.dp
1518
import androidx.compose.ui.unit.sp
1619
import com.chatgptlite.wanted.data.fake.fakeMessages
1720
import com.chatgptlite.wanted.models.MessageModel
21+
import com.chatgptlite.wanted.ui.common.simpleVerticalScrollbar
1822
import com.chatgptlite.wanted.ui.conversations.components.TextInput
1923
import com.chatgptlite.wanted.ui.conversations.ui.theme.ChatGPTLiteTheme
2024
import com.chatgptlite.wanted.ui.theme.*
@@ -36,13 +40,26 @@ fun Conversations() {
3640

3741
@Composable
3842
fun ColumnScope.MessageList(messages: List<MessageModel>) {
43+
val listState = rememberLazyListState()
44+
val scrollState = rememberScrollState()
45+
3946
LazyColumn(
40-
Modifier.fillMaxWidth().weight(1f, false)
47+
Modifier
48+
.fillMaxWidth()
49+
.weight(1f, false)
50+
.simpleVerticalScrollbar(
51+
listState,
52+
width = 4.dp,
53+
color = MaterialTheme.colorScheme.secondary,
54+
)
4155
.padding(horizontal = 16.dp),
4256
reverseLayout = true,
57+
state = listState,
4358
) {
4459
items(messages.size) { index ->
45-
MessageCard(message = messages[index], isLast = index == messages.size - 1)
60+
Box(modifier = Modifier.padding(bottom = if (index == 0) 10.dp else 0.dp)) {
61+
MessageCard(message = messages[index], isLast = index == messages.size - 1)
62+
}
4663
}
4764
}
4865
}
@@ -53,7 +70,8 @@ fun MessageCard(message: MessageModel, isLast: Boolean = false) {
5370
horizontalAlignment = if (message.isMe) Alignment.End else Alignment.Start,
5471
modifier = Modifier
5572
.fillMaxWidth()
56-
.padding(vertical = 4.dp).padding(top = if (isLast) 80.dp else 0.dp)
73+
.padding(vertical = 4.dp)
74+
.padding(top = if (isLast) 80.dp else 0.dp)
5775
) {
5876
Box(
5977
modifier = Modifier
@@ -64,7 +82,9 @@ fun MessageCard(message: MessageModel, isLast: Boolean = false) {
6482
),
6583
) {
6684
Text(
67-
text = message.message, fontSize = 13.sp, color = if (message.isMe) ColorTextHuman else ColorTextGPT,
85+
text = message.message,
86+
fontSize = 13.sp,
87+
color = if (message.isMe) ColorTextHuman else ColorTextGPT,
6888
modifier = Modifier.padding(horizontal = 18.dp, vertical = 12.dp),
6989
textAlign = TextAlign.Justify,
7090
)

0 commit comments

Comments
 (0)