1
1
package ai.mlc.mlcchat
2
2
3
+ import android.app.Activity
4
+ import android.graphics.Bitmap
5
+ import android.graphics.BitmapFactory
6
+ import androidx.compose.foundation.Image
3
7
import androidx.compose.foundation.background
4
8
import androidx.compose.foundation.gestures.detectTapGestures
5
9
import androidx.compose.foundation.layout.Arrangement
@@ -20,7 +24,9 @@ import androidx.compose.foundation.lazy.rememberLazyListState
20
24
import androidx.compose.foundation.shape.RoundedCornerShape
21
25
import androidx.compose.foundation.text.selection.SelectionContainer
22
26
import androidx.compose.material.icons.Icons
27
+ import androidx.compose.material.icons.filled.AddAPhoto
23
28
import androidx.compose.material.icons.filled.ArrowBack
29
+ import androidx.compose.material.icons.filled.Photo
24
30
import androidx.compose.material.icons.filled.Replay
25
31
import androidx.compose.material.icons.filled.Send
26
32
import androidx.compose.material3.Divider
@@ -43,6 +49,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
43
49
import androidx.compose.runtime.setValue
44
50
import androidx.compose.ui.Alignment
45
51
import androidx.compose.ui.Modifier
52
+ import androidx.compose.ui.graphics.asImageBitmap
46
53
import androidx.compose.ui.input.pointer.pointerInput
47
54
import androidx.compose.ui.platform.LocalFocusManager
48
55
import androidx.compose.ui.text.style.TextAlign
@@ -55,9 +62,10 @@ import kotlinx.coroutines.launch
55
62
@ExperimentalMaterial3Api
56
63
@Composable
57
64
fun ChatView (
58
- navController : NavController , chatState : AppViewModel .ChatState
65
+ navController : NavController , chatState : AppViewModel .ChatState , activity : Activity
59
66
) {
60
67
val localFocusManager = LocalFocusManager .current
68
+ (activity as MainActivity ).chatState = chatState
61
69
Scaffold (topBar = {
62
70
TopAppBar (
63
71
title = {
@@ -81,7 +89,9 @@ fun ChatView(
81
89
},
82
90
actions = {
83
91
IconButton (
84
- onClick = { chatState.requestResetChat() },
92
+ onClick = {
93
+ chatState.requestResetChat()
94
+ activity.hasImage = false },
85
95
enabled = chatState.interruptable()
86
96
) {
87
97
Icon (
@@ -125,23 +135,23 @@ fun ChatView(
125
135
items = chatState.messages,
126
136
key = { message -> message.id },
127
137
) { message ->
128
- MessageView (messageData = message)
138
+ MessageView (messageData = message, activity )
129
139
}
130
140
item {
131
141
// place holder item for scrolling to the bottom
132
142
}
133
143
}
134
144
Divider (thickness = 1 .dp, modifier = Modifier .padding(top = 5 .dp))
135
- SendMessageView (chatState = chatState)
145
+ SendMessageView (chatState = chatState, activity )
136
146
}
137
147
}
138
148
}
139
149
140
150
@Composable
141
- fun MessageView (messageData : MessageData ) {
151
+ fun MessageView (messageData : MessageData , activity : Activity ? ) {
142
152
// default render the Assistant text as MarkdownText
143
153
var useMarkdown by remember { mutableStateOf(true ) }
144
-
154
+ var localActivity : MainActivity = activity as MainActivity
145
155
SelectionContainer {
146
156
if (messageData.role == MessageRole .Assistant ) {
147
157
Column {
@@ -202,19 +212,47 @@ fun MessageView(messageData: MessageData) {
202
212
horizontalArrangement = Arrangement .End ,
203
213
modifier = Modifier .fillMaxWidth()
204
214
) {
205
- Text (
206
- text = messageData.text,
207
- textAlign = TextAlign .Right ,
208
- color = MaterialTheme .colorScheme.onPrimaryContainer,
209
- modifier = Modifier
210
- .wrapContentWidth()
211
- .background(
212
- color = MaterialTheme .colorScheme.primaryContainer,
213
- shape = RoundedCornerShape (5 .dp)
215
+ if (messageData.imageUri != null ) {
216
+ val uri = messageData.imageUri
217
+ val bitmap = uri?.let {
218
+ activity.contentResolver.openInputStream(it)?.use { input ->
219
+ BitmapFactory .decodeStream(input)
220
+ }
221
+ }
222
+ val displayBitmap = bitmap?.let { Bitmap .createScaledBitmap(it, 224 , 224 , true ) }
223
+ if (displayBitmap != null ) {
224
+ Image (
225
+ displayBitmap.asImageBitmap(),
226
+ " " ,
227
+ modifier = Modifier
228
+ .wrapContentWidth()
229
+ .background(
230
+ color = MaterialTheme .colorScheme.secondaryContainer,
231
+ shape = RoundedCornerShape (5 .dp)
232
+ )
233
+ .padding(5 .dp)
234
+ .widthIn(max = 300 .dp)
214
235
)
215
- .padding(5 .dp)
216
- .widthIn(max = 300 .dp)
217
- )
236
+ }
237
+ if (! localActivity.hasImage) {
238
+ localActivity.chatState.requestImageBitmap(messageData.imageUri)
239
+ }
240
+ localActivity.hasImage = true
241
+ } else {
242
+ Text (
243
+ text = messageData.text,
244
+ textAlign = TextAlign .Right ,
245
+ color = MaterialTheme .colorScheme.onPrimaryContainer,
246
+ modifier = Modifier
247
+ .wrapContentWidth()
248
+ .background(
249
+ color = MaterialTheme .colorScheme.primaryContainer,
250
+ shape = RoundedCornerShape (5 .dp)
251
+ )
252
+ .padding(5 .dp)
253
+ .widthIn(max = 300 .dp)
254
+ )
255
+ }
218
256
219
257
}
220
258
}
@@ -223,8 +261,9 @@ fun MessageView(messageData: MessageData) {
223
261
224
262
@ExperimentalMaterial3Api
225
263
@Composable
226
- fun SendMessageView (chatState : AppViewModel .ChatState ) {
264
+ fun SendMessageView (chatState : AppViewModel .ChatState , activity : Activity ) {
227
265
val localFocusManager = LocalFocusManager .current
266
+ val localActivity : MainActivity = activity as MainActivity
228
267
Row (
229
268
horizontalArrangement = Arrangement .spacedBy(5 .dp),
230
269
verticalAlignment = Alignment .CenterVertically ,
@@ -241,10 +280,38 @@ fun SendMessageView(chatState: AppViewModel.ChatState) {
241
280
modifier = Modifier
242
281
.weight(9f ),
243
282
)
283
+ IconButton (
284
+ onClick = {
285
+ activity.takePhoto()
286
+ },
287
+ modifier = Modifier
288
+ .aspectRatio(1f )
289
+ .weight(1f ),
290
+ enabled = (chatState.chatable() && ! localActivity.hasImage)
291
+ ) {
292
+ Icon (
293
+ imageVector = Icons .Filled .AddAPhoto ,
294
+ contentDescription = " use camera" ,
295
+ )
296
+ }
297
+ IconButton (
298
+ onClick = {
299
+ activity.pickImageFromGallery()
300
+ },
301
+ modifier = Modifier
302
+ .aspectRatio(1f )
303
+ .weight(1f ),
304
+ enabled = (chatState.chatable() && ! localActivity.hasImage)
305
+ ) {
306
+ Icon (
307
+ imageVector = Icons .Filled .Photo ,
308
+ contentDescription = " select image" ,
309
+ )
310
+ }
244
311
IconButton (
245
312
onClick = {
246
313
localFocusManager.clearFocus()
247
- chatState.requestGenerate(text)
314
+ chatState.requestGenerate(text, activity )
248
315
text = " "
249
316
},
250
317
modifier = Modifier
@@ -271,6 +338,6 @@ fun MessageViewPreviewWithMarkdown() {
271
338
* [Link](https://example.com)
272
339
<a href="https://www.google.com/">Google</a>
273
340
"""
274
- )
341
+ ), null
275
342
)
276
343
}
0 commit comments