Skip to content

Commit 9f23ed4

Browse files
adalparidcalhoun
andauthored
CMM-872 support he conversations and tickets logic (#22309)
* Adding basic UI * Renaming * Some styling * Renaming and dummy data * Using proper "new conversation icon" * Conversation details screen * Creating the reply bottomsheet * Linking to the support screen * bottomsheet fix * Mov navigation form activity to viewmodel * Adding create ticket screen * More screen adjustments * Extracting common code * Margin fix * detekt * Style * New ticket check * Creating tests * Creating repository and load conversations function * Adding createConversation function * Creating loadConversation func * Loading conversations form the viewmodel * Adding loading spinner * Pull to refresh * Proper ionitialization * Adding empty screen * Handling send new conversation * Show loading when sending * New ticket creation fix * Using snackbar for errors * Error handling * Answering conversation * Adding some test to the repository * More tests! * Compile fixes * Similarities improvements * Using snackbar in bots activity * Extracting EmptyConversationsView * Renaming * Extracting VM and UI common code * Extracting navigation common code * Renaming VMs for clarification * More refactor * Capitalise text fields * Updating rs library * Loading conversation UX * Style fix * Fixing scaffolds paddings * userID fix * Fixing the padding problem in bot chat when the keyboard is opened * Apply padding to create ticket screen when the keyboard is opened * Fixing scroll state in reply bottomsheet * Adding tests for the new common viewmodel * Fixing AIBotSupportViewModel tests * detekt * Improvements int he conversation interaction * Adding tests for HE VM * Saving draft state * Properly navigating when a ticket is selected * Error parsing improvement * accessToken suggestion improvements * General suggestions * Send message error UX improvement * Fixing tests * Bots screen renaming * Update WordPress/src/main/res/values/strings.xml Co-authored-by: David Calhoun <github@davidcalhoun.me> --------- Co-authored-by: David Calhoun <github@davidcalhoun.me>
1 parent 2189e63 commit 9f23ed4

29 files changed

+2335
-786
lines changed

WordPress/src/main/AndroidManifest.xml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,8 @@
438438

439439
<activity android:name="org.wordpress.android.support.aibot.ui.AIBotSupportActivity"
440440
android:theme="@style/WordPress.NoActionBar"
441-
android:label="@string/ai_bot_conversations_title"/>
441+
android:label="@string/ai_bot_conversations_title"
442+
android:windowSoftInputMode="adjustResize"/>
442443

443444
<activity android:name="org.wordpress.android.support.main.ui.SupportActivity"
444445
android:theme="@style/WordPress.NoActionBar"
@@ -450,7 +451,8 @@
450451

451452
<activity android:name="org.wordpress.android.support.he.ui.HESupportActivity"
452453
android:theme="@style/WordPress.NoActionBar"
453-
android:label="@string/support_screen_title"/>
454+
android:label="@string/support_screen_title"
455+
android:windowSoftInputMode="adjustResize"/>
454456

455457
<!-- Deep Linking Activity -->
456458
<activity
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.wordpress.android.support.aibot.model
22

3+
import org.wordpress.android.support.common.model.Conversation
34
import java.util.Date
45

56
data class BotConversation(
@@ -8,4 +9,6 @@ data class BotConversation(
89
val mostRecentMessageDate: Date,
910
val lastMessage: String,
1011
val messages: List<BotMessage>
11-
)
12+
): Conversation {
13+
override fun getConversationId(): Long = id
14+
}

WordPress/src/main/java/org/wordpress/android/support/aibot/repository/AIBotSupportRepository.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,19 @@ class AIBotSupportRepository @Inject constructor(
2424
private val wpComApiClientProvider: WpComApiClientProvider,
2525
@Named(IO_THREAD) private val ioDispatcher: CoroutineDispatcher,
2626
) {
27+
/**
28+
* Access token for API authentication.
29+
* Marked as @Volatile to ensure visibility across threads since this repository is accessed
30+
* from multiple coroutine contexts (main thread initialization, IO dispatcher for API calls).
31+
*/
32+
@Volatile
2733
private var accessToken: String? = null
34+
35+
/**
36+
* User ID for API operations.
37+
* Marked as @Volatile to ensure visibility across threads.
38+
*/
39+
@Volatile
2840
private var userId: Long = 0
2941

3042
private val wpComApiClient: WpComApiClient by lazy {

WordPress/src/main/java/org/wordpress/android/support/aibot/ui/ConversationDetailScreen.kt renamed to WordPress/src/main/java/org/wordpress/android/support/aibot/ui/AIBotConversationDetailScreen.kt

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,13 @@ import androidx.compose.ui.Alignment
4242
import androidx.compose.ui.Modifier
4343
import androidx.compose.ui.res.stringResource
4444
import androidx.compose.ui.text.font.FontWeight
45+
import androidx.compose.ui.text.input.KeyboardCapitalization
4546
import androidx.compose.ui.tooling.preview.Preview
4647
import androidx.compose.ui.unit.dp
48+
import androidx.compose.foundation.text.KeyboardOptions
4749
import android.content.res.Configuration.UI_MODE_NIGHT_YES
50+
import androidx.compose.material3.SnackbarHost
51+
import androidx.compose.material3.SnackbarHostState
4852
import androidx.compose.ui.platform.LocalResources
4953
import androidx.compose.ui.text.style.TextAlign
5054
import org.wordpress.android.R
@@ -56,7 +60,8 @@ import org.wordpress.android.ui.compose.theme.AppThemeM3
5660

5761
@OptIn(ExperimentalMaterial3Api::class)
5862
@Composable
59-
fun ConversationDetailScreen(
63+
fun AIBotConversationDetailScreen(
64+
snackbarHostState: SnackbarHostState,
6065
conversation: BotConversation,
6166
isLoading: Boolean,
6267
isBotTyping: Boolean,
@@ -83,6 +88,7 @@ fun ConversationDetailScreen(
8388
val resources = LocalResources.current
8489

8590
Scaffold(
91+
snackbarHost = { SnackbarHost(snackbarHostState) },
8692
topBar = {
8793
TopAppBar(
8894
title = { },
@@ -118,7 +124,6 @@ fun ConversationDetailScreen(
118124
LazyColumn(
119125
modifier = Modifier
120126
.fillMaxSize()
121-
.imePadding()
122127
.padding(horizontal = 16.dp),
123128
state = listState,
124129
verticalArrangement = Arrangement.spacedBy(12.dp)
@@ -208,6 +213,7 @@ private fun ChatInputBar(
208213
Row(
209214
modifier = Modifier
210215
.fillMaxWidth()
216+
.imePadding()
211217
.padding(16.dp),
212218
verticalAlignment = Alignment.CenterVertically,
213219
horizontalArrangement = Arrangement.spacedBy(8.dp)
@@ -218,6 +224,7 @@ private fun ChatInputBar(
218224
modifier = Modifier.weight(1f),
219225
placeholder = { Text(stringResource(R.string.ai_bot_message_input_placeholder)) },
220226
maxLines = 4,
227+
keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Sentences)
221228
)
222229

223230
IconButton(
@@ -352,9 +359,11 @@ private fun TypingDot(delay: Int) {
352359
@Composable
353360
private fun ConversationDetailScreenPreview() {
354361
val sampleConversation = generateSampleBotConversations()[0]
362+
val snackbarHostState = remember { SnackbarHostState() }
355363

356364
AppThemeM3(isDarkTheme = false) {
357-
ConversationDetailScreen(
365+
AIBotConversationDetailScreen(
366+
snackbarHostState = snackbarHostState,
358367
userName = "UserName",
359368
conversation = sampleConversation,
360369
isLoading = false,
@@ -370,9 +379,11 @@ private fun ConversationDetailScreenPreview() {
370379
@Composable
371380
private fun ConversationDetailScreenPreviewDark() {
372381
val sampleConversation = generateSampleBotConversations()[0]
382+
val snackbarHostState = remember { SnackbarHostState() }
373383

374384
AppThemeM3(isDarkTheme = true) {
375-
ConversationDetailScreen(
385+
AIBotConversationDetailScreen(
386+
snackbarHostState = snackbarHostState,
376387
userName = "UserName",
377388
conversation = sampleConversation,
378389
isLoading = false,
@@ -388,9 +399,11 @@ private fun ConversationDetailScreenPreviewDark() {
388399
@Composable
389400
private fun ConversationDetailScreenWordPressPreview() {
390401
val sampleConversation = generateSampleBotConversations()[0]
402+
val snackbarHostState = remember { SnackbarHostState() }
391403

392404
AppThemeM3(isDarkTheme = false, isJetpackApp = false) {
393-
ConversationDetailScreen(
405+
AIBotConversationDetailScreen(
406+
snackbarHostState = snackbarHostState,
394407
userName = "UserName",
395408
conversation = sampleConversation,
396409
isLoading = false,
@@ -406,9 +419,11 @@ private fun ConversationDetailScreenWordPressPreview() {
406419
@Composable
407420
private fun ConversationDetailScreenPreviewWordPressDark() {
408421
val sampleConversation = generateSampleBotConversations()[0]
422+
val snackbarHostState = remember { SnackbarHostState() }
409423

410424
AppThemeM3(isDarkTheme = true, isJetpackApp = false) {
411-
ConversationDetailScreen(
425+
AIBotConversationDetailScreen(
426+
snackbarHostState = snackbarHostState,
412427
userName = "UserName",
413428
conversation = sampleConversation,
414429
isLoading = false,

WordPress/src/main/java/org/wordpress/android/support/aibot/ui/ConversationsListScreen.kt renamed to WordPress/src/main/java/org/wordpress/android/support/aibot/ui/AIBotConversationsListScreen.kt

Lines changed: 25 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,11 @@ import androidx.compose.foundation.layout.Row
99
import androidx.compose.foundation.layout.Spacer
1010
import androidx.compose.foundation.layout.fillMaxSize
1111
import androidx.compose.foundation.layout.fillMaxWidth
12-
import androidx.compose.foundation.layout.height
1312
import androidx.compose.foundation.layout.padding
1413
import androidx.compose.foundation.lazy.LazyColumn
1514
import androidx.compose.foundation.lazy.items
1615
import androidx.compose.material.icons.Icons
1716
import androidx.compose.material.icons.automirrored.filled.ArrowBack
18-
import androidx.compose.material3.Button
1917
import androidx.compose.material.icons.filled.Edit
2018
import androidx.compose.material3.Card
2119
import androidx.compose.material3.CardDefaults
@@ -24,18 +22,18 @@ import androidx.compose.material3.Icon
2422
import androidx.compose.material3.IconButton
2523
import androidx.compose.material3.MaterialTheme
2624
import androidx.compose.material3.Scaffold
25+
import androidx.compose.material3.SnackbarHost
26+
import androidx.compose.material3.SnackbarHostState
2727
import androidx.compose.material3.Text
2828
import androidx.compose.material3.TopAppBar
2929
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
3030
import androidx.compose.runtime.Composable
3131
import androidx.compose.runtime.collectAsState
3232
import androidx.compose.runtime.getValue
33-
import androidx.compose.ui.Alignment
33+
import androidx.compose.runtime.remember
3434
import androidx.compose.ui.Modifier
3535
import androidx.compose.ui.platform.LocalResources
3636
import androidx.compose.ui.res.stringResource
37-
import androidx.compose.ui.text.font.FontWeight
38-
import androidx.compose.ui.text.style.TextAlign
3937
import androidx.compose.ui.text.style.TextOverflow
4038
import androidx.compose.ui.tooling.preview.Preview
4139
import androidx.compose.ui.unit.dp
@@ -46,11 +44,13 @@ import org.wordpress.android.R
4644
import org.wordpress.android.support.aibot.model.BotConversation
4745
import org.wordpress.android.support.aibot.util.formatRelativeTime
4846
import org.wordpress.android.support.aibot.util.generateSampleBotConversations
47+
import org.wordpress.android.support.common.ui.EmptyConversationsView
4948
import org.wordpress.android.ui.compose.theme.AppThemeM3
5049

5150
@OptIn(ExperimentalMaterial3Api::class)
5251
@Composable
53-
fun ConversationsListScreen(
52+
fun AIBotConversationsListScreen(
53+
snackbarHostState: SnackbarHostState,
5454
conversations: StateFlow<List<BotConversation>>,
5555
isLoading: Boolean,
5656
onConversationClick: (BotConversation) -> Unit,
@@ -59,6 +59,7 @@ fun ConversationsListScreen(
5959
onRefresh: () -> Unit,
6060
) {
6161
Scaffold(
62+
snackbarHost = { SnackbarHost(snackbarHostState) },
6263
topBar = {
6364
TopAppBar(
6465
title = { Text(stringResource(R.string.ai_bot_conversations_title)) },
@@ -109,49 +110,6 @@ fun ConversationsListScreen(
109110
}
110111
}
111112

112-
@Composable
113-
private fun EmptyConversationsView(
114-
modifier: Modifier,
115-
onCreateNewConversationClick: () -> Unit
116-
) {
117-
Column(
118-
modifier = modifier
119-
.fillMaxSize()
120-
.padding(32.dp),
121-
horizontalAlignment = Alignment.CenterHorizontally,
122-
verticalArrangement = Arrangement.Center
123-
) {
124-
Text(
125-
text = "💬",
126-
style = MaterialTheme.typography.displayLarge
127-
)
128-
129-
Spacer(modifier = Modifier.height(32.dp))
130-
131-
Text(
132-
text = stringResource(R.string.ai_bot_empty_conversations_title),
133-
style = MaterialTheme.typography.titleLarge,
134-
fontWeight = FontWeight.Bold,
135-
color = MaterialTheme.colorScheme.onSurface
136-
)
137-
138-
Spacer(modifier = Modifier.padding(8.dp))
139-
140-
Text(
141-
text = stringResource(R.string.ai_bot_empty_conversations_message),
142-
style = MaterialTheme.typography.bodyLarge,
143-
color = MaterialTheme.colorScheme.onSurfaceVariant,
144-
textAlign = TextAlign.Center
145-
)
146-
147-
Spacer(modifier = Modifier.padding(24.dp))
148-
149-
Button(onClick = onCreateNewConversationClick) {
150-
Text(text = stringResource(R.string.ai_bot_empty_conversations_button))
151-
}
152-
}
153-
}
154-
155113
@Composable
156114
private fun ShowConversationsList(
157115
modifier: Modifier,
@@ -233,9 +191,11 @@ private fun ConversationCard(
233191
@Composable
234192
private fun ConversationsScreenPreview() {
235193
val sampleConversations = MutableStateFlow(generateSampleBotConversations())
194+
val snackbarHostState = remember { SnackbarHostState() }
236195

237196
AppThemeM3(isDarkTheme = false) {
238-
ConversationsListScreen(
197+
AIBotConversationsListScreen(
198+
snackbarHostState = snackbarHostState,
239199
conversations = sampleConversations.asStateFlow(),
240200
isLoading = false,
241201
onConversationClick = { },
@@ -250,9 +210,11 @@ private fun ConversationsScreenPreview() {
250210
@Composable
251211
private fun ConversationsScreenPreviewDark() {
252212
val sampleConversations = MutableStateFlow(generateSampleBotConversations())
213+
val snackbarHostState = remember { SnackbarHostState() }
253214

254215
AppThemeM3(isDarkTheme = true) {
255-
ConversationsListScreen(
216+
AIBotConversationsListScreen(
217+
snackbarHostState = snackbarHostState,
256218
conversations = sampleConversations.asStateFlow(),
257219
isLoading = false,
258220
onConversationClick = { },
@@ -267,9 +229,11 @@ private fun ConversationsScreenPreviewDark() {
267229
@Composable
268230
private fun ConversationsScreenWordPressPreview() {
269231
val sampleConversations = MutableStateFlow(generateSampleBotConversations())
232+
val snackbarHostState = remember { SnackbarHostState() }
270233

271234
AppThemeM3(isDarkTheme = false, isJetpackApp = false) {
272-
ConversationsListScreen(
235+
AIBotConversationsListScreen(
236+
snackbarHostState = snackbarHostState,
273237
conversations = sampleConversations.asStateFlow(),
274238
isLoading = true,
275239
onConversationClick = { },
@@ -284,9 +248,11 @@ private fun ConversationsScreenWordPressPreview() {
284248
@Composable
285249
private fun ConversationsScreenPreviewWordPressDark() {
286250
val sampleConversations = MutableStateFlow(generateSampleBotConversations())
251+
val snackbarHostState = remember { SnackbarHostState() }
287252

288253
AppThemeM3(isDarkTheme = true, isJetpackApp = false) {
289-
ConversationsListScreen(
254+
AIBotConversationsListScreen(
255+
snackbarHostState = snackbarHostState,
290256
conversations = sampleConversations.asStateFlow(),
291257
isLoading = true,
292258
onConversationClick = { },
@@ -301,9 +267,11 @@ private fun ConversationsScreenPreviewWordPressDark() {
301267
@Composable
302268
private fun EmptyConversationsScreenPreview() {
303269
val emptyConversations = MutableStateFlow(emptyList<BotConversation>())
270+
val snackbarHostState = remember { SnackbarHostState() }
304271

305272
AppThemeM3(isDarkTheme = false) {
306-
ConversationsListScreen(
273+
AIBotConversationsListScreen(
274+
snackbarHostState = snackbarHostState,
307275
conversations = emptyConversations.asStateFlow(),
308276
isLoading = false,
309277
onConversationClick = { },
@@ -318,9 +286,11 @@ private fun EmptyConversationsScreenPreview() {
318286
@Composable
319287
private fun EmptyConversationsScreenPreviewDark() {
320288
val emptyConversations = MutableStateFlow(emptyList<BotConversation>())
289+
val snackbarHostState = remember { SnackbarHostState() }
321290

322291
AppThemeM3(isDarkTheme = true) {
323-
ConversationsListScreen(
292+
AIBotConversationsListScreen(
293+
snackbarHostState = snackbarHostState,
324294
conversations = emptyConversations.asStateFlow(),
325295
isLoading = false,
326296
onConversationClick = { },

0 commit comments

Comments
 (0)