From 2fa724ad09455accd0042a4072cedd3a2b0eafba Mon Sep 17 00:00:00 2001 From: daferya Date: Wed, 18 Dec 2024 12:01:02 +0100 Subject: [PATCH 1/4] feature:Add ChatsScreen to the worker UI --- .../quickfix/kaspresso/MainActivityTest.kt | 16 ++- .../workerMode/WorkerModeNavGraph.kt | 14 +- .../workerMode/messages/ChatsScreen.kt | 132 ++++++++++++++++++ .../workerMode/messages/MessagesScreen.kt | 20 --- .../workerMode/navigation/WorkerNavigation.kt | 12 +- 5 files changed, 158 insertions(+), 36 deletions(-) create mode 100644 app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/workerMode/messages/ChatsScreen.kt delete mode 100644 app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/workerMode/messages/MessagesScreen.kt diff --git a/app/src/androidTest/java/com/arygm/quickfix/kaspresso/MainActivityTest.kt b/app/src/androidTest/java/com/arygm/quickfix/kaspresso/MainActivityTest.kt index 1fd17341..72ac854b 100644 --- a/app/src/androidTest/java/com/arygm/quickfix/kaspresso/MainActivityTest.kt +++ b/app/src/androidTest/java/com/arygm/quickfix/kaspresso/MainActivityTest.kt @@ -246,8 +246,9 @@ class MainActivityTest : TestCase() { composeTestRule.onNodeWithTag(C.Tag.personalInfoScreencontinueButton).performClick() composeTestRule.onNodeWithTag(professionalInfoScreenCategoryField).performClick() + Log.e("TestLog", "debut test") - // Select the first category + // Select the first category composeTestRule .onNodeWithTag(C.Tag.professionalInfoScreenCategoryDropdownMenuItem + 0) .performClick() @@ -358,19 +359,26 @@ class MainActivityTest : TestCase() { composeTestRule.onNodeWithText("Support").assertIsDisplayed() composeTestRule.onNodeWithText("Legal").assertIsDisplayed() composeTestRule.onNodeWithText("Log out").assertIsDisplayed() - composeTestRule.waitUntil("find the AccountConfigurationOption", timeoutMillis = 20000) { + Log.e("TestLog", "avant probbleme") + + composeTestRule.waitUntil("find the AccountConfigurationOption", timeoutMillis = 20000) { composeTestRule .onAllNodesWithTag("AccountConfigurationOption") .fetchSemanticsNodes() .isNotEmpty() } - updateAccountConfigurationAndVerify( + Log.e("TestLog", "mid probbleme") + + updateAccountConfigurationAndVerify( composeTestRule, "Ramo", "Hatimo", "28/10/2004", "Ramo Hatimo", 2) composeTestRule.waitUntil("find the switch", timeoutMillis = 20000) { composeTestRule.onAllNodesWithTag(C.Tag.buttonSwitch).fetchSemanticsNodes().isNotEmpty() } - composeTestRule.onNodeWithTag(C.Tag.buttonSwitch, useUnmergedTree = true).performClick() + Log.e("TestLog", "apres probbleme") + + composeTestRule.onNodeWithTag(C.Tag.buttonSwitch, useUnmergedTree = true).performClick() } + Log.e("TestLog", "fin test") } @Test diff --git a/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/workerMode/WorkerModeNavGraph.kt b/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/workerMode/WorkerModeNavGraph.kt index 1ddda2a9..f4fea7ac 100644 --- a/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/workerMode/WorkerModeNavGraph.kt +++ b/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/workerMode/WorkerModeNavGraph.kt @@ -19,6 +19,7 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import com.arygm.quickfix.model.account.AccountViewModel +import com.arygm.quickfix.model.messaging.ChatViewModel import com.arygm.quickfix.model.offline.small.PreferencesViewModel import com.arygm.quickfix.model.offline.small.PreferencesViewModelUserProfile import com.arygm.quickfix.model.switchModes.ModeViewModel @@ -28,7 +29,7 @@ import com.arygm.quickfix.ui.navigation.NavigationActions import com.arygm.quickfix.ui.uiMode.appContentUI.userModeUI.profile.AccountConfigurationScreen import com.arygm.quickfix.ui.uiMode.appContentUI.userModeUI.profile.WorkerProfileScreen import com.arygm.quickfix.ui.uiMode.appContentUI.workerMode.announcements.AnnouncementsScreen -import com.arygm.quickfix.ui.uiMode.appContentUI.workerMode.messages.MessagesScreen +import com.arygm.quickfix.ui.uiMode.appContentUI.workerMode.messages.ChatsScreen import com.arygm.quickfix.ui.uiMode.workerMode.home.HomeScreen import com.arygm.quickfix.ui.uiMode.workerMode.navigation.WORKER_TOP_LEVEL_DESTINATIONS import com.arygm.quickfix.ui.uiMode.workerMode.navigation.WorkerRoute @@ -44,6 +45,7 @@ fun WorkerModeNavGraph( preferencesViewModel: PreferencesViewModel, accountViewModel: AccountViewModel, rootMainNavigationActions: NavigationActions, + chatViewModel: ChatViewModel, userPreferencesViewModel: PreferencesViewModelUserProfile ) { val workerNavController = rememberNavController() @@ -88,8 +90,8 @@ fun WorkerModeNavGraph( startDestination = startDestination, modifier = Modifier.padding(innerPadding)) { composable(WorkerRoute.HOME) { HomeNavHost(onScreenChange = { currentScreen = it }) } - composable(WorkerRoute.MESSAGES) { - MessagesNavHost(onScreenChange = { currentScreen = it }) + composable(WorkerRoute.CHATS) { + MessagesNavHost(onScreenChange = { currentScreen = it }, preferencesViewModel,workerNavigationActions, chatViewModel) } composable(WorkerRoute.ANNOUNCEMENT) { AnnouncementsNavHost(onScreenChange = { currentScreen = it }) @@ -110,14 +112,14 @@ fun WorkerModeNavGraph( } @Composable -fun MessagesNavHost(onScreenChange: (String) -> Unit) { +fun MessagesNavHost(onScreenChange: (String) -> Unit, pre: PreferencesViewModel, workerNavigationActions: NavigationActions, chatViewModel: ChatViewModel) { val dashboardNavController = rememberNavController() val navigationActions = remember { NavigationActions(dashboardNavController) } LaunchedEffect(navigationActions.currentScreen) { onScreenChange(navigationActions.currentScreen) } - NavHost(navController = dashboardNavController, startDestination = WorkerScreen.MESSAGES) { - composable(WorkerScreen.MESSAGES) { MessagesScreen() } + NavHost(navController = dashboardNavController, startDestination = WorkerScreen.CHATS) { + composable(WorkerScreen.CHATS) { ChatsScreen (chatViewModel,pre,navigationActions) } } } diff --git a/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/workerMode/messages/ChatsScreen.kt b/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/workerMode/messages/ChatsScreen.kt new file mode 100644 index 00000000..57e7d00f --- /dev/null +++ b/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/workerMode/messages/ChatsScreen.kt @@ -0,0 +1,132 @@ +package com.arygm.quickfix.ui.uiMode.appContentUI.workerMode.messages + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +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.material.icons.Icons +import androidx.compose.material.icons.outlined.Search +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.* +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.runtime.* +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import com.arygm.quickfix.model.messaging.Chat +import com.arygm.quickfix.model.messaging.ChatViewModel +import com.arygm.quickfix.model.offline.small.PreferencesViewModel +import com.arygm.quickfix.ui.navigation.NavigationActions +import com.arygm.quickfix.utils.loadUserId +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ChatsScreen( + chatViewModel: ChatViewModel, + preferencesViewModel: PreferencesViewModel, + navigationActions: NavigationActions +) { + var userId by remember { mutableStateOf("") } + val chats = chatViewModel.chats.collectAsState().value + val coroutineScope = rememberCoroutineScope() + + // Charge l'utilisateur ID en utilisant PreferencesViewModel + LaunchedEffect(Unit) { + userId = loadUserId(preferencesViewModel) + } + + Scaffold( + topBar = { + TopAppBar( + title = { + Text( + text = "Chats", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold + ) + }, + modifier = Modifier.fillMaxWidth(), + colors = TopAppBarDefaults.topAppBarColors(containerColor = colorScheme.surface) + ) + } + ) { paddingValues -> + Box( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + ) { + if (chats.isEmpty()) { + // Placeholder si aucun chat n'est disponible + Text( + text = "No chats available.", + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.align(Alignment.Center), + textAlign = TextAlign.Center + ) + } else { + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp, vertical = 8.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + items(chats) { chat -> + ChatItem( + chat = chat, + onClick = { + coroutineScope.launch { + chatViewModel.selectChat(chat) // Met à jour le chat sélectionné + navigationActions.navigateTo("messageScreen") // Navigue vers l'écran des messages + } + } + ) + } + } + } + } + } +} + +@Composable +fun ChatItem(chat: Chat, onClick: () -> Unit) { + Surface( + modifier = Modifier + .fillMaxWidth() + .clickable { onClick() }, + shape = MaterialTheme.shapes.medium, + color = MaterialTheme.colorScheme.surface, + tonalElevation = 4.dp + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + text = "Chat with ${chat.workeruid}", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold + ) + Text( + text = "Last message: ${ + if (chat.messages.isNotEmpty()) chat.messages.last().content else "No messages yet" + }", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/workerMode/messages/MessagesScreen.kt b/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/workerMode/messages/MessagesScreen.kt deleted file mode 100644 index 0ca5dc80..00000000 --- a/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/workerMode/messages/MessagesScreen.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.arygm.quickfix.ui.uiMode.appContentUI.workerMode.messages - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import com.arygm.quickfix.ui.theme.poppinsTypography - -@Composable -fun MessagesScreen() { - Column( - modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally) { - Text("Messages Screen", style = poppinsTypography.headlineLarge) - } -} diff --git a/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/workerMode/navigation/WorkerNavigation.kt b/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/workerMode/navigation/WorkerNavigation.kt index ed270679..342e5bd5 100644 --- a/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/workerMode/navigation/WorkerNavigation.kt +++ b/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/workerMode/navigation/WorkerNavigation.kt @@ -11,14 +11,14 @@ import com.arygm.quickfix.ui.navigation.TopLevelDestination object WorkerRoute { const val HOME = "Home" const val ANNOUNCEMENT = "Announcement" - const val MESSAGES = "Messages" + const val CHATS = "Chats" const val PROFILE = "Profile" } object WorkerScreen { const val HOME = "Home Screen" const val ANNOUNCEMENT = "Announcement Screen" - const val MESSAGES = "Messages Screen" + const val CHATS = "Chats Screen" const val PROFILE = "Profile Screen" const val ACCOUNT_CONFIGURATION = "Account configuration Screen" } @@ -29,9 +29,9 @@ object WorkerTopLevelDestinations { val ANNOUNCEMENT = TopLevelDestination( route = WorkerRoute.ANNOUNCEMENT, icon = Icons.Outlined.Campaign, textId = "Announcement") - val MESSAGES = + val CHATS = TopLevelDestination( - route = WorkerRoute.MESSAGES, icon = Icons.Filled.MailOutline, textId = "Messages") + route = WorkerRoute.CHATS, icon = Icons.Filled.MailOutline, textId = "Messages") val PROFILE = TopLevelDestination( route = WorkerRoute.PROFILE, icon = Icons.Filled.PersonOutline, textId = "Profile") @@ -41,14 +41,14 @@ val WORKER_TOP_LEVEL_DESTINATIONS = listOf( WorkerTopLevelDestinations.HOME, WorkerTopLevelDestinations.ANNOUNCEMENT, - WorkerTopLevelDestinations.MESSAGES, + WorkerTopLevelDestinations.CHATS, WorkerTopLevelDestinations.PROFILE) val getBottomBarIdWorker: (String) -> Int = { route -> when (route) { WorkerRoute.HOME -> 1 WorkerRoute.ANNOUNCEMENT -> 2 - WorkerRoute.MESSAGES -> 3 + WorkerRoute.CHATS -> 3 WorkerRoute.PROFILE -> 4 else -> -1 // Should not happen } From 6f4a7a8dddaa18993d97d247df9ca38bb2a37464 Mon Sep 17 00:00:00 2001 From: daferya Date: Wed, 18 Dec 2024 22:12:07 +0100 Subject: [PATCH 2/4] feature:Add ChatsScreen to the worker UI --- .../arygm/quickfix/ui/chat/ChatsScreenTest.kt | 206 +++++++++++++++ .../ui/home/MessageUserNoModeScreenTest.kt | 9 - .../uiMode/appContentUI/AppContentNavGraph.kt | 4 +- .../userModeUI/UserModeNavGraph.kt | 2 - .../userModeUI/home/MessagesScreen.kt | 1 - .../workerMode/WorkerModeNavGraph.kt | 21 +- .../workerMode/messages/ChatsScreen.kt | 247 +++++++++++++----- .../workerMode/navigation/WorkerNavigation.kt | 6 +- .../model/messaging/ChatViewModelTest.kt | 1 + 9 files changed, 415 insertions(+), 82 deletions(-) create mode 100644 app/src/androidTest/java/com/arygm/quickfix/ui/chat/ChatsScreenTest.kt diff --git a/app/src/androidTest/java/com/arygm/quickfix/ui/chat/ChatsScreenTest.kt b/app/src/androidTest/java/com/arygm/quickfix/ui/chat/ChatsScreenTest.kt new file mode 100644 index 00000000..5e53a50f --- /dev/null +++ b/app/src/androidTest/java/com/arygm/quickfix/ui/chat/ChatsScreenTest.kt @@ -0,0 +1,206 @@ +package com.arygm.quickfix.ui.chat + +import android.util.Log +import androidx.compose.ui.test.* +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.arygm.quickfix.model.account.Account +import com.arygm.quickfix.model.account.AccountRepository +import com.arygm.quickfix.model.account.AccountViewModel +import com.arygm.quickfix.model.messaging.Chat +import com.arygm.quickfix.model.messaging.ChatRepository +import com.arygm.quickfix.model.messaging.ChatViewModel +import com.arygm.quickfix.model.messaging.Message +import com.arygm.quickfix.model.offline.small.PreferencesRepository +import com.arygm.quickfix.model.offline.small.PreferencesViewModel +import com.arygm.quickfix.ui.navigation.NavigationActions +import com.arygm.quickfix.ui.uiMode.appContentUI.workerMode.messages.ChatsScreen +import com.arygm.quickfix.utils.* +import com.google.firebase.Timestamp +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import org.junit.* +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +@RunWith(AndroidJUnit4::class) +@OptIn(ExperimentalCoroutinesApi::class) +class ChatsScreenTest { + + @get:Rule + val composeTestRule = createComposeRule() + + @Mock + private lateinit var accountRepository: AccountRepository + private lateinit var accountViewModel: AccountViewModel + + @Mock + private lateinit var chatRepository: ChatRepository + private lateinit var chatViewModel: ChatViewModel + + @Mock + private lateinit var preferencesRepository: PreferencesRepository + private lateinit var preferencesViewModel: PreferencesViewModel + + @Mock + private lateinit var navigationActions: NavigationActions + + private val testUserId = "testUserId" + + private val testChats = listOf( + Chat( + chatId = "1", + workeruid = "worker1", + useruid = "user1", + quickFixUid = "quickfix1", + messages = listOf( + Message("msg1", "user1", "Hello there!", Timestamp.now()), + Message("msg2", "worker1", "Hi!", Timestamp.now()) + ) + ), + Chat( + chatId = "2", + workeruid = "worker2", + useruid = "user2", + quickFixUid = "quickfix2", + messages = listOf( + Message("msg1", "user2", "Another message", Timestamp.now()), + Message("msg2", "worker2", "Reply to message", Timestamp.now()) + ) + ) + ) + + @Before + fun setup() { + MockitoAnnotations.openMocks(this) + + accountViewModel = AccountViewModel(accountRepository) + chatViewModel = ChatViewModel(chatRepository) + preferencesViewModel = PreferencesViewModel(preferencesRepository) + + // Utiliser flowOf(...) pour chaque préférence afin d'émettre une seule valeur et terminer + whenever(preferencesRepository.getPreferenceByKey(eq(UID_KEY))).thenReturn(flowOf(testUserId)) + whenever(preferencesRepository.getPreferenceByKey(eq(APP_MODE_KEY))).thenReturn(flowOf("USER")) + whenever(preferencesRepository.getPreferenceByKey(eq(FIRST_NAME_KEY))).thenReturn(flowOf("Tester")) + whenever(preferencesRepository.getPreferenceByKey(eq(LAST_NAME_KEY))).thenReturn(flowOf("User")) + whenever(preferencesRepository.getPreferenceByKey(eq(EMAIL_KEY))).thenReturn(flowOf("test@example.com")) + whenever(preferencesRepository.getPreferenceByKey(eq(BIRTH_DATE_KEY))).thenReturn(flowOf("01/01/2000")) + whenever(preferencesRepository.getPreferenceByKey(eq(IS_WORKER_KEY))).thenReturn(flowOf(false)) + + val mainAccount = Account( + uid = testUserId, + firstName = "Tester", + lastName = "User", + email = "test@example.com", + birthDate = Timestamp.now(), + isWorker = false, + activeChats = listOf("1", "2") + ) + + whenever(accountRepository.getAccountById(eq(testUserId), any(), any())).thenAnswer { + val onSuccess = it.arguments[1] as (Account?) -> Unit + onSuccess(mainAccount) + } + + // user1 + whenever(accountRepository.getAccountById(eq("user1"), any(), any())).thenAnswer { + val onSuccess = it.arguments[1] as (Account?) -> Unit + onSuccess( + Account( + uid = "user1", + firstName = "John", + lastName = "Doe", + email = "john@example.com", + birthDate = Timestamp.now(), + isWorker = false, + activeChats = emptyList() + ) + ) + } + + // user2 + whenever(accountRepository.getAccountById(eq("user2"), any(), any())).thenAnswer { + val onSuccess = it.arguments[1] as (Account?) -> Unit + onSuccess( + Account( + uid = "user2", + firstName = "Jane", + lastName = "Smith", + email = "jane@example.com", + birthDate = Timestamp.now(), + isWorker = false, + activeChats = emptyList() + ) + ) + } + + // Mock des chats, appelés en interne par le ChatViewModel + + } + + @Test + fun chatsScreen_displaysChatsAndNavigatesOnClick() = runTest { + whenever(chatRepository.getChatByChatUid(eq("1"), any(), any())).thenAnswer { + val onSuccess = it.arguments[1] as (Chat?) -> Unit + onSuccess(testChats[0]) + } + whenever(chatRepository.getChatByChatUid(eq("2"), any(), any())).thenAnswer { + val onSuccess = it.arguments[1] as (Chat?) -> Unit + onSuccess(testChats[1]) + } + // runTest pour pouvoir appeler du suspend si nécessaire + composeTestRule.setContent { + ChatsScreen( + navigationActions = navigationActions, + accountViewModel = accountViewModel, + chatViewModel = chatViewModel, + preferencesViewModel = preferencesViewModel + ) + } + + // Vérifie que "John" et "Jane" s'affichent + composeTestRule.onNodeWithText("John").assertExists() + composeTestRule.onNodeWithText("Jane").assertExists() + + // Clique sur "John" + composeTestRule.onNodeWithText("John").performClick() + + // Vérifie la navigation + verify(navigationActions).navigateTo(any()) + } + + @Test + fun chatsScreen_filtersChatsBasedOnSearchQuery() = runTest { + whenever(chatRepository.getChatByChatUid(eq("1"), any(), any())).thenAnswer { + val onSuccess = it.arguments[1] as (Chat?) -> Unit + onSuccess(testChats[0]) + } + whenever(chatRepository.getChatByChatUid(eq("2"), any(), any())).thenAnswer { + val onSuccess = it.arguments[1] as (Chat?) -> Unit + onSuccess(testChats[1]) + } + composeTestRule.setContent { + ChatsScreen( + navigationActions = navigationActions, + accountViewModel = accountViewModel, + chatViewModel = chatViewModel, + preferencesViewModel = preferencesViewModel + ) + } + + // Entrer "Jane" + composeTestRule.onNodeWithTag("customSearchField").performTextInput("Jane") + + // Vérifie que seul "Jane" est visible + composeTestRule.onAllNodesWithText("Jane") + .filter(hasTestTag("ChatItem")) + .assertCountEquals(1) + composeTestRule.onNodeWithText("John").assertDoesNotExist() + } +} diff --git a/app/src/androidTest/java/com/arygm/quickfix/ui/home/MessageUserNoModeScreenTest.kt b/app/src/androidTest/java/com/arygm/quickfix/ui/home/MessageUserNoModeScreenTest.kt index 552f9920..a60c5df5 100644 --- a/app/src/androidTest/java/com/arygm/quickfix/ui/home/MessageUserNoModeScreenTest.kt +++ b/app/src/androidTest/java/com/arygm/quickfix/ui/home/MessageUserNoModeScreenTest.kt @@ -175,7 +175,6 @@ class MessageUserNoModeScreenTest { chatViewModel = chatViewModel, navigationActions = navigationActions, quickFixViewModel = quickFixViewModel, - modeViewModel = modeViewModel, preferencesViewModel = preferencesViewModel) } } @@ -192,7 +191,6 @@ class MessageUserNoModeScreenTest { chatViewModel = chatViewModel, navigationActions = navigationActions, quickFixViewModel = quickFixViewModel, - modeViewModel = modeViewModel, preferencesViewModel = preferencesViewModel) } } @@ -211,7 +209,6 @@ class MessageUserNoModeScreenTest { chatViewModel = chatViewModel, navigationActions = navigationActions, quickFixViewModel = quickFixViewModel, - modeViewModel = modeViewModel, preferencesViewModel = preferencesViewModel) } } @@ -246,7 +243,6 @@ class MessageUserNoModeScreenTest { chatViewModel = chatViewModel, navigationActions = navigationActions, quickFixViewModel = quickFixViewModel, - modeViewModel = modeViewModel, preferencesViewModel = preferencesViewModel) } } @@ -285,7 +281,6 @@ class MessageUserNoModeScreenTest { chatViewModel = chatViewModel, navigationActions = navigationActions, quickFixViewModel = quickFixViewModel, - modeViewModel = modeViewModel, preferencesViewModel = preferencesViewModel) } } @@ -322,7 +317,6 @@ class MessageUserNoModeScreenTest { chatViewModel = chatViewModel, navigationActions = navigationActions, quickFixViewModel = quickFixViewModel, - modeViewModel = modeViewModel, preferencesViewModel = preferencesViewModel) } } @@ -365,7 +359,6 @@ class MessageUserNoModeScreenTest { chatViewModel = chatViewModel, navigationActions = navigationActions, quickFixViewModel = quickFixViewModel, - modeViewModel = modeViewModel, preferencesViewModel = preferencesViewModel) } } @@ -406,7 +399,6 @@ class MessageUserNoModeScreenTest { chatViewModel = chatViewModel, navigationActions = navigationActions, quickFixViewModel = quickFixViewModel, - modeViewModel = modeViewModel, preferencesViewModel = preferencesViewModel) } } @@ -474,7 +466,6 @@ class MessageUserNoModeScreenTest { chatViewModel = chatViewModel, navigationActions = navigationActions, quickFixViewModel = quickFixViewModel, - modeViewModel = modeViewModel, preferencesViewModel = preferencesViewModel) } } diff --git a/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/AppContentNavGraph.kt b/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/AppContentNavGraph.kt index 228bb35d..8a58ad2c 100644 --- a/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/AppContentNavGraph.kt +++ b/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/AppContentNavGraph.kt @@ -108,7 +108,9 @@ fun AppContentNavGraph( preferencesViewModel, accountViewModel, rootNavigationActions, - userPreferencesViewModel) + chatViewModel, + userPreferencesViewModel, + quickFixViewModel) } } } diff --git a/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/userModeUI/UserModeNavGraph.kt b/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/userModeUI/UserModeNavGraph.kt index e7e0f5dd..4ff023d1 100644 --- a/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/userModeUI/UserModeNavGraph.kt +++ b/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/userModeUI/UserModeNavGraph.kt @@ -266,7 +266,6 @@ fun HomeNavHost( chatViewModel = chatViewModel, navigationActions = navigationActions, quickFixViewModel = quickFixViewModel, - modeViewModel = modeViewModel, preferencesViewModel = preferencesViewModel, ) } @@ -440,7 +439,6 @@ fun SearchNavHost( chatViewModel = chatViewModel, navigationActions = navigationActions, quickFixViewModel = quickFixViewModel, - modeViewModel = modeViewModel, preferencesViewModel = preferencesViewModel, ) } diff --git a/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/userModeUI/home/MessagesScreen.kt b/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/userModeUI/home/MessagesScreen.kt index 5a561226..db2efd32 100644 --- a/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/userModeUI/home/MessagesScreen.kt +++ b/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/userModeUI/home/MessagesScreen.kt @@ -50,7 +50,6 @@ fun MessageScreen( chatViewModel: ChatViewModel, navigationActions: NavigationActions, quickFixViewModel: QuickFixViewModel, - modeViewModel: ModeViewModel, preferencesViewModel: PreferencesViewModel, ) { var userId by remember { mutableStateOf("") } diff --git a/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/workerMode/WorkerModeNavGraph.kt b/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/workerMode/WorkerModeNavGraph.kt index f4fea7ac..88aaf88c 100644 --- a/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/workerMode/WorkerModeNavGraph.kt +++ b/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/workerMode/WorkerModeNavGraph.kt @@ -22,10 +22,12 @@ import com.arygm.quickfix.model.account.AccountViewModel import com.arygm.quickfix.model.messaging.ChatViewModel import com.arygm.quickfix.model.offline.small.PreferencesViewModel import com.arygm.quickfix.model.offline.small.PreferencesViewModelUserProfile +import com.arygm.quickfix.model.quickfix.QuickFixViewModel import com.arygm.quickfix.model.switchModes.ModeViewModel import com.arygm.quickfix.ui.elements.QuickFixOfflineBar import com.arygm.quickfix.ui.navigation.BottomNavigationMenu import com.arygm.quickfix.ui.navigation.NavigationActions +import com.arygm.quickfix.ui.uiMode.appContentUI.userModeUI.home.MessageScreen import com.arygm.quickfix.ui.uiMode.appContentUI.userModeUI.profile.AccountConfigurationScreen import com.arygm.quickfix.ui.uiMode.appContentUI.userModeUI.profile.WorkerProfileScreen import com.arygm.quickfix.ui.uiMode.appContentUI.workerMode.announcements.AnnouncementsScreen @@ -46,13 +48,16 @@ fun WorkerModeNavGraph( accountViewModel: AccountViewModel, rootMainNavigationActions: NavigationActions, chatViewModel: ChatViewModel, - userPreferencesViewModel: PreferencesViewModelUserProfile -) { + userPreferencesViewModel: PreferencesViewModelUserProfile, + quickFixViewModel: QuickFixViewModel, + + ) { val workerNavController = rememberNavController() val workerNavigationActions = remember { NavigationActions(workerNavController) } var currentScreen by remember { mutableStateOf(null) } val shouldShowBottomBar by remember { - derivedStateOf { currentScreen?.let { it != WorkerScreen.ACCOUNT_CONFIGURATION } ?: true } + derivedStateOf { currentScreen?.let { it != WorkerScreen.ACCOUNT_CONFIGURATION && it != WorkerScreen.MESSAGES} ?: true } + } val startDestination by modeViewModel.onSwitchStartDestWorker.collectAsState() var showBottomBar by remember { mutableStateOf(false) } @@ -91,7 +96,7 @@ fun WorkerModeNavGraph( modifier = Modifier.padding(innerPadding)) { composable(WorkerRoute.HOME) { HomeNavHost(onScreenChange = { currentScreen = it }) } composable(WorkerRoute.CHATS) { - MessagesNavHost(onScreenChange = { currentScreen = it }, preferencesViewModel,workerNavigationActions, chatViewModel) + MessagesNavHost(onScreenChange = { currentScreen = it }, preferencesViewModel,workerNavigationActions, chatViewModel,quickFixViewModel,accountViewModel) } composable(WorkerRoute.ANNOUNCEMENT) { AnnouncementsNavHost(onScreenChange = { currentScreen = it }) @@ -112,15 +117,19 @@ fun WorkerModeNavGraph( } @Composable -fun MessagesNavHost(onScreenChange: (String) -> Unit, pre: PreferencesViewModel, workerNavigationActions: NavigationActions, chatViewModel: ChatViewModel) { +fun MessagesNavHost(onScreenChange: (String) -> Unit, pre: PreferencesViewModel, workerNavigationActions: NavigationActions, chatViewModel: ChatViewModel,quickFixViewModel: QuickFixViewModel,accountViewModel: AccountViewModel) { val dashboardNavController = rememberNavController() val navigationActions = remember { NavigationActions(dashboardNavController) } LaunchedEffect(navigationActions.currentScreen) { onScreenChange(navigationActions.currentScreen) } NavHost(navController = dashboardNavController, startDestination = WorkerScreen.CHATS) { - composable(WorkerScreen.CHATS) { ChatsScreen (chatViewModel,pre,navigationActions) } + composable(WorkerScreen.CHATS) { ChatsScreen (navigationActions,accountViewModel,chatViewModel,pre ) } + composable(WorkerScreen.MESSAGES) { + MessageScreen(chatViewModel, navigationActions, quickFixViewModel, pre) + } } + } @Composable diff --git a/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/workerMode/messages/ChatsScreen.kt b/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/workerMode/messages/ChatsScreen.kt index 57e7d00f..03824a50 100644 --- a/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/workerMode/messages/ChatsScreen.kt +++ b/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/workerMode/messages/ChatsScreen.kt @@ -1,5 +1,8 @@ package com.arygm.quickfix.ui.uiMode.appContentUI.workerMode.messages +import android.util.Log +import androidx.compose.foundation.Image +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize @@ -11,122 +14,244 @@ 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.lazy.itemsIndexed +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Clear import androidx.compose.material.icons.outlined.Search import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.* import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.runtime.* +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel +import com.arygm.quickfix.R +import com.arygm.quickfix.model.account.AccountViewModel import com.arygm.quickfix.model.messaging.Chat import com.arygm.quickfix.model.messaging.ChatViewModel import com.arygm.quickfix.model.offline.small.PreferencesViewModel +import com.arygm.quickfix.model.profile.ProfileViewModel +import com.arygm.quickfix.ui.elements.QuickFixTextFieldCustom import com.arygm.quickfix.ui.navigation.NavigationActions +import com.arygm.quickfix.ui.theme.poppinsTypography +import com.arygm.quickfix.ui.uiMode.appContentUI.userModeUI.navigation.UserScreen +import com.arygm.quickfix.ui.uiMode.workerMode.navigation.WorkerScreen +import com.arygm.quickfix.utils.loadAppMode import com.arygm.quickfix.utils.loadUserId import kotlinx.coroutines.launch +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale @OptIn(ExperimentalMaterial3Api::class) @Composable fun ChatsScreen( + navigationActions: NavigationActions, + accountViewModel: AccountViewModel, chatViewModel: ChatViewModel, - preferencesViewModel: PreferencesViewModel, - navigationActions: NavigationActions + preferencesViewModel: PreferencesViewModel ) { - var userId by remember { mutableStateOf("") } - val chats = chatViewModel.chats.collectAsState().value + var mode by remember { mutableStateOf("") } + var uid by remember { mutableStateOf("") } + var chats by remember { mutableStateOf(emptyList()) } + var searchQuery by remember { mutableStateOf("") } val coroutineScope = rememberCoroutineScope() - // Charge l'utilisateur ID en utilisant PreferencesViewModel + // Map pour stocker useruid -> firstName + val userFirstNameMap = remember { mutableStateMapOf() } + LaunchedEffect(Unit) { - userId = loadUserId(preferencesViewModel) + mode = loadAppMode(preferencesViewModel) + uid = loadUserId(preferencesViewModel) + + accountViewModel.fetchUserAccount(uid) { account -> + account?.activeChats?.forEach { chatUid -> + coroutineScope.launch { + chatViewModel.getChatByChatUid( + chatUid, + onSuccess = { chat -> + if (chat != null) { + chats = chats + chat + // Récupérer le firstName de useruid pour chaque chat + accountViewModel.fetchUserAccount(chat.useruid) { userAccount -> + userFirstNameMap[chat.useruid] = + userAccount?.firstName ?: "Unknown" + } + } + }, + onFailure = { e -> Log.e("ChatScreen", "Failed: ${e.message}") } + ) + } + } + } } - Scaffold( - topBar = { - TopAppBar( - title = { + BoxWithConstraints { + val widthRatio = maxWidth.value / 411f + val heightRatio = maxHeight.value / 860f + + Scaffold( + containerColor = colorScheme.background, + topBar = { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp * widthRatio, vertical = 8.dp * heightRatio) + ) { Text( - text = "Chats", - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.Bold + text = "Messages", + style = poppinsTypography.headlineLarge.copy(fontWeight = FontWeight.Bold), + color = colorScheme.onBackground, + modifier = Modifier.padding(bottom = 8.dp * heightRatio) ) - }, - modifier = Modifier.fillMaxWidth(), - colors = TopAppBarDefaults.topAppBarColors(containerColor = colorScheme.surface) - ) - } - ) { paddingValues -> - Box( - modifier = Modifier - .fillMaxSize() - .padding(paddingValues) - ) { - if (chats.isEmpty()) { - // Placeholder si aucun chat n'est disponible - Text( - text = "No chats available.", - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.align(Alignment.Center), - textAlign = TextAlign.Center - ) - } else { + + Column( + modifier = Modifier + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + QuickFixTextFieldCustom( + value = searchQuery, + onValueChange = { searchQuery = it }, + showLeadingIcon = { true }, + leadingIcon = Icons.Outlined.Search, + placeHolderText = "Search", + modifier = Modifier + .fillMaxWidth() + .height(40.dp * heightRatio).testTag("customSearchField"), + widthField = 380.dp * widthRatio + )} + } + }, + content = { padding -> LazyColumn( modifier = Modifier .fillMaxSize() - .padding(horizontal = 16.dp, vertical = 8.dp), - verticalArrangement = Arrangement.spacedBy(12.dp) + .padding(padding) ) { - items(chats) { chat -> + // Filtrer les chats en comparant avec userFirstName + val filteredChats = chats.filter { chat -> + val userFirstName = userFirstNameMap[chat.useruid] ?: "" + chat.workeruid == uid &&userFirstName.contains(searchQuery, ignoreCase = true) + } + + itemsIndexed(filteredChats) { index, chat -> + // Chat Item + ChatItem( chat = chat, + userFirstName = userFirstNameMap[chat.useruid] ?: "Loading...", onClick = { - coroutineScope.launch { - chatViewModel.selectChat(chat) // Met à jour le chat sélectionné - navigationActions.navigateTo("messageScreen") // Navigue vers l'écran des messages - } - } + chatViewModel.selectChat(chat) + if (mode == "USER") navigationActions.navigateTo(UserScreen.MESSAGES) + else navigationActions.navigateTo(WorkerScreen.MESSAGES) + }, + widthRatio = widthRatio, + heightRatio = heightRatio ) + + // Ajouter un Divider sauf pour le dernier élément + if (index < filteredChats.size - 1) { + Column (modifier = Modifier.padding(start = 32 .dp * widthRatio )){ + Divider( + color = colorScheme.onSurface.copy(alpha = 0.2f), // Couleur de la ligne + thickness = 1.dp, // Épaisseur de la ligne + modifier = Modifier.padding(vertical = 4.dp * heightRatio) // Espacement autour du Divider + .testTag("Divider") // Ajout du testTag ici + + )} + } } } + } - } + ) } } @Composable -fun ChatItem(chat: Chat, onClick: () -> Unit) { - Surface( +fun ChatItem( + chat: Chat, + userFirstName: String, + onClick: () -> Unit, + widthRatio: Float, + heightRatio: Float +) { + Row( modifier = Modifier .fillMaxWidth() - .clickable { onClick() }, - shape = MaterialTheme.shapes.medium, - color = MaterialTheme.colorScheme.surface, - tonalElevation = 4.dp + .testTag("ChatItem") + .clickable { onClick() } + .padding(vertical = 8.dp * heightRatio, horizontal = 12.dp * widthRatio), + verticalAlignment = Alignment.CenterVertically ) { - Column( + // Profile Picture Placeholder + Image( + painter = painterResource(id = R.drawable.placeholder_worker), + contentDescription = "Profile Picture", + contentScale = ContentScale.Crop, modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { + .size(48.dp * widthRatio) + .clip(CircleShape) + .background(Color.Gray) + ) + + Spacer(modifier = Modifier.width(8.dp * widthRatio)) + + // Chat Info + Column(modifier = Modifier.weight(1f)) { Text( - text = "Chat with ${chat.workeruid}", - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.Bold + text = userFirstName, + style = poppinsTypography.bodyMedium.copy(fontWeight = FontWeight.Bold), + color = colorScheme.onBackground ) Text( - text = "Last message: ${ - if (chat.messages.isNotEmpty()) chat.messages.last().content else "No messages yet" - }", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant + text = chat.messages.lastOrNull()?.content ?: "No messages yet", + style = poppinsTypography.bodySmall, + color = colorScheme.onSurface, + maxLines = 1, + overflow = TextOverflow.Ellipsis ) } + + Spacer(modifier = Modifier.width(8.dp * widthRatio)) + + // Timestamp + val formattedDate = formatMessageTimestamp(chat.messages.lastOrNull()?.timestamp) + Text( + text = formattedDate, + style = MaterialTheme.typography.labelSmall, + color = colorScheme.onSurfaceVariant + ) + } +} + + +// Helper function to format timestamp +fun formatMessageTimestamp(timestamp: com.google.firebase.Timestamp?): String { + if (timestamp == null) return "--:--" + + val now = Calendar.getInstance() + val messageTime = Calendar.getInstance().apply { time = timestamp.toDate() } + + return if (now.get(Calendar.YEAR) == messageTime.get(Calendar.YEAR) && + now.get(Calendar.DAY_OF_YEAR) == messageTime.get(Calendar.DAY_OF_YEAR) + ) { + // If the message is from today, show hours and minutes + SimpleDateFormat("HH:mm", Locale.getDefault()).format(messageTime.time) + } else { + // If the message is from a different day, show day and month + SimpleDateFormat("dd MMM", Locale.getDefault()).format(messageTime.time) } } \ No newline at end of file diff --git a/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/workerMode/navigation/WorkerNavigation.kt b/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/workerMode/navigation/WorkerNavigation.kt index 342e5bd5..65cd0e1a 100644 --- a/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/workerMode/navigation/WorkerNavigation.kt +++ b/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/workerMode/navigation/WorkerNavigation.kt @@ -11,14 +11,16 @@ import com.arygm.quickfix.ui.navigation.TopLevelDestination object WorkerRoute { const val HOME = "Home" const val ANNOUNCEMENT = "Announcement" - const val CHATS = "Chats" + const val MESSAGES = "Messages" + const val CHATS = "Chats" const val PROFILE = "Profile" } object WorkerScreen { const val HOME = "Home Screen" const val ANNOUNCEMENT = "Announcement Screen" - const val CHATS = "Chats Screen" + const val MESSAGES = "Messages Screen" + const val CHATS = "Chats Screen" const val PROFILE = "Profile Screen" const val ACCOUNT_CONFIGURATION = "Account configuration Screen" } diff --git a/app/src/test/java/com/arygm/quickfix/model/messaging/ChatViewModelTest.kt b/app/src/test/java/com/arygm/quickfix/model/messaging/ChatViewModelTest.kt index 08c5cf36..802e5ca7 100644 --- a/app/src/test/java/com/arygm/quickfix/model/messaging/ChatViewModelTest.kt +++ b/app/src/test/java/com/arygm/quickfix/model/messaging/ChatViewModelTest.kt @@ -162,6 +162,7 @@ class ChatViewModelTest { @Test fun deleteChat_whenSuccess_updatesChats() = runTest { + println("9bl") doAnswer { invocation -> val onSuccess = invocation.getArgument<() -> Unit>(1) onSuccess() From 9442b2fd1eac149141574a13d0f8fdf2780339af Mon Sep 17 00:00:00 2001 From: daferya Date: Fri, 20 Dec 2024 03:12:22 +0100 Subject: [PATCH 3/4] Merge branch main to 255-implement-messages-screen and fix bugs --- .../ui/home/MessageUserNoModeScreenTest.kt | 16 +------- .../arygm/quickfix/model/account/Account.kt | 3 +- .../arygm/quickfix/ui/dashboard/ChatWidget.kt | 40 +++++++++++++------ .../ui/elements/QuickFixProfileElement.kt | 3 +- .../userModeUI/UserModeNavGraph.kt | 6 +-- .../userModeUI/home/MessagesScreen.kt | 5 +-- .../userModeUI/profile/UserProfileScreen.kt | 3 +- .../workerMode/WorkerModeNavGraph.kt | 14 ++++--- 8 files changed, 43 insertions(+), 47 deletions(-) diff --git a/app/src/androidTest/java/com/arygm/quickfix/ui/home/MessageUserNoModeScreenTest.kt b/app/src/androidTest/java/com/arygm/quickfix/ui/home/MessageUserNoModeScreenTest.kt index 656b72d9..73271c1e 100644 --- a/app/src/androidTest/java/com/arygm/quickfix/ui/home/MessageUserNoModeScreenTest.kt +++ b/app/src/androidTest/java/com/arygm/quickfix/ui/home/MessageUserNoModeScreenTest.kt @@ -190,7 +190,6 @@ class MessageUserNoModeScreenTest { navigationActions = navigationActions, quickFixViewModel = quickFixViewModel, preferencesViewModel = preferencesViewModel, - accountViewModel = accountViewModel, workerViewModel = workerViewModel) } @@ -229,7 +228,6 @@ class MessageUserNoModeScreenTest { } } - // Vérifie les messages composeTestRule.onNodeWithText("Hello!").assertIsDisplayed() composeTestRule.onNodeWithText("Hi, how can I help you?").assertIsDisplayed() @@ -245,7 +243,6 @@ class MessageUserNoModeScreenTest { navigationActions = navigationActions, quickFixViewModel = quickFixViewModel, preferencesViewModel = preferencesViewModel, - accountViewModel = accountViewModel, workerViewModel = workerViewModel) } @@ -279,7 +276,6 @@ class MessageUserNoModeScreenTest { navigationActions = navigationActions, quickFixViewModel = quickFixViewModel, preferencesViewModel = preferencesViewModel, - accountViewModel = accountViewModel, workerViewModel = workerViewModel) } @@ -287,7 +283,6 @@ class MessageUserNoModeScreenTest { composeTestRule.onNodeWithText("How is it going?").assertIsDisplayed() composeTestRule.onNodeWithText("Is the time and day okay for you?").assertIsDisplayed() - composeTestRule.onNodeWithText("I can’t wait to work with you!").assertIsDisplayed() } } @@ -316,7 +311,6 @@ class MessageUserNoModeScreenTest { navigationActions = navigationActions, quickFixViewModel = quickFixViewModel, preferencesViewModel = preferencesViewModel, - accountViewModel = accountViewModel, workerViewModel = workerViewModel) } @@ -347,7 +341,6 @@ class MessageUserNoModeScreenTest { navigationActions = navigationActions, quickFixViewModel = quickFixViewModel, preferencesViewModel = preferencesViewModel, - accountViewModel = accountViewModel, workerViewModel = workerViewModel) } @@ -386,7 +379,6 @@ class MessageUserNoModeScreenTest { navigationActions = navigationActions, quickFixViewModel = quickFixViewModel, preferencesViewModel = preferencesViewModel, - accountViewModel = accountViewModel, workerViewModel = workerViewModel) } @@ -426,7 +418,6 @@ class MessageUserNoModeScreenTest { navigationActions = navigationActions, quickFixViewModel = quickFixViewModel, preferencesViewModel = preferencesViewModel, - accountViewModel = accountViewModel, workerViewModel = workerViewModel) } @@ -458,11 +449,7 @@ class MessageUserNoModeScreenTest { quickFixViewModel.getQuickFixes() } - val suggestions = - listOf( - "How is it going?", - "Is the time and day okay for you?", - "I can’t wait to work with you!") + val suggestions = listOf("How is it going?", "Is the time and day okay for you?") doAnswer { invocation -> val onSuccess = invocation.getArgument<() -> Unit>(1) @@ -487,7 +474,6 @@ class MessageUserNoModeScreenTest { navigationActions = navigationActions, quickFixViewModel = quickFixViewModel, preferencesViewModel = preferencesViewModel, - accountViewModel = accountViewModel, workerViewModel = workerViewModel) } diff --git a/app/src/main/java/com/arygm/quickfix/model/account/Account.kt b/app/src/main/java/com/arygm/quickfix/model/account/Account.kt index aa97b907..768d8c4a 100644 --- a/app/src/main/java/com/arygm/quickfix/model/account/Account.kt +++ b/app/src/main/java/com/arygm/quickfix/model/account/Account.kt @@ -10,6 +10,5 @@ data class Account( val birthDate: Timestamp, val isWorker: Boolean = false, val activeChats: List = emptyList(), - val profilePicture: String = - "https://example.com/default-profile-pic.jpg" // Default profile picture URL + val profilePicture: String = "" // Default profile picture URL ) diff --git a/app/src/main/java/com/arygm/quickfix/ui/dashboard/ChatWidget.kt b/app/src/main/java/com/arygm/quickfix/ui/dashboard/ChatWidget.kt index cad7e64d..0f003004 100644 --- a/app/src/main/java/com/arygm/quickfix/ui/dashboard/ChatWidget.kt +++ b/app/src/main/java/com/arygm/quickfix/ui/dashboard/ChatWidget.kt @@ -1,6 +1,7 @@ package com.arygm.quickfix.ui.dashboard import android.annotation.SuppressLint +import android.graphics.Bitmap import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -32,15 +33,14 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.painterResource 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 com.arygm.quickfix.R import com.arygm.quickfix.model.account.AccountViewModel import com.arygm.quickfix.model.category.CategoryViewModel import com.arygm.quickfix.model.category.getCategoryIcon @@ -162,6 +162,19 @@ fun ChatItem( image = it?.let { it1 -> getCategoryIcon(it1) } } } + var otherUserId = chat.useruid + if (mode == AppMode.USER) { + otherUserId = chat.workeruid + } + + var otherProfileBitmap by remember { mutableStateOf(null) } + + LaunchedEffect(otherUserId) { + accountViewModel.fetchAccountProfileImageAsBitmap( + accountId = otherUserId, + onSuccess = { bitmap -> otherProfileBitmap = bitmap }, + onFailure = {}) + } Row( modifier = Modifier.fillMaxWidth() @@ -170,17 +183,20 @@ fun ChatItem( .testTag("MessageItem_${chat.chatId}"), // Added testTag verticalAlignment = Alignment.CenterVertically) { Column(modifier = Modifier.weight(0.15f)) { + val imageModifier = + Modifier.size(40.dp) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f)) + if (otherProfileBitmap != null) { + Image( + bitmap = otherProfileBitmap!!.asImageBitmap(), + contentDescription = "Profile Picture", + contentScale = ContentScale.Crop, + modifier = imageModifier) + } else { + Box(modifier = imageModifier) + } // Profile image placeholder - Image( - painter = - painterResource( - id = R.drawable.placeholder_worker), // Replace with an actual drawable - contentDescription = "Profile Picture", - contentScale = ContentScale.Crop, - modifier = - Modifier.size(40.dp) - .clip(CircleShape) - .background(MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f))) } // Text information diff --git a/app/src/main/java/com/arygm/quickfix/ui/elements/QuickFixProfileElement.kt b/app/src/main/java/com/arygm/quickfix/ui/elements/QuickFixProfileElement.kt index ac783785..9042fc03 100644 --- a/app/src/main/java/com/arygm/quickfix/ui/elements/QuickFixProfileElement.kt +++ b/app/src/main/java/com/arygm/quickfix/ui/elements/QuickFixProfileElement.kt @@ -25,7 +25,6 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.ArrowForwardIos import androidx.compose.material.icons.outlined.CameraAlt -import androidx.compose.material.icons.outlined.Person import androidx.compose.material.icons.outlined.WorkOutline import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults @@ -80,9 +79,9 @@ import com.arygm.quickfix.ui.uiMode.appContentUI.userModeUI.navigation.UserRoute import com.arygm.quickfix.ui.uiMode.workerMode.navigation.WorkerRoute import com.arygm.quickfix.utils.clearPreferences import com.arygm.quickfix.utils.clearUserProfilePreferences -import com.arygm.quickfix.utils.loadUserId import com.arygm.quickfix.utils.clearWorkerProfilePreferences import com.arygm.quickfix.utils.loadAppMode +import com.arygm.quickfix.utils.loadUserId import com.arygm.quickfix.utils.loadWallet import com.arygm.quickfix.utils.setAppMode import com.google.firebase.auth.ktx.auth diff --git a/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/userModeUI/UserModeNavGraph.kt b/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/userModeUI/UserModeNavGraph.kt index a7d4e4b7..3cc466c9 100644 --- a/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/userModeUI/UserModeNavGraph.kt +++ b/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/userModeUI/UserModeNavGraph.kt @@ -168,10 +168,10 @@ fun UserModeNavHost( userViewModel, workerViewModel, quickFixViewModel, - accountViewModel, userNavigationActions, searchViewModel) } + composable(UserRoute.SEARCH) { SearchNavHost( isUser, @@ -258,8 +258,6 @@ fun HomeNavHost( userViewModel: ProfileViewModel, workerViewModel: ProfileViewModel, quickFixViewModel: QuickFixViewModel, - accountViewModel: AccountViewModel, - navigationActionsRoot: NavigationActions, searchViewModel: SearchViewModel ) { @@ -307,7 +305,6 @@ fun HomeNavHost( navigationActions = navigationActions, quickFixViewModel = quickFixViewModel, preferencesViewModel = preferencesViewModel, - accountViewModel = accountViewModel, workerViewModel = workerViewModel) } @@ -540,7 +537,6 @@ fun SearchNavHost( navigationActions = navigationActions, quickFixViewModel = quickFixViewModel, preferencesViewModel = preferencesViewModel, - accountViewModel = accountViewModel, workerViewModel = workerViewModel) } diff --git a/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/userModeUI/home/MessagesScreen.kt b/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/userModeUI/home/MessagesScreen.kt index d24f3213..9240f2b7 100644 --- a/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/userModeUI/home/MessagesScreen.kt +++ b/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/userModeUI/home/MessagesScreen.kt @@ -212,10 +212,7 @@ fun MessageScreen( Header( navigationActions = navigationActions, modifier = Modifier.testTag("backButton"), - otherProfileBitmap = otherProfileBitmap), - displayName = displayNameHeader - navigationActions, - modifier = Modifier.testTag("backButton"), + otherProfileBitmap = otherProfileBitmap, displayName = displayNameHeader) }, bottomBar = { diff --git a/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/userModeUI/profile/UserProfileScreen.kt b/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/userModeUI/profile/UserProfileScreen.kt index 12e531ff..23e5c85f 100644 --- a/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/userModeUI/profile/UserProfileScreen.kt +++ b/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/userModeUI/profile/UserProfileScreen.kt @@ -109,6 +109,7 @@ fun UserProfileScreen( cardCornerRadius = 16.dp, showConditionalItem = !isWorker, conditionalItem = conditionalWorkerSection) - }),accountViewModel = accountViewModel) + }), + accountViewModel = accountViewModel) } } diff --git a/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/workerMode/WorkerModeNavGraph.kt b/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/workerMode/WorkerModeNavGraph.kt index e3016650..084203f3 100644 --- a/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/workerMode/WorkerModeNavGraph.kt +++ b/app/src/main/java/com/arygm/quickfix/ui/uiMode/appContentUI/workerMode/WorkerModeNavGraph.kt @@ -28,11 +28,11 @@ import androidx.navigation.compose.rememberNavController import com.arygm.quickfix.dataStore import com.arygm.quickfix.model.account.AccountViewModel import com.arygm.quickfix.model.category.CategoryViewModel - import com.arygm.quickfix.model.locations.LocationViewModel import com.arygm.quickfix.model.messaging.ChatViewModel import com.arygm.quickfix.model.offline.small.PreferencesRepositoryDataStore import com.arygm.quickfix.model.offline.small.PreferencesViewModel +import com.arygm.quickfix.model.offline.small.PreferencesViewModelUserProfile import com.arygm.quickfix.model.offline.small.PreferencesViewModelWorkerProfile import com.arygm.quickfix.model.profile.ProfileViewModel import com.arygm.quickfix.model.profile.WorkerProfileRepositoryFirestore @@ -51,8 +51,6 @@ import com.arygm.quickfix.ui.uiMode.appContentUI.userModeUI.quickfix.QuickFixOnB import com.arygm.quickfix.ui.uiMode.appContentUI.userModeUI.search.AnnouncementDetailScreen import com.arygm.quickfix.ui.uiMode.appContentUI.workerMode.announcements.AnnouncementsScreen import com.arygm.quickfix.ui.uiMode.appContentUI.workerMode.messages.ChatsScreen -import com.arygm.quickfix.ui.uiMode.workerMode.home.HomeScreen -import com.arygm.quickfix.ui.uiMode.appContentUI.workerMode.messages.MessagesScreen import com.arygm.quickfix.ui.uiMode.appContentUI.workerMode.profile.WorkerProfileScreen import com.arygm.quickfix.ui.uiMode.appContentUI.workerMode.quickfix.QuickFixBilling import com.arygm.quickfix.ui.uiMode.workerMode.navigation.WORKER_TOP_LEVEL_DESTINATIONS @@ -72,7 +70,6 @@ fun WorkerModeNavGraph( preferencesViewModel: PreferencesViewModel, accountViewModel: AccountViewModel, rootMainNavigationActions: NavigationActions, - workerPreferenceViewModel: PreferencesViewModelWorkerProfile, locationViewModel: LocationViewModel, userViewModel: ProfileViewModel, @@ -102,7 +99,6 @@ fun WorkerModeNavGraph( var currentScreen by remember { mutableStateOf(null) } val shouldShowBottomBar by remember { derivedStateOf { - currentScreen?.let { it != WorkerScreen.ACCOUNT_CONFIGURATION } ?: true && currentScreen?.let { it != WorkerScreen.MESSAGES } ?: true && currentScreen?.let { it != WorkerScreen.QUIKFIX_ONBOARDING } ?: true && @@ -229,7 +225,13 @@ fun MessagesNavHost( ChatsScreen(navigationActions, accountViewModel, chatViewModel, pre) } composable(WorkerScreen.MESSAGES) { - MessageScreen(chatViewModel, navigationActions, quickFixViewModel, pre,workerViewModel,accountViewModel) + MessageScreen( + chatViewModel, + navigationActions, + quickFixViewModel, + pre, + workerViewModel, + accountViewModel) } } } From 620e3e2dc121771a6a969ff4bab73a5ada8d03a3 Mon Sep 17 00:00:00 2001 From: daferya Date: Fri, 20 Dec 2024 06:21:25 +0100 Subject: [PATCH 4/4] bug: fix an issue with crashing on edge cases --- .../main/java/com/arygm/quickfix/ui/dashboard/ChatWidget.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/arygm/quickfix/ui/dashboard/ChatWidget.kt b/app/src/main/java/com/arygm/quickfix/ui/dashboard/ChatWidget.kt index 0f003004..d82b4d95 100644 --- a/app/src/main/java/com/arygm/quickfix/ui/dashboard/ChatWidget.kt +++ b/app/src/main/java/com/arygm/quickfix/ui/dashboard/ChatWidget.kt @@ -252,7 +252,9 @@ fun ChatItem( (chat.chatStatus == ChatStatus.WAITING_FOR_RESPONSE || chat.chatStatus == ChatStatus.GETTING_SUGGESTIONS)) { "Await confirmation from ${workerProfile.displayName}" - } else chat.messages.last().content, // Removed leading comma for clarity + } else { + if (!chat.messages.isEmpty()) chat.messages.last().content else "" + }, // Removed leading comma for clarity modifier = Modifier.testTag( if (chat.messages.isEmpty()) "No messages"