From 6c1f633ab43c6c77388ede525d8e08d5ba654b3f Mon Sep 17 00:00:00 2001 From: Nguyen Quang Minh Date: Fri, 29 Nov 2024 23:01:08 +0700 Subject: [PATCH 1/7] =?UTF-8?q?fix(state):=20s=E1=BB=ADa=20l=E1=BB=97i=20?= =?UTF-8?q?=C4=91=C4=83ng=20xu=E1=BA=A5t=20=C4=91=C4=83ng=20nh=E1=BA=ADp?= =?UTF-8?q?=20l=E1=BA=A1i=20th=C3=AC=20state=20v=E1=BA=ABn=20l=C3=A0=20t?= =?UTF-8?q?=C3=A0i=20kho=E1=BA=A3n=20c=C5=A9,=20ko=20ph=E1=BA=A3i=20l?= =?UTF-8?q?=C3=A0=20t=C3=A0i=20kho=E1=BA=A3n=20m=E1=BB=9Bi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../quickmem/core/datastore/AppManager.kt | 4 +- .../add_folder/AddFolderToClassViewModel.kt | 4 +- .../AddStudySetToClassViewModel.kt | 4 +- .../app/explore/ExploreViewModel.kt | 57 +++++++------ .../AddStudySetToFolderViewModel.kt | 4 +- .../presentation/app/home/HomeUiAction.kt | 1 - .../presentation/app/home/HomeViewModel.kt | 83 ++++++++++--------- .../presentation/app/library/LibraryScreen.kt | 2 +- .../app/library/LibraryUiState.kt | 5 +- .../app/library/LibraryViewModel.kt | 82 +++++++++++------- .../app/profile/ProfileViewModel.kt | 44 ++++++---- .../choose_picture/ChoosePictureViewModel.kt | 2 +- .../app/settings/SettingsViewModel.kt | 9 +- .../AddStudySetToClassesViewModel.kt | 4 +- .../AddStudySetToFoldersViewModel.kt | 4 +- .../onboarding/OnboardingScreen.kt | 2 +- 16 files changed, 175 insertions(+), 136 deletions(-) diff --git a/app/src/main/java/com/pwhs/quickmem/core/datastore/AppManager.kt b/app/src/main/java/com/pwhs/quickmem/core/datastore/AppManager.kt index 1e094bf9..161a7e99 100644 --- a/app/src/main/java/com/pwhs/quickmem/core/datastore/AppManager.kt +++ b/app/src/main/java/com/pwhs/quickmem/core/datastore/AppManager.kt @@ -41,11 +41,11 @@ class AppManager(private val context: Context) { .map { preferences -> preferences[USER_FULL_NAME] ?: "" } - val userName: Flow = context.dataStore.data + val username: Flow = context.dataStore.data .map { preferences -> preferences[USER_NAME] ?: "" } - val userAvatar: Flow = context.dataStore.data + val userAvatarUrl: Flow = context.dataStore.data .map { preferences -> preferences[USER_AVATAR] ?: "" } diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/add_folder/AddFolderToClassViewModel.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/add_folder/AddFolderToClassViewModel.kt index 9c46c5cc..ca291af0 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/add_folder/AddFolderToClassViewModel.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/add_folder/AddFolderToClassViewModel.kt @@ -45,8 +45,8 @@ class AddFolderToClassViewModel @Inject constructor( viewModelScope.launch { val token = tokenManager.accessToken.firstOrNull() ?: return@launch val ownerId = appManager.userId.firstOrNull() ?: return@launch - val userAvatar = appManager.userAvatar.firstOrNull() ?: return@launch - val username = appManager.userName.firstOrNull() ?: return@launch + val userAvatar = appManager.userAvatarUrl.firstOrNull() ?: return@launch + val username = appManager.username.firstOrNull() ?: return@launch _uiState.update { it.copy( token = token, diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/add_study_set/AddStudySetToClassViewModel.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/add_study_set/AddStudySetToClassViewModel.kt index 857c05d6..582a0b65 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/add_study_set/AddStudySetToClassViewModel.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/add_study_set/AddStudySetToClassViewModel.kt @@ -38,8 +38,8 @@ class AddStudySetToClassViewModel @Inject constructor( viewModelScope.launch { val token = tokenManager.accessToken.firstOrNull() ?: return@launch val ownerId = appManager.userId.firstOrNull() ?: return@launch - val userAvatar = appManager.userAvatar.firstOrNull() ?: return@launch - val username = appManager.userName.firstOrNull() ?: return@launch + val userAvatar = appManager.userAvatarUrl.firstOrNull() ?: return@launch + val username = appManager.username.firstOrNull() ?: return@launch _uiState.update { it.copy( token = token, diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreViewModel.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreViewModel.kt index 3745902d..df4d55f5 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreViewModel.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreViewModel.kt @@ -16,6 +16,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.update @@ -98,35 +99,39 @@ class ExploreViewModel @Inject constructor( private fun getTopStreaks() { viewModelScope.launch { - val token = tokenManager.accessToken.firstOrNull() ?: "" - streakRepository.getTopStreaks(token, 10).collect { resource -> - when (resource) { - is Resources.Loading -> { - _uiState.update { it.copy(isLoading = true) } - } - - is Resources.Success -> { - val topStreaks = resource.data ?: emptyList() - val streakOwner = topStreaks.find { it.userId == uiState.value.ownerId } - val rankOwner = - topStreaks.indexOfFirst { it.userId == uiState.value.ownerId } - .takeIf { it != -1 }?.plus(1) - _uiState.update { - it.copy( - isLoading = false, - topStreaks = topStreaks, - streakOwner = streakOwner, - rankOwner = rankOwner - ) + tokenManager.accessToken.collect { token -> + streakRepository.getTopStreaks(token = token ?: "", limit = 10) + .collect { resource -> + when (resource) { + is Resources.Loading -> { + _uiState.update { it.copy(isLoading = true) } + } + + is Resources.Success -> { + val topStreaks = resource.data ?: emptyList() + val streakOwner = + topStreaks.find { it.userId == uiState.value.ownerId } + val rankOwner = + topStreaks.indexOfFirst { it.userId == uiState.value.ownerId } + .takeIf { it != -1 }?.plus(1) + _uiState.update { + it.copy( + isLoading = false, + topStreaks = topStreaks, + streakOwner = streakOwner, + rankOwner = rankOwner + ) + } + } + + is Resources.Error -> { + _uiState.update { it.copy(isLoading = false) } + _uiEvent.send(ExploreUiEvent.Error(resource.message ?: "")) + } } } - - is Resources.Error -> { - _uiState.update { it.copy(isLoading = false) } - _uiEvent.send(ExploreUiEvent.Error(resource.message ?: "")) - } - } } + } } diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/folder/add_study_set/AddStudySetToFolderViewModel.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/folder/add_study_set/AddStudySetToFolderViewModel.kt index 810bcd7f..8e0488c2 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/folder/add_study_set/AddStudySetToFolderViewModel.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/folder/add_study_set/AddStudySetToFolderViewModel.kt @@ -40,8 +40,8 @@ class AddStudySetToFolderViewModel @Inject constructor( viewModelScope.launch { val token = tokenManager.accessToken.firstOrNull() ?: return@launch val ownerId = appManager.userId.firstOrNull() ?: return@launch - val userAvatar = appManager.userAvatar.firstOrNull() ?: return@launch - val username = appManager.userName.firstOrNull() ?: return@launch + val userAvatar = appManager.userAvatarUrl.firstOrNull() ?: return@launch + val username = appManager.username.firstOrNull() ?: return@launch _uiState.update { it.copy( token = token, diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/home/HomeUiAction.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/home/HomeUiAction.kt index e4048193..adbcf82a 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/home/HomeUiAction.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/home/HomeUiAction.kt @@ -9,6 +9,5 @@ sealed class HomeUiAction { data class OnChangeCustomerInfo(val customerInfo: CustomerInfo) : HomeUiAction() data class LoadNotifications(val userId: String) : HomeUiAction() data class MarkAsRead(val notificationId: String) : HomeUiAction() - data object RefreshNotifications : HomeUiAction() data object RefreshHome : HomeUiAction() } \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/home/HomeViewModel.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/home/HomeViewModel.kt index 3bef30e5..aec81993 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/home/HomeViewModel.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/home/HomeViewModel.kt @@ -23,6 +23,7 @@ import kotlinx.coroutines.async import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.update @@ -55,28 +56,41 @@ class HomeViewModel @Inject constructor( val userId = appManager.userId.firstOrNull() ?: "" _uiState.value = HomeUiState(userId = userId) initData() - updateStreak() } } private fun initData() { job?.cancel() job = viewModelScope.launch { - val studySetsDeferred = async { getRecentAccessStudySets() } - val foldersDeferred = async { getRecentAccessFolders() } - val classesDeferred = async { getRecentAccessClasses() } - val top5SubjectsDeferred = async { getTop5Subjects() } - val streaksDeferred = async { getStreaksByUserId() } - val customerInfoDeferred = async { getCustomerInfo() } - val notificationsDeferred = async { loadNotifications() } - - studySetsDeferred.await() - foldersDeferred.await() - classesDeferred.await() - top5SubjectsDeferred.await() - streaksDeferred.await() - customerInfoDeferred.await() - notificationsDeferred.await() + combine(tokenManager.accessToken, appManager.userId) { token, userId -> + token to userId + }.collect { (token, userId) -> + if (token?.isNotEmpty() == true && userId.isNotEmpty()) { + val studySetsDeferred = + async { getRecentAccessStudySets(token = token, userId = userId) } + val foldersDeferred = + async { getRecentAccessFolders(token = token, userId = userId) } + val classesDeferred = + async { getRecentAccessClasses(token = token, userId = userId) } + val top5SubjectsDeferred = async { getTop5Subjects(token = token) } + val streaksDeferred = + async { getStreaksByUserId(token = token, userId = userId) } + val customerInfoDeferred = async { getCustomerInfo() } + val notificationsDeferred = + async { loadNotifications(token = token, userId = userId) } + val updateStreakDeferred = + async { updateStreak(token = token, userId = userId) } + + studySetsDeferred.await() + foldersDeferred.await() + classesDeferred.await() + top5SubjectsDeferred.await() + streaksDeferred.await() + customerInfoDeferred.await() + notificationsDeferred.await() + updateStreakDeferred.await() + } + } } } @@ -97,17 +111,17 @@ class HomeViewModel @Inject constructor( } is HomeUiAction.LoadNotifications -> { - loadNotifications() + viewModelScope.launch { + val token = tokenManager.accessToken.firstOrNull() ?: "" + val userId = appManager.userId.firstOrNull() ?: "" + loadNotifications(token = token, userId = userId) + } } is HomeUiAction.MarkAsRead -> { markNotificationAsRead(event.notificationId) } - is HomeUiAction.RefreshNotifications -> { - loadNotifications() - } - HomeUiAction.RefreshHome -> { initData() } @@ -130,10 +144,8 @@ class HomeViewModel @Inject constructor( }) } - private fun getStreaksByUserId() { + private fun getStreaksByUserId(token: String, userId: String) { viewModelScope.launch { - val token = tokenManager.accessToken.firstOrNull() ?: "" - val userId = appManager.userId.firstOrNull() ?: "" streakRepository.getStreaksByUserId(token, userId).collect { resource -> when (resource) { is Resources.Loading -> { @@ -169,10 +181,8 @@ class HomeViewModel @Inject constructor( }.distinct() } - private fun updateStreak() { + private fun updateStreak(token: String, userId: String) { viewModelScope.launch { - val userId = appManager.userId.firstOrNull() ?: "" - val token = tokenManager.accessToken.firstOrNull() ?: "" streakRepository.updateStreak(token, userId).collect { resource -> when (resource) { is Resources.Loading -> { @@ -200,10 +210,8 @@ class HomeViewModel @Inject constructor( } } - private fun loadNotifications() { + private fun loadNotifications(token: String, userId: String) { viewModelScope.launch { - val token = tokenManager.accessToken.firstOrNull() ?: "" - val userId = appManager.userId.firstOrNull() ?: "" notificationRepository.loadNotifications(userId, token).collect { result -> when (result) { is Resources.Loading -> { @@ -259,9 +267,8 @@ class HomeViewModel @Inject constructor( } } - private fun getTop5Subjects() { + private fun getTop5Subjects(token: String) { viewModelScope.launch { - val token = tokenManager.accessToken.firstOrNull() ?: "" studySetRepository.getTop5Subject(token).collect { resource -> when (resource) { is Resources.Loading -> { @@ -297,10 +304,8 @@ class HomeViewModel @Inject constructor( } } - private fun getRecentAccessStudySets() { + private fun getRecentAccessStudySets(token: String, userId: String) { viewModelScope.launch { - val token = tokenManager.accessToken.firstOrNull() ?: "" - val userId = appManager.userId.firstOrNull() ?: "" studySetRepository.getRecentAccessStudySet(token, userId).collect { resource -> when (resource) { is Resources.Loading -> { @@ -322,10 +327,8 @@ class HomeViewModel @Inject constructor( } } - private fun getRecentAccessFolders() { + private fun getRecentAccessFolders(token: String, userId: String) { viewModelScope.launch { - val token = tokenManager.accessToken.firstOrNull() ?: "" - val userId = appManager.userId.firstOrNull() ?: "" folderRepository.getRecentAccessFolders(token, userId).collect { resource -> when (resource) { is Resources.Loading -> { @@ -347,10 +350,8 @@ class HomeViewModel @Inject constructor( } } - private fun getRecentAccessClasses() { + private fun getRecentAccessClasses(token: String, userId: String) { viewModelScope.launch { - val token = tokenManager.accessToken.firstOrNull() ?: "" - val userId = appManager.userId.firstOrNull() ?: "" classRepository.getRecentAccessClass(token, userId).collect { resource -> when (resource) { is Resources.Loading -> { diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/library/LibraryScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/library/LibraryScreen.kt index 22e3830e..6317715e 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/library/LibraryScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/library/LibraryScreen.kt @@ -124,7 +124,7 @@ fun LibraryScreen( studySets = uiState.studySets, classes = uiState.classes, folders = uiState.folders, - avatarUrl = uiState.userAvatar, + avatarUrl = uiState.userAvatarUrl, username = uiState.username, onStudySetRefresh = { viewModel.onEvent(LibraryUiAction.RefreshStudySets) diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/library/LibraryUiState.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/library/LibraryUiState.kt index 5ec3f2ef..ce2baf24 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/library/LibraryUiState.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/library/LibraryUiState.kt @@ -1,17 +1,14 @@ package com.pwhs.quickmem.presentation.app.library import com.pwhs.quickmem.domain.model.classes.GetClassByOwnerResponseModel -import com.pwhs.quickmem.domain.model.classes.GetClassDetailResponseModel import com.pwhs.quickmem.domain.model.folder.GetFolderResponseModel import com.pwhs.quickmem.domain.model.study_set.GetStudySetResponseModel data class LibraryUiState( val title: String = "", val isLoading: Boolean = false, - val userAvatar: String = "", + val userAvatarUrl: String = "", val username: String = "", - val token: String = "", - val userId: String = "", val studySets: List = emptyList(), val classes: List = emptyList(), val folders: List = emptyList(), diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/library/LibraryViewModel.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/library/LibraryViewModel.kt index 7920d598..fea9ea81 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/library/LibraryViewModel.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/library/LibraryViewModel.kt @@ -9,11 +9,13 @@ import com.pwhs.quickmem.domain.repository.ClassRepository import com.pwhs.quickmem.domain.repository.FolderRepository import com.pwhs.quickmem.domain.repository.StudySetRepository import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Job import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.update @@ -34,61 +36,81 @@ class LibraryViewModel @Inject constructor( private val _uiEvent = Channel() val uiEvent = _uiEvent.receiveAsFlow() + private var job: Job? = null + init { + initData() + } + + private fun initData() { viewModelScope.launch { - val token = tokenManager.accessToken.firstOrNull() ?: return@launch - val ownerId = appManager.userId.firstOrNull() ?: return@launch - val userAvatar = appManager.userAvatar.firstOrNull() ?: return@launch - val username = appManager.userName.firstOrNull() ?: return@launch - _uiState.update { - it.copy( - token = token, - userId = ownerId, - userAvatar = userAvatar, - username = username - ) + combine(tokenManager.accessToken, appManager.userId) { token, userId -> + token to userId + }.collectLatest { (token, userId) -> + if (token?.isNotEmpty() == true && userId.isNotEmpty()) { + getUserInfo() + getStudySets(token = token, userId = userId) + getClasses(token = token, userId = userId) + getFolders(token = token, userId = userId) + } } - getStudySets() - getClasses() - getFolders() } - } fun onEvent(event: LibraryUiAction) { when (event) { is LibraryUiAction.Refresh -> { - getStudySets() - getClasses() - getFolders() + initData() } LibraryUiAction.RefreshStudySets -> { - viewModelScope.launch { + job?.cancel() + job = viewModelScope.launch { delay(500) - getStudySets() + val token = tokenManager.accessToken.firstOrNull() ?: "" + val userId = appManager.userId.firstOrNull() ?: "" + getStudySets(token = token, userId = userId) } } LibraryUiAction.RefreshClasses -> { - viewModelScope.launch { + job?.cancel() + job = viewModelScope.launch { delay(500) - getClasses() + val token = tokenManager.accessToken.firstOrNull() ?: "" + val userId = appManager.userId.firstOrNull() ?: "" + getClasses(token = token, userId = userId) } } LibraryUiAction.RefreshFolders -> { - viewModelScope.launch { + job?.cancel() + job = viewModelScope.launch { delay(500) - getFolders() + val token = tokenManager.accessToken.firstOrNull() ?: "" + val userId = appManager.userId.firstOrNull() ?: "" + getFolders(token = token, userId = userId) } } } } - private fun getStudySets() { + private fun getUserInfo() { + viewModelScope.launch { + val username = appManager.username.firstOrNull() ?: "" + val userAvatarUrl = appManager.userAvatarUrl.firstOrNull() ?: "" + _uiState.update { + it.copy( + username = username, + userAvatarUrl = userAvatarUrl + ) + } + } + } + + private fun getStudySets(token: String, userId: String) { viewModelScope.launch { - studySetRepository.getStudySetsByOwnerId(_uiState.value.token, _uiState.value.userId, null, null) + studySetRepository.getStudySetsByOwnerId(token, userId, null, null) .collectLatest { resources -> when (resources) { is Resources.Success -> { @@ -121,9 +143,9 @@ class LibraryViewModel @Inject constructor( } } - private fun getClasses() { + private fun getClasses(token: String, userId: String) { viewModelScope.launch { - classRepository.getClassByOwnerId(_uiState.value.token, _uiState.value.userId, null, null) + classRepository.getClassByOwnerId(token, userId, null, null) .collectLatest { resource -> when (resource) { is Resources.Error -> { @@ -156,9 +178,9 @@ class LibraryViewModel @Inject constructor( } } - private fun getFolders() { + private fun getFolders(token: String, userId: String) { viewModelScope.launch { - folderRepository.getFoldersByUserId(_uiState.value.token, _uiState.value.userId, null, null) + folderRepository.getFoldersByUserId(token, userId, null, null) .collectLatest { resources -> when (resources) { is Resources.Success -> { diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/profile/ProfileViewModel.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/profile/ProfileViewModel.kt index e13c6d4e..14ac1fdb 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/profile/ProfileViewModel.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/profile/ProfileViewModel.kt @@ -12,14 +12,15 @@ import com.revenuecat.purchases.Purchases import com.revenuecat.purchases.PurchasesError import com.revenuecat.purchases.interfaces.ReceiveCustomerInfoCallback import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Job import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -39,11 +40,25 @@ class ProfileViewModel @Inject constructor( private val _uiEvent = Channel() val uiEvent = _uiEvent.receiveAsFlow() + private var job: Job? = null + init { - loadProfile() - getUserProfile() - getCustomerInfo() - getStudyTime() + initData() + } + + private fun initData() { + viewModelScope.launch { + combine(tokenManager.accessToken, appManager.userId) { token, userId -> + token to userId + }.collectLatest { (token, userId) -> + if (token?.isNotEmpty() == true && userId.isNotEmpty()) { + loadProfile() + getUserProfile(token = token, userId = userId) + getCustomerInfo() + getStudyTime(token = token, userId = userId) + } + } + } } fun onEvent(event: ProfileUiAction) { @@ -57,10 +72,11 @@ class ProfileViewModel @Inject constructor( } ProfileUiAction.Refresh -> { - getUserProfile() - getUserProfile() - getCustomerInfo() - getStudyTime() + job?.cancel() + job = viewModelScope.launch { + delay(500) + initData() + } } } } @@ -81,10 +97,8 @@ class ProfileViewModel @Inject constructor( }) } - private fun getUserProfile() { + private fun getUserProfile(token: String, userId: String) { viewModelScope.launch { - val token = tokenManager.accessToken.firstOrNull() ?: "" - val userId = appManager.userId.firstOrNull() ?: "" authRepository.getUserProfile(token, userId).collectLatest { resource -> when (resource) { @@ -123,7 +137,7 @@ class ProfileViewModel @Inject constructor( } viewModelScope.launch { try { - appManager.userName.combine(appManager.userAvatar) { username, avatar -> + appManager.username.combine(appManager.userAvatarUrl) { username, avatar -> _uiState.update { it.copy( isLoading = false, @@ -145,10 +159,8 @@ class ProfileViewModel @Inject constructor( } } - private fun getStudyTime() { + private fun getStudyTime(token: String, userId: String) { viewModelScope.launch { - val token = tokenManager.accessToken.firstOrNull() ?: "" - val userId = appManager.userId.firstOrNull() ?: "" studyTimeRepository.getStudyTimeByUser(token, userId).collectLatest { resource -> when (resource) { diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/profile/choose_picture/ChoosePictureViewModel.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/profile/choose_picture/ChoosePictureViewModel.kt index 30378cb9..96c6a9e5 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/profile/choose_picture/ChoosePictureViewModel.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/profile/choose_picture/ChoosePictureViewModel.kt @@ -39,7 +39,7 @@ class ChoosePictureViewModel @Inject constructor( private fun getCurrentAvatar() { viewModelScope.launch { - val currentAvatarUrl = appManager.userAvatar.firstOrNull() + val currentAvatarUrl = appManager.userAvatarUrl.firstOrNull() if (currentAvatarUrl != null) { _uiState.update { it.copy( diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/settings/SettingsViewModel.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/settings/SettingsViewModel.kt index 698b70f3..360022ef 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/settings/SettingsViewModel.kt @@ -1,5 +1,7 @@ package com.pwhs.quickmem.presentation.app.settings +import android.app.Application +import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.pwhs.quickmem.core.datastore.AppManager @@ -28,8 +30,9 @@ class SettingsViewModel @Inject constructor( private val tokenManager: TokenManager, private val appManager: AppManager, private val authRepository: AuthRepository, - private val searchQueryRepository: SearchQueryRepository -) : ViewModel() { + private val searchQueryRepository: SearchQueryRepository, + application: Application +) : AndroidViewModel(application) { private val _uiState = MutableStateFlow(SettingUiState()) val uiState = _uiState.asStateFlow() @@ -98,7 +101,7 @@ class SettingsViewModel @Inject constructor( try { val userId = appManager.userId.firstOrNull() ?: "" val fullName = appManager.userFullName.firstOrNull() ?: "" - val username = appManager.userName.firstOrNull() ?: "" + val username = appManager.username.firstOrNull() ?: "" val role = appManager.userRole.firstOrNull() ?: "" val email = appManager.userEmail.firstOrNull() ?: "" val isPushNotificationsEnabled = appManager.pushNotifications.firstOrNull() ?: false diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/study_set/add_to_class/AddStudySetToClassesViewModel.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/study_set/add_to_class/AddStudySetToClassesViewModel.kt index 391ae5b1..19e6fca7 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/study_set/add_to_class/AddStudySetToClassesViewModel.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/study_set/add_to_class/AddStudySetToClassesViewModel.kt @@ -40,8 +40,8 @@ class AddStudySetToClassesViewModel @Inject constructor( viewModelScope.launch { val token = tokenManager.accessToken.firstOrNull() ?: return@launch val ownerId = appManager.userId.firstOrNull() ?: return@launch - val userAvatar = appManager.userAvatar.firstOrNull() ?: return@launch - val username = appManager.userName.firstOrNull() ?: return@launch + val userAvatar = appManager.userAvatarUrl.firstOrNull() ?: return@launch + val username = appManager.username.firstOrNull() ?: return@launch _uiState.update { it.copy( token = token, diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/study_set/add_to_folder/AddStudySetToFoldersViewModel.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/study_set/add_to_folder/AddStudySetToFoldersViewModel.kt index d1d9545b..024b7bbd 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/study_set/add_to_folder/AddStudySetToFoldersViewModel.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/study_set/add_to_folder/AddStudySetToFoldersViewModel.kt @@ -40,8 +40,8 @@ class AddStudySetToFoldersViewModel @Inject constructor( viewModelScope.launch { val token = tokenManager.accessToken.firstOrNull() ?: return@launch val ownerId = appManager.userId.firstOrNull() ?: return@launch - val userAvatar = appManager.userAvatar.firstOrNull() ?: return@launch - val username = appManager.userName.firstOrNull() ?: return@launch + val userAvatar = appManager.userAvatarUrl.firstOrNull() ?: return@launch + val username = appManager.username.firstOrNull() ?: return@launch _uiState.update { it.copy( token = token, diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/onboarding/OnboardingScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/onboarding/OnboardingScreen.kt index f3bc46eb..8e695ad2 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/onboarding/OnboardingScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/onboarding/OnboardingScreen.kt @@ -61,7 +61,7 @@ fun OnboardingScreen( }, onGetStartedClick = { viewModel.saveIsFirstRun(false) - navigator.navigate(WelcomeScreenDestination) { + navigator.navigate(WelcomeScreenDestination()) { popUpTo(NavGraphs.root) { saveState = false } From 81afb21c146c6323c503093f072e5978d0342d8f Mon Sep 17 00:00:00 2001 From: Nguyen Quang Minh Date: Fri, 29 Nov 2024 23:05:57 +0700 Subject: [PATCH 2/7] =?UTF-8?q?refactor(class,=20folder):=20lo=E1=BA=A1i?= =?UTF-8?q?=20b=E1=BB=8F=20code=20khi=20truy=E1=BB=81n=20param?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/app/classes/create/CreateClassScreen.kt | 1 - .../quickmem/presentation/app/classes/detail/ClassDetailArgs.kt | 1 - .../presentation/app/classes/detail/ClassDetailScreen.kt | 1 - .../presentation/app/classes/detail/ClassDetailViewModel.kt | 2 -- .../presentation/app/deeplink/classes/JoinClassScreen.kt | 1 - .../presentation/app/deeplink/folder/LoadFolderScreen.kt | 1 - .../presentation/app/folder/create/CreateFolderScreen.kt | 1 - .../quickmem/presentation/app/folder/detail/FolderDetailArgs.kt | 1 - .../java/com/pwhs/quickmem/presentation/app/home/HomeScreen.kt | 2 -- .../com/pwhs/quickmem/presentation/app/library/LibraryScreen.kt | 2 -- .../presentation/app/search_result/SearchResultScreen.kt | 2 -- .../quickmem/presentation/app/user_detail/UserDetailScreen.kt | 2 -- 12 files changed, 17 deletions(-) diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/create/CreateClassScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/create/CreateClassScreen.kt index cae12f13..992a7650 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/create/CreateClassScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/create/CreateClassScreen.kt @@ -41,7 +41,6 @@ fun CreateClassScreen( navigator.navigateUp() navigator.navigate( ClassDetailScreenDestination( - code = "", id = event.id, title = uiState.title, description = uiState.description diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/ClassDetailArgs.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/ClassDetailArgs.kt index e7f758b5..920abe79 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/ClassDetailArgs.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/ClassDetailArgs.kt @@ -4,5 +4,4 @@ data class ClassDetailArgs( val title: String, val description: String, val id: String, - val code: String ) \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/ClassDetailScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/ClassDetailScreen.kt index 35e54ee5..d972769a 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/ClassDetailScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/ClassDetailScreen.kt @@ -255,7 +255,6 @@ fun ClassDetailScreen( navigator.navigate( FolderDetailScreenDestination( id = it.id, - code = "" ) ) }, diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/ClassDetailViewModel.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/ClassDetailViewModel.kt index 92a2807c..ca392ddf 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/ClassDetailViewModel.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/ClassDetailViewModel.kt @@ -39,7 +39,6 @@ class ClassDetailViewModel @Inject constructor( val uiEvent = _uiEvent.receiveAsFlow() init { - val joinClassCode: String = savedStateHandle["code"] ?: "" val id: String = savedStateHandle["id"] ?: "" val title: String = savedStateHandle["title"] ?: "" val description: String = savedStateHandle["description"] ?: "" @@ -49,7 +48,6 @@ class ClassDetailViewModel @Inject constructor( _uiState.update { it.copy( isLogin = true, - joinClassCode = joinClassCode, id = id, title = title, description = description diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/classes/JoinClassScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/classes/JoinClassScreen.kt index 8bc77367..09703f19 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/classes/JoinClassScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/classes/JoinClassScreen.kt @@ -72,7 +72,6 @@ fun JoinClassScreen( navigator.navigate( ClassDetailScreenDestination( id = event.id, - code = event.classCode, title = event.title, description = event.description ) diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/folder/LoadFolderScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/folder/LoadFolderScreen.kt index 75d2f36c..e291781e 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/folder/LoadFolderScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/folder/LoadFolderScreen.kt @@ -35,7 +35,6 @@ fun LoadFolderScreen( navigator.navigate( FolderDetailScreenDestination( id = event.folderId, - code = uiState.folderCode, ) ) { popUpTo(NavGraphs.root) { diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/folder/create/CreateFolderScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/folder/create/CreateFolderScreen.kt index 82f7a78e..7a53f6f7 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/folder/create/CreateFolderScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/folder/create/CreateFolderScreen.kt @@ -47,7 +47,6 @@ fun CreateFolderScreen( navigator.navigate( FolderDetailScreenDestination( id = event.id, - code = "" ) ) } diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/folder/detail/FolderDetailArgs.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/folder/detail/FolderDetailArgs.kt index f3c69e3c..bedf614f 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/folder/detail/FolderDetailArgs.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/folder/detail/FolderDetailArgs.kt @@ -2,5 +2,4 @@ package com.pwhs.quickmem.presentation.app.folder.detail data class FolderDetailArgs ( val id: String, - val code: String, ) \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/home/HomeScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/home/HomeScreen.kt index 9ba2f384..e2547f13 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/home/HomeScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/home/HomeScreen.kt @@ -139,7 +139,6 @@ fun HomeScreen( navigator.navigate( ClassDetailScreenDestination( id = it.id, - code = it.joinToken ?: "", title = it.title, description = it.description ) @@ -149,7 +148,6 @@ fun HomeScreen( navigator.navigate( FolderDetailScreenDestination( id = it.id, - code = "" ) ) }, diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/library/LibraryScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/library/LibraryScreen.kt index 6317715e..8f6546c5 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/library/LibraryScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/library/LibraryScreen.kt @@ -146,7 +146,6 @@ fun LibraryScreen( navigator.navigate( ClassDetailScreenDestination( id = it.id, - code = it.joinToken ?: "", title = it.title, description = it.description ) @@ -156,7 +155,6 @@ fun LibraryScreen( navigator.navigate( FolderDetailScreenDestination( id = it.id, - code = "" ) ) }, diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/search_result/SearchResultScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/search_result/SearchResultScreen.kt index f5ad4ebb..379ac803 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/search_result/SearchResultScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/search_result/SearchResultScreen.kt @@ -144,7 +144,6 @@ fun SearchResultScreen( navigator.navigate( ClassDetailScreenDestination( id = it?.id ?: "", - code = it?.joinToken ?: "", title = it?.title ?: "", description = it?.description ?: "" ) @@ -154,7 +153,6 @@ fun SearchResultScreen( navigator.navigate( FolderDetailScreenDestination( id = it?.id ?: "", - code = "" ) ) }, diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/user_detail/UserDetailScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/user_detail/UserDetailScreen.kt index 631d3fca..f5e69992 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/user_detail/UserDetailScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/user_detail/UserDetailScreen.kt @@ -147,7 +147,6 @@ fun UserDetailScreen( id = it.id, description = it.description, title = it.title, - code = "" ) ) }, @@ -162,7 +161,6 @@ fun UserDetailScreen( navigator.navigate( FolderDetailScreenDestination( id = it.id, - code = "" ) ) }, From 10f67fb45c31c50e55213949ed1554377a4506c5 Mon Sep 17 00:00:00 2001 From: Nguyen Quang Minh Date: Sat, 30 Nov 2024 11:41:05 +0700 Subject: [PATCH 3/7] =?UTF-8?q?feat(class,=20notification):=20tham=20gia?= =?UTF-8?q?=20class=20th=C3=B4ng=20qua=20notification?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 1 - app/src/main/java/com/pwhs/quickmem/App.kt | 4 +-- .../core/data/enums/NotificationType.kt | 6 ++++ .../service/AppFirebaseMessagingService.kt | 28 +++++++++++---- ...RequestDto.kt => DeviceTokenRequestDto.kt} | 2 +- .../GetNotificationResponseDto.kt | 5 +++ .../dto/notification/NotificationDataDto.kt | 10 ++++++ .../GetNotificationResponseMapper.kt | 8 +++-- .../notification/NotificationDataMapper.kt | 14 ++++++++ .../pwhs/quickmem/data/remote/ApiService.kt | 4 +-- .../GetNotificationResponseModel.kt | 4 +++ .../notification/NotificationDataModel.kt | 6 ++++ .../app/deeplink/DeepLinkScreen.kt | 10 +----- .../app/deeplink/classes/JoinClassArgs.kt | 3 +- .../app/deeplink/classes/JoinClassScreen.kt | 17 +++++---- .../app/deeplink/classes/JoinClassUiState.kt | 2 +- .../deeplink/classes/JoinClassViewModel.kt | 17 +++++---- .../app/deeplink/folder/LoadFolderArgs.kt | 2 +- .../app/deeplink/folder/LoadFolderUiState.kt | 1 - .../deeplink/folder/LoadFolderViewModel.kt | 5 +-- .../deeplink/study_set/LoadStudySetArgs.kt | 1 - .../deeplink/study_set/LoadStudySetUiState.kt | 1 - .../study_set/LoadStudySetViewModel.kt | 6 +--- .../presentation/app/home/HomeScreen.kt | 35 ++++++++++--------- .../app/home/components/NotificationItem.kt | 6 ++-- .../components/NotificationListBottomSheet.kt | 8 +++-- .../presentation/app/search/SearchScreen.kt | 3 +- 27 files changed, 134 insertions(+), 75 deletions(-) create mode 100644 app/src/main/java/com/pwhs/quickmem/core/data/enums/NotificationType.kt rename app/src/main/java/com/pwhs/quickmem/data/dto/notification/{TokenRequestDto.kt => DeviceTokenRequestDto.kt} (86%) create mode 100644 app/src/main/java/com/pwhs/quickmem/data/dto/notification/NotificationDataDto.kt create mode 100644 app/src/main/java/com/pwhs/quickmem/data/mapper/notification/NotificationDataMapper.kt create mode 100644 app/src/main/java/com/pwhs/quickmem/domain/model/notification/NotificationDataModel.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6202b8df..e9e2e707 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -101,7 +101,6 @@ dependencies { ksp(libs.androidx.room.compiler) implementation(libs.compose.charts) - implementation(libs.play.services.ads) implementation(project(":compose-cardstack")) diff --git a/app/src/main/java/com/pwhs/quickmem/App.kt b/app/src/main/java/com/pwhs/quickmem/App.kt index 947f4841..0ef74e71 100644 --- a/app/src/main/java/com/pwhs/quickmem/App.kt +++ b/app/src/main/java/com/pwhs/quickmem/App.kt @@ -6,7 +6,7 @@ import com.onesignal.OneSignal import com.onesignal.debug.LogLevel as OneSignalLogLevel import com.pwhs.quickmem.core.datastore.AppManager import com.pwhs.quickmem.core.datastore.TokenManager -import com.pwhs.quickmem.data.dto.notification.TokenRequestDto +import com.pwhs.quickmem.data.dto.notification.DeviceTokenRequestDto import com.pwhs.quickmem.data.remote.ApiService import com.revenuecat.purchases.LogLevel as RevenueCatLogLevel import com.revenuecat.purchases.Purchases @@ -78,7 +78,7 @@ class App : Application() { val accessToken = tokenManager.accessToken.firstOrNull() ?: "" val userId = appManager.userId.firstOrNull() ?: "" try { - apiService.sendDeviceToken(accessToken, TokenRequestDto(userId, token)) + apiService.sendDeviceToken(accessToken, DeviceTokenRequestDto(userId, token)) Timber.d("Token sent to server successfully.") } catch (e: Exception) { Timber.e(e, "Error sending token to server") diff --git a/app/src/main/java/com/pwhs/quickmem/core/data/enums/NotificationType.kt b/app/src/main/java/com/pwhs/quickmem/core/data/enums/NotificationType.kt new file mode 100644 index 00000000..3d51ab61 --- /dev/null +++ b/app/src/main/java/com/pwhs/quickmem/core/data/enums/NotificationType.kt @@ -0,0 +1,6 @@ +package com.pwhs.quickmem.core.data.enums + +enum class NotificationType(val type: String) { + INVITE_USER_JOIN_CLASS("INVITE_USER_JOIN_CLASS"), + NONE("NONE"), +} \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/core/service/AppFirebaseMessagingService.kt b/app/src/main/java/com/pwhs/quickmem/core/service/AppFirebaseMessagingService.kt index a00ab904..b7a2d68b 100644 --- a/app/src/main/java/com/pwhs/quickmem/core/service/AppFirebaseMessagingService.kt +++ b/app/src/main/java/com/pwhs/quickmem/core/service/AppFirebaseMessagingService.kt @@ -7,13 +7,14 @@ import android.content.Intent import android.os.Build.VERSION.SDK_INT import android.os.Build.VERSION_CODES import androidx.core.app.NotificationCompat +import androidx.core.net.toUri import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage import com.pwhs.quickmem.MainActivity import com.pwhs.quickmem.R import com.pwhs.quickmem.core.datastore.AppManager import com.pwhs.quickmem.core.datastore.TokenManager -import com.pwhs.quickmem.data.dto.notification.TokenRequestDto +import com.pwhs.quickmem.data.dto.notification.DeviceTokenRequestDto import com.pwhs.quickmem.data.remote.ApiService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -42,7 +43,7 @@ class AppFirebaseMessagingService : FirebaseMessagingService() { val accessToken = tokenManager.accessToken.firstOrNull() ?: "" val userId = appManager.userId.firstOrNull() ?: "" try { - apiService.sendDeviceToken(accessToken, TokenRequestDto(userId, token)) + apiService.sendDeviceToken(accessToken, DeviceTokenRequestDto(userId, token)) Timber.d("Token sent to server successfully.") } catch (e: Exception) { Timber.e(e, "Error sending token to server") @@ -54,23 +55,38 @@ class AppFirebaseMessagingService : FirebaseMessagingService() { override fun onMessageReceived(remoteMessage: RemoteMessage) { Timber.d("From: ${remoteMessage.from}") + remoteMessage.notification?.title?.let { Timber.d("Notification Title: $it") } + remoteMessage.notification?.body?.let { Timber.d("Notification Body: $it") } + remoteMessage.data.isNotEmpty() + .let { Timber.d("Message data payload: ${remoteMessage.data}") } CoroutineScope(Dispatchers.IO).launch { val isPushNotificationsEnabled = appManager.pushNotifications.firstOrNull() ?: false if (isPushNotificationsEnabled) { remoteMessage.notification?.let { - showNotification(it.title, it.body) + showNotification(it.title, it.body, remoteMessage.data) } } } } - private fun showNotification(title: String?, body: String?) { + private fun showNotification(title: String?, body: String?, data: Map) { val channelId = "QuickMem Channel" val notificationId = 0 - val intent = Intent(this, MainActivity::class.java).apply { - addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + val notificationType = data["notificationType"] + val classCode = data["code"] + + val intent = if (notificationType == "INVITE_USER_JOIN_CLASS" && classCode != null) { + Intent( + Intent.ACTION_VIEW, + "quickmem://join/class?code=$classCode".toUri() + ) + } else { + Intent(this, MainActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + } } + val pendingIntent = PendingIntent.getActivity( this, 0, diff --git a/app/src/main/java/com/pwhs/quickmem/data/dto/notification/TokenRequestDto.kt b/app/src/main/java/com/pwhs/quickmem/data/dto/notification/DeviceTokenRequestDto.kt similarity index 86% rename from app/src/main/java/com/pwhs/quickmem/data/dto/notification/TokenRequestDto.kt rename to app/src/main/java/com/pwhs/quickmem/data/dto/notification/DeviceTokenRequestDto.kt index 8a925029..8519d576 100644 --- a/app/src/main/java/com/pwhs/quickmem/data/dto/notification/TokenRequestDto.kt +++ b/app/src/main/java/com/pwhs/quickmem/data/dto/notification/DeviceTokenRequestDto.kt @@ -2,7 +2,7 @@ package com.pwhs.quickmem.data.dto.notification import com.google.gson.annotations.SerializedName -data class TokenRequestDto( +data class DeviceTokenRequestDto( @SerializedName("userId") val userId: String, @SerializedName("deviceToken") diff --git a/app/src/main/java/com/pwhs/quickmem/data/dto/notification/GetNotificationResponseDto.kt b/app/src/main/java/com/pwhs/quickmem/data/dto/notification/GetNotificationResponseDto.kt index c585b67a..8e4c7b1f 100644 --- a/app/src/main/java/com/pwhs/quickmem/data/dto/notification/GetNotificationResponseDto.kt +++ b/app/src/main/java/com/pwhs/quickmem/data/dto/notification/GetNotificationResponseDto.kt @@ -1,6 +1,7 @@ package com.pwhs.quickmem.data.dto.notification import com.google.gson.annotations.SerializedName +import com.pwhs.quickmem.core.data.enums.NotificationType data class GetNotificationResponseDto( @SerializedName("id") @@ -13,6 +14,10 @@ data class GetNotificationResponseDto( val userId: String, @SerializedName("isRead") val isRead: Boolean, + @SerializedName("notificationType") + val notificationType: NotificationType? = NotificationType.NONE, + @SerializedName("data") + val data: NotificationDataDto? = null, @SerializedName("createdAt") val createdAt: String, @SerializedName("updatedAt") diff --git a/app/src/main/java/com/pwhs/quickmem/data/dto/notification/NotificationDataDto.kt b/app/src/main/java/com/pwhs/quickmem/data/dto/notification/NotificationDataDto.kt new file mode 100644 index 00000000..e0db1034 --- /dev/null +++ b/app/src/main/java/com/pwhs/quickmem/data/dto/notification/NotificationDataDto.kt @@ -0,0 +1,10 @@ +package com.pwhs.quickmem.data.dto.notification + +import com.google.gson.annotations.SerializedName + +data class NotificationDataDto( + @SerializedName("id") + val id: String? = null, + @SerializedName("code") + val code: String? = null, +) diff --git a/app/src/main/java/com/pwhs/quickmem/data/mapper/notification/GetNotificationResponseMapper.kt b/app/src/main/java/com/pwhs/quickmem/data/mapper/notification/GetNotificationResponseMapper.kt index d3b7b88e..553769b9 100644 --- a/app/src/main/java/com/pwhs/quickmem/data/mapper/notification/GetNotificationResponseMapper.kt +++ b/app/src/main/java/com/pwhs/quickmem/data/mapper/notification/GetNotificationResponseMapper.kt @@ -10,7 +10,9 @@ fun GetNotificationResponseDto.toModel() = GetNotificationResponseModel( userId = userId, isRead = isRead, createdAt = createdAt, - updatedAt = updatedAt + updatedAt = updatedAt, + notificationType = notificationType, + data = data?.toModel() ) fun GetNotificationResponseModel.toDto() = GetNotificationResponseDto( @@ -20,5 +22,7 @@ fun GetNotificationResponseModel.toDto() = GetNotificationResponseDto( userId = userId, isRead = isRead, createdAt = createdAt, - updatedAt = updatedAt + updatedAt = updatedAt, + notificationType = notificationType, + data = data?.toDto() ) diff --git a/app/src/main/java/com/pwhs/quickmem/data/mapper/notification/NotificationDataMapper.kt b/app/src/main/java/com/pwhs/quickmem/data/mapper/notification/NotificationDataMapper.kt new file mode 100644 index 00000000..5a5903a5 --- /dev/null +++ b/app/src/main/java/com/pwhs/quickmem/data/mapper/notification/NotificationDataMapper.kt @@ -0,0 +1,14 @@ +package com.pwhs.quickmem.data.mapper.notification + +import com.pwhs.quickmem.data.dto.notification.NotificationDataDto +import com.pwhs.quickmem.domain.model.notification.NotificationDataModel + +fun NotificationDataDto.toModel() = NotificationDataModel( + id = id, + code = code +) + +fun NotificationDataModel.toDto() = NotificationDataDto( + id = id, + code = code +) \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/data/remote/ApiService.kt b/app/src/main/java/com/pwhs/quickmem/data/remote/ApiService.kt index a7b66786..cd27dad4 100644 --- a/app/src/main/java/com/pwhs/quickmem/data/remote/ApiService.kt +++ b/app/src/main/java/com/pwhs/quickmem/data/remote/ApiService.kt @@ -57,7 +57,7 @@ import com.pwhs.quickmem.data.dto.folder.UpdateFolderRequestDto import com.pwhs.quickmem.data.dto.folder.UpdateFolderResponseDto import com.pwhs.quickmem.data.dto.notification.GetNotificationResponseDto import com.pwhs.quickmem.data.dto.notification.MarkNotificationReadRequestDto -import com.pwhs.quickmem.data.dto.notification.TokenRequestDto +import com.pwhs.quickmem.data.dto.notification.DeviceTokenRequestDto import com.pwhs.quickmem.data.dto.flashcard.WriteStatusFlashCardDto import com.pwhs.quickmem.data.dto.streak.GetStreakDto import com.pwhs.quickmem.data.dto.streak.GetTopStreakResponseDto @@ -564,7 +564,7 @@ interface ApiService { @POST("notifications/register") suspend fun sendDeviceToken( @Header("Authorization") authorization: String, - @Body tokenRequest: TokenRequestDto + @Body tokenRequest: DeviceTokenRequestDto ): Response @GET("notifications/user/{id}") diff --git a/app/src/main/java/com/pwhs/quickmem/domain/model/notification/GetNotificationResponseModel.kt b/app/src/main/java/com/pwhs/quickmem/domain/model/notification/GetNotificationResponseModel.kt index 57412118..fc807eb6 100644 --- a/app/src/main/java/com/pwhs/quickmem/domain/model/notification/GetNotificationResponseModel.kt +++ b/app/src/main/java/com/pwhs/quickmem/domain/model/notification/GetNotificationResponseModel.kt @@ -1,10 +1,14 @@ package com.pwhs.quickmem.domain.model.notification +import com.pwhs.quickmem.core.data.enums.NotificationType + data class GetNotificationResponseModel( val id: String, val title: String, val message: String, val userId: String, + val notificationType: NotificationType? = NotificationType.NONE, + val data: NotificationDataModel? = null, val isRead: Boolean, val createdAt: String, val updatedAt: String diff --git a/app/src/main/java/com/pwhs/quickmem/domain/model/notification/NotificationDataModel.kt b/app/src/main/java/com/pwhs/quickmem/domain/model/notification/NotificationDataModel.kt new file mode 100644 index 00000000..eefc9933 --- /dev/null +++ b/app/src/main/java/com/pwhs/quickmem/domain/model/notification/NotificationDataModel.kt @@ -0,0 +1,6 @@ +package com.pwhs.quickmem.domain.model.notification + +data class NotificationDataModel( + val id: String? = null, + val code: String? = null, +) \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/DeepLinkScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/DeepLinkScreen.kt index 748d9f1b..159e1d42 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/DeepLinkScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/DeepLinkScreen.kt @@ -2,7 +2,6 @@ package com.pwhs.quickmem.presentation.app.deeplink import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.ui.Modifier import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.annotation.parameters.DeepLink @@ -11,10 +10,7 @@ import com.ramcosta.composedestinations.generated.destinations.JoinClassScreenDe import com.ramcosta.composedestinations.generated.destinations.LoadFolderScreenDestination import com.ramcosta.composedestinations.generated.destinations.LoadStudySetScreenDestination import com.ramcosta.composedestinations.navigation.DestinationsNavigator -import timber.log.Timber -//Deeplink for receiving code from class, folder, study set -//TODO: Implement this screen @Destination( deepLinks = [ DeepLink(uriPattern = "quickmem://join/class?code={classCode}"), @@ -24,20 +20,18 @@ import timber.log.Timber ) @Composable fun DeepLinkScreen( - modifier: Modifier = Modifier, studySetCode: String? = null, folderCode: String? = null, classCode: String? = null, navigator: DestinationsNavigator, ) { - Timber.d("DeepLinkScreen: studySetCode: $studySetCode, folderCode: $folderCode, classCode: $classCode") LaunchedEffect(key1 = true) { when { classCode != null -> { navigator.navigate( JoinClassScreenDestination( code = classCode, - type = "class" + isFromDeepLink = true ) ) { popUpTo(NavGraphs.root) { @@ -51,7 +45,6 @@ fun DeepLinkScreen( folderCode != null -> { navigator.navigate( LoadFolderScreenDestination( - type = "folder", folderCode = folderCode ) ) { @@ -66,7 +59,6 @@ fun DeepLinkScreen( studySetCode != null -> { navigator.navigate( LoadStudySetScreenDestination( - type = "studySet", studySetCode = studySetCode ) ) { diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/classes/JoinClassArgs.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/classes/JoinClassArgs.kt index a189acab..22d5fab5 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/classes/JoinClassArgs.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/classes/JoinClassArgs.kt @@ -1,6 +1,7 @@ package com.pwhs.quickmem.presentation.app.deeplink.classes + data class JoinClassArgs( val code: String, - val type: String + val isFromDeepLink: Boolean ) \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/classes/JoinClassScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/classes/JoinClassScreen.kt index 09703f19..8372b9cf 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/classes/JoinClassScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/classes/JoinClassScreen.kt @@ -9,7 +9,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Home +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.Button import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api @@ -69,6 +69,9 @@ fun JoinClassScreen( viewModel.uiEvent.collect { event -> when (event) { is JoinClassUiEvent.JoinedClass -> { + if (!uiState.isFromDeepLink) { + navigator.navigateUp() + } navigator.navigate( ClassDetailScreenDestination( id = event.id, @@ -76,11 +79,13 @@ fun JoinClassScreen( description = event.description ) ) { - popUpTo(NavGraphs.root) { - saveState = false + if (uiState.isFromDeepLink) { + popUpTo(NavGraphs.root) { + saveState = false + } + launchSingleTop = true + restoreState = false } - launchSingleTop = true - restoreState = false } } @@ -156,7 +161,7 @@ fun JoinClass( onClick = onBackHome ) { Icon( - imageVector = Icons.Default.Home, + imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back", tint = Color.Gray.copy(alpha = 0.6f) ) diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/classes/JoinClassUiState.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/classes/JoinClassUiState.kt index d5cbde04..7687b5e2 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/classes/JoinClassUiState.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/classes/JoinClassUiState.kt @@ -8,6 +8,6 @@ data class JoinClassUiState( val classId: String? = null, val isUnAuthorized: Boolean = false, val code: String? = null, - val type: String? = null, + val isFromDeepLink: Boolean = false, val classDetailResponseModel: GetClassDetailResponseModel? = null ) \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/classes/JoinClassViewModel.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/classes/JoinClassViewModel.kt index ab746cf3..2f18b178 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/classes/JoinClassViewModel.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/classes/JoinClassViewModel.kt @@ -34,15 +34,12 @@ class JoinClassViewModel @Inject constructor( val uiEvent = _uiEvent.receiveAsFlow() init { - val code = savedStateHandle.get("code") - val type = savedStateHandle.get("type") - - Timber.d("JoinClassViewModel: code: $code, type: $type") - + val code = savedStateHandle.get("code") ?: "" + val isFromDeepLink = savedStateHandle.get("isFromDeepLink") ?: false _uiState.update { it.copy( code = code, - type = type + isFromDeepLink = isFromDeepLink ) } @@ -85,6 +82,14 @@ class JoinClassViewModel @Inject constructor( is Resources.Success -> { if (resource.data?.owner?.id == userId || resource.data?.isJoined == true) { + _uiState.update { + it.copy( + classDetailResponseModel = resource.data, + isLoading = false, + userId = userId, + classId = resource.data.id + ) + } _uiEvent.send( JoinClassUiEvent.JoinedClass( id = resource.data.id, diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/folder/LoadFolderArgs.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/folder/LoadFolderArgs.kt index 6b738cff..a8b214c6 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/folder/LoadFolderArgs.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/folder/LoadFolderArgs.kt @@ -1,6 +1,6 @@ package com.pwhs.quickmem.presentation.app.deeplink.folder + data class LoadFolderArgs( val folderCode: String, - val type: String ) \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/folder/LoadFolderUiState.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/folder/LoadFolderUiState.kt index 29ac10b3..138eb39e 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/folder/LoadFolderUiState.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/folder/LoadFolderUiState.kt @@ -2,7 +2,6 @@ package com.pwhs.quickmem.presentation.app.deeplink.folder data class LoadFolderUiState( val folderCode: String = "", - val type: String = "", val isLoading: Boolean = true, val folderId: String? = null ) \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/folder/LoadFolderViewModel.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/folder/LoadFolderViewModel.kt index 5ffd6f0c..88902336 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/folder/LoadFolderViewModel.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/folder/LoadFolderViewModel.kt @@ -30,12 +30,10 @@ class LoadFolderViewModel @Inject constructor( init { val folderCode = saveStateHandle.get("folderCode") ?: "" - val type = saveStateHandle.get("type") ?: "" _uiState.update { it.copy( folderCode = folderCode, - type = type ) } loadFolder() @@ -44,10 +42,9 @@ class LoadFolderViewModel @Inject constructor( private fun loadFolder() { viewModelScope.launch { val folderCode = uiState.value.folderCode - val type = uiState.value.type val token = tokenManager.accessToken.firstOrNull() - if (folderCode.isEmpty() || type.isEmpty() || token == null) { + if (folderCode.isEmpty() || token == null) { _uiEvent.send(LoadFolderUiEvent.UnAuthorized) return@launch } diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/study_set/LoadStudySetArgs.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/study_set/LoadStudySetArgs.kt index 0da55621..fcabd1e6 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/study_set/LoadStudySetArgs.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/study_set/LoadStudySetArgs.kt @@ -2,5 +2,4 @@ package com.pwhs.quickmem.presentation.app.deeplink.study_set data class LoadStudySetArgs( val studySetCode: String, - val type: String ) \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/study_set/LoadStudySetUiState.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/study_set/LoadStudySetUiState.kt index 1f60703f..c75654d4 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/study_set/LoadStudySetUiState.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/study_set/LoadStudySetUiState.kt @@ -2,7 +2,6 @@ package com.pwhs.quickmem.presentation.app.deeplink.study_set data class LoadStudySetUiState( val studySetCode: String = "", - val type: String = "", val isLoading: Boolean = true, val studySetId: String? = null ) \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/study_set/LoadStudySetViewModel.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/study_set/LoadStudySetViewModel.kt index 4108faac..98e148ca 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/study_set/LoadStudySetViewModel.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/study_set/LoadStudySetViewModel.kt @@ -3,7 +3,6 @@ package com.pwhs.quickmem.presentation.app.deeplink.study_set import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.pwhs.quickmem.core.datastore.AppManager import com.pwhs.quickmem.core.datastore.TokenManager import com.pwhs.quickmem.core.utils.Resources import com.pwhs.quickmem.domain.repository.StudySetRepository @@ -31,12 +30,10 @@ class LoadStudySetViewModel @Inject constructor( init { val studySetCode = savedStateHandle.get("studySetCode") ?: "" - val type = savedStateHandle.get("type") ?: "" _uiState.update { it.copy( studySetCode = studySetCode, - type = type ) } loadStudySet() @@ -45,10 +42,9 @@ class LoadStudySetViewModel @Inject constructor( private fun loadStudySet() { viewModelScope.launch { val studySetCode = uiState.value.studySetCode - val type = uiState.value.type val token = tokenManager.accessToken.firstOrNull() - if (studySetCode.isEmpty() || type.isEmpty() || token == null) { + if (studySetCode.isEmpty() || token == null) { _uiEvent.send(LoadStudySetUiEvent.UnAuthorized) return@launch } diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/home/HomeScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/home/HomeScreen.kt index e2547f13..2b0a1f56 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/home/HomeScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/home/HomeScreen.kt @@ -71,6 +71,7 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.isGranted import com.google.accompanist.permissions.rememberPermissionState import com.pwhs.quickmem.R +import com.pwhs.quickmem.core.data.enums.NotificationType import com.pwhs.quickmem.domain.model.classes.GetClassByOwnerResponseModel import com.pwhs.quickmem.domain.model.folder.GetFolderResponseModel import com.pwhs.quickmem.domain.model.notification.GetNotificationResponseModel @@ -94,6 +95,7 @@ import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.generated.destinations.ClassDetailScreenDestination import com.ramcosta.composedestinations.generated.destinations.CreateStudySetScreenDestination import com.ramcosta.composedestinations.generated.destinations.FolderDetailScreenDestination +import com.ramcosta.composedestinations.generated.destinations.JoinClassScreenDestination import com.ramcosta.composedestinations.generated.destinations.SearchScreenDestination import com.ramcosta.composedestinations.generated.destinations.SearchStudySetBySubjectScreenDestination import com.ramcosta.composedestinations.generated.destinations.StudySetDetailScreenDestination @@ -162,9 +164,19 @@ fun HomeScreen( viewModel.onEvent(HomeUiAction.OnChangeCustomerInfo(customerInfo)) }, notifications = uiState.notifications, - onNotificationClicked = { notificationId -> + onMarkAsRead = { notificationId -> viewModel.onEvent(HomeUiAction.MarkAsRead(notificationId)) }, + onNotificationClick = { notification -> + if (notification.data?.id?.isNotEmpty() == true && notification.data.code?.isNotEmpty() == true && notification.notificationType == NotificationType.INVITE_USER_JOIN_CLASS) { + navigator.navigate( + JoinClassScreenDestination( + code = notification.data.code, + isFromDeepLink = false + ) + ) + } + }, onSearchStudySetBySubject = { subject -> navigator.navigate( SearchStudySetBySubjectScreenDestination( @@ -206,7 +218,8 @@ private fun Home( onClickToCreateStudySet: () -> Unit = {}, customer: CustomerInfo? = null, onCustomerInfoChanged: (CustomerInfo) -> Unit = {}, - onNotificationClicked: (String) -> Unit = {}, + onMarkAsRead: (String) -> Unit = {}, + onNotificationClick: (GetNotificationResponseModel) -> Unit = {}, notifications: List = emptyList(), onSearchStudySetBySubject: (SubjectModel) -> Unit = {}, ) { @@ -338,20 +351,7 @@ private fun Home( modifier = Modifier .align(Alignment.TopEnd) .size(16.dp), - ) { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - Text( - text = "$notificationCount", - style = typography.bodySmall.copy( - fontSize = 10.sp, - fontWeight = FontWeight.Bold, - ) - ) - } - } + ) } } } @@ -626,7 +626,8 @@ private fun Home( NotificationListBottomSheet( onDismissRequest = { showNotificationBottomSheet = false }, notifications = notifications, - onNotificationClicked = onNotificationClicked, + onMarkAsRead = onMarkAsRead, + onNotificationClicked = onNotificationClick, sheetState = modalBottomSheetState ) } diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/home/components/NotificationItem.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/home/components/NotificationItem.kt index cd258819..69976e08 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/home/components/NotificationItem.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/home/components/NotificationItem.kt @@ -1,7 +1,6 @@ package com.pwhs.quickmem.presentation.app.home.components import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth @@ -30,6 +29,7 @@ import com.pwhs.quickmem.util.calculateTimeAgo fun NotificationItem( notification: GetNotificationResponseModel, onMarkAsRead: (String) -> Unit, + onNotificationClicked: (GetNotificationResponseModel) -> Unit ) { val backgroundColor = if (notification.isRead) { @@ -53,6 +53,7 @@ fun NotificationItem( if (!notification.isRead) { onMarkAsRead(notification.id) } + onNotificationClicked(notification) }, colors = CardDefaults.cardColors( containerColor = backgroundColor @@ -62,8 +63,7 @@ fun NotificationItem( Row( modifier = Modifier .fillMaxWidth() - .background(backgroundColor) - .clickable { onMarkAsRead(notification.id) }, + .background(backgroundColor), verticalAlignment = Alignment.CenterVertically ) { Column( diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/home/components/NotificationListBottomSheet.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/home/components/NotificationListBottomSheet.kt index ee8772ef..b7cb5725 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/home/components/NotificationListBottomSheet.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/home/components/NotificationListBottomSheet.kt @@ -36,7 +36,8 @@ fun NotificationListBottomSheet( onDismissRequest: () -> Unit, sheetState: SheetState, notifications: List, - onNotificationClicked: (String) -> Unit, + onMarkAsRead: (String) -> Unit, + onNotificationClicked: (GetNotificationResponseModel) -> Unit ) { ModalBottomSheet( @@ -91,8 +92,9 @@ fun NotificationListBottomSheet( NotificationItem( notification = notification, onMarkAsRead = { - onNotificationClicked(notification.id) - } + onMarkAsRead(it) + }, + onNotificationClicked = onNotificationClicked ) HorizontalDivider( modifier = Modifier.padding(horizontal = 10.dp), diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/search/SearchScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/search/SearchScreen.kt index 8e771376..11da5c26 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/search/SearchScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/search/SearchScreen.kt @@ -139,8 +139,7 @@ private fun Search( errorMessage = errorMessage, placeholder = stringResource(R.string.txt_study_sets_folder_classes), onSearch = onSearch, - - ) + ) }, expandedHeight = 160.dp, actions = { From 9beeabb42005930cd8be43cf445eec43e9467afd Mon Sep 17 00:00:00 2001 From: Nguyen Quang Minh Date: Sat, 30 Nov 2024 11:50:53 +0700 Subject: [PATCH 4/7] =?UTF-8?q?feat(auth):=20t=E1=BB=91i=20=C6=B0u=20giao?= =?UTF-8?q?=20di=E1=BB=87n=20auth=20-=20lo=E1=BA=A1i=20b=E1=BB=8F=20logo?= =?UTF-8?q?=20=E1=BB=9F=20m=C3=A0n=20login=20v=C3=A0=20sign=20up=20-=20nav?= =?UTF-8?q?igate=20to=20policy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/component/AuthTopAppBar.kt | 17 -------------- .../presentation/auth/login/LoginScreen.kt | 6 ----- .../presentation/auth/signup/SignupScreen.kt | 22 ++++++++++++++++--- .../auth/signup/SignupViewModel.kt | 5 ----- 4 files changed, 19 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/auth/component/AuthTopAppBar.kt b/app/src/main/java/com/pwhs/quickmem/presentation/auth/component/AuthTopAppBar.kt index e477e8c4..ec82e45b 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/auth/component/AuthTopAppBar.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/auth/component/AuthTopAppBar.kt @@ -1,7 +1,5 @@ package com.pwhs.quickmem.presentation.auth.component -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons.AutoMirrored.Filled import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.ExperimentalMaterial3Api @@ -11,19 +9,14 @@ import androidx.compose.material3.IconButtonDefaults.iconButtonColors import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults.topAppBarColors import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.pwhs.quickmem.R import com.pwhs.quickmem.ui.theme.QuickMemTheme @OptIn(ExperimentalMaterial3Api::class) @Composable fun AuthTopAppBar( onClick: () -> Unit = {}, - showLogo: Boolean = false ) { TopAppBar( colors = topAppBarColors( @@ -44,16 +37,6 @@ fun AuthTopAppBar( }, title = { }, - actions = { - if (showLogo) { - Image( - painter = painterResource(id = R.drawable.ic_logo), - contentDescription = "QuickMem Logo", - modifier = Modifier - .size(32.dp) - ) - } - } ) } diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/auth/login/LoginScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/auth/login/LoginScreen.kt index c606dfe1..5208c6ea 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/auth/login/LoginScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/auth/login/LoginScreen.kt @@ -13,12 +13,9 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString @@ -50,8 +47,6 @@ fun LoginScreen( navigator: DestinationsNavigator, viewModel: LoginViewModel = hiltViewModel(), ) { - val uiState by viewModel.uiState.collectAsState() - val context = LocalContext.current Timber.d("Run here") LaunchedEffect(key1 = true) { viewModel.uiEvent.collect { event -> @@ -128,7 +123,6 @@ fun Login( topBar = { AuthTopAppBar( onClick = onNavigationIconClick, - showLogo = true, ) } ) { innerPadding -> diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/auth/signup/SignupScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/auth/signup/SignupScreen.kt index 27d386ed..18232c48 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/auth/signup/SignupScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/auth/signup/SignupScreen.kt @@ -1,5 +1,7 @@ package com.pwhs.quickmem.presentation.auth.signup +import android.content.Intent +import android.net.Uri import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -16,6 +18,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString @@ -47,6 +50,7 @@ fun SignupScreen( navigator: DestinationsNavigator, viewModel: SignupViewModel = hiltViewModel() ) { + val context = LocalContext.current LaunchedEffect(key1 = true) { viewModel.uiEvent.collect { event -> when (event) { @@ -95,8 +99,18 @@ fun SignupScreen( }, onSignupWithFacebook = { viewModel.signupWithFacebook() + }, + onPrivacyPolicyClick = { + val intent = Intent( + Intent.ACTION_VIEW, + Uri.parse("https://pass-with-high-score.github.io/QuickMem-Services/") + ) + try { + context.startActivity(intent) + } catch (e: Exception) { + e.stackTrace + } } - ) } @@ -108,6 +122,7 @@ fun Signup( onSignupWithEmail: () -> Unit = {}, onSignupWithGoogle: () -> Unit = {}, onSignupWithFacebook: () -> Unit = {}, + onPrivacyPolicyClick: () -> Unit = {} ) { Scaffold( modifier = modifier.gradientBackground(), @@ -115,7 +130,6 @@ fun Signup( topBar = { AuthTopAppBar( onClick = onNavigationIconClick, - showLogo = true ) } ) { innerPadding -> @@ -222,7 +236,9 @@ fun Signup( }, modifier = Modifier .padding(top = 16.dp) - .clickable { }, + .clickable { + onPrivacyPolicyClick() + }, textAlign = TextAlign.Center ) diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/auth/signup/SignupViewModel.kt b/app/src/main/java/com/pwhs/quickmem/presentation/auth/signup/SignupViewModel.kt index a1016102..53e16258 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/auth/signup/SignupViewModel.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/auth/signup/SignupViewModel.kt @@ -4,16 +4,12 @@ import android.app.Application import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import com.pwhs.quickmem.domain.repository.AuthRepository -import com.pwhs.quickmem.presentation.auth.login.LoginUiAction -import com.pwhs.quickmem.presentation.auth.login.LoginUiEvent -import com.pwhs.quickmem.presentation.auth.login.LoginUiState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch -import timber.log.Timber import javax.inject.Inject @HiltViewModel @@ -35,7 +31,6 @@ class SignupViewModel @Inject constructor( } fun signupWithGoogle() { - Timber.d("Signup with Google") viewModelScope.launch { _uiEvent.trySend(SignupUiEvent.SignupWithGoogle) } From d8329487cd190f10add50e320b518c79918db3b2 Mon Sep 17 00:00:00 2001 From: Nguyen Quang Minh Date: Sat, 30 Nov 2024 12:08:02 +0700 Subject: [PATCH 5/7] =?UTF-8?q?fix(ux):=20l=C6=B0u=20l=E1=BA=A1i=20tr?= =?UTF-8?q?=E1=BA=A1ng=20th=C3=A1i=20tabIndex?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/app/classes/detail/ClassDetailScreen.kt | 3 ++- .../presentation/app/deeplink/classes/JoinClassScreen.kt | 2 +- .../pwhs/quickmem/presentation/app/explore/ExploreScreen.kt | 3 ++- .../pwhs/quickmem/presentation/app/library/LibraryScreen.kt | 3 ++- .../presentation/app/search_result/SearchResultScreen.kt | 3 ++- .../presentation/app/study_set/detail/StudySetDetailScreen.kt | 3 ++- .../quickmem/presentation/app/user_detail/UserDetailScreen.kt | 4 ++-- 7 files changed, 13 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/ClassDetailScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/ClassDetailScreen.kt index d972769a..11882376 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/ClassDetailScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/ClassDetailScreen.kt @@ -25,6 +25,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -315,7 +316,7 @@ fun ClassDetail( onDeleteStudySetClick: (String) -> Unit = {}, onDeleteFolderClick: (String) -> Unit = {}, ) { - var tabIndex by remember { mutableIntStateOf(0) } + var tabIndex by rememberSaveable { mutableIntStateOf(0) } val tabTitles = listOf("Study sets", "Folders", "Members") val refreshState = rememberPullToRefreshState() diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/classes/JoinClassScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/classes/JoinClassScreen.kt index 8372b9cf..fd3929fd 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/classes/JoinClassScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/classes/JoinClassScreen.kt @@ -184,7 +184,7 @@ fun JoinClass( modifier = Modifier.padding(16.dp) ) { SettingItem( - title = "Title", + title = "Name", subtitle = classDetailResponseModel?.title ?: "", showArrow = false ) diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreScreen.kt index 00228d72..d5182b28 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreScreen.kt @@ -21,6 +21,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -133,7 +134,7 @@ fun Explore( onCreateStudySet: () -> Unit = {}, errorMessage: String = "" ) { - var tabIndex by remember { mutableIntStateOf(0) } + var tabIndex by rememberSaveable { mutableIntStateOf(0) } val tabTitles = listOf( "Create Study Set AI", "Top Streak", diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/library/LibraryScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/library/LibraryScreen.kt index 8f6546c5..ee2982f4 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/library/LibraryScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/library/LibraryScreen.kt @@ -25,6 +25,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -190,7 +191,7 @@ fun Library( navigateToCreateClass: () -> Unit = {}, navigateToCreateFolder: () -> Unit = {}, ) { - var tabIndex by remember { mutableIntStateOf(0) } + var tabIndex by rememberSaveable { mutableIntStateOf(0) } val tabTitles = listOf( stringResource(R.string.txt_study_sets), stringResource(R.string.txt_classes), diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/search_result/SearchResultScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/search_result/SearchResultScreen.kt index 379ac803..b1575680 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/search_result/SearchResultScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/search_result/SearchResultScreen.kt @@ -20,6 +20,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -69,7 +70,7 @@ fun SearchResultScreen( ) { val uiState by viewModel.uiState.collectAsState() val context = LocalContext.current - var tabIndex by remember { mutableIntStateOf(0) } + var tabIndex by rememberSaveable { mutableIntStateOf(0) } val studySetItems: LazyPagingItems = viewModel.studySetState.collectAsLazyPagingItems() diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/study_set/detail/StudySetDetailScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/study_set/detail/StudySetDetailScreen.kt index e795ee20..8a503a06 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/study_set/detail/StudySetDetailScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/study_set/detail/StudySetDetailScreen.kt @@ -26,6 +26,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -403,7 +404,7 @@ fun StudySetDetail( onReportClick: () -> Unit = {} ) { val context = LocalContext.current - var tabIndex by remember { mutableIntStateOf(0) } + var tabIndex by rememberSaveable { mutableIntStateOf(0) } val tabTitles = if (isOwner) { listOf( stringResource(R.string.txt_material), diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/user_detail/UserDetailScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/user_detail/UserDetailScreen.kt index f5e69992..7e8a2062 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/user_detail/UserDetailScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/user_detail/UserDetailScreen.kt @@ -28,7 +28,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -199,7 +199,7 @@ private fun UserDetail( onRefresh: () -> Unit = {}, onReportClick: () -> Unit = {} ) { - var tabIndex by remember { mutableIntStateOf(UserDetailTabEnum.STUDY_SET.index) } + var tabIndex by rememberSaveable { mutableIntStateOf(UserDetailTabEnum.STUDY_SET.index) } val tabTitles = listOf( stringResource(R.string.txt_study_sets), stringResource(R.string.txt_classes), From e65b696faf71e16d5f47bdac764c8461cb0d5823 Mon Sep 17 00:00:00 2001 From: Nguyen Quang Minh Date: Sat, 30 Nov 2024 13:29:10 +0700 Subject: [PATCH 6/7] =?UTF-8?q?feat(explore):=20t=E1=BA=A1o=20study=20set?= =?UTF-8?q?=20b=E1=BA=B1ng=20AI=20(s=E1=BB=AD=20d=E1=BB=A5ng=20coin=20?= =?UTF-8?q?=C4=91=E1=BB=83=20t=E1=BA=A1o)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 2 +- .../quickmem/core/data/enums/CoinAction.kt | 6 ++ .../quickmem/core/datastore/AppManager.kt | 14 +++ .../quickmem/data/dto/auth/AuthResponseDto.kt | 2 + .../dto/auth/GetUserProfileResponseDto.kt | 2 + .../data/dto/user/UpdateCoinRequestDto.kt | 12 +++ .../data/dto/user/UpdateCoinResponseDto.kt | 12 +++ .../auth/GetUserProfileResponseMapper.kt | 6 +- .../mapper/user/UpdateCoinRequestMapper.kt | 16 ++++ .../mapper/user/UpdateCoinResponseMapper.kt | 16 ++++ .../pwhs/quickmem/data/remote/ApiService.kt | 8 ++ .../remote/repository/AuthRepositoryImpl.kt | 22 +++++ .../domain/model/auth/AuthResponseModel.kt | 1 + .../model/auth/GetUserProfileResponseModel.kt | 1 + .../model/users/UpdateCoinRequestModel.kt | 7 ++ .../model/users/UpdateCoinResponseModel.kt | 7 ++ .../domain/repository/AuthRepository.kt | 6 ++ .../presentation/app/explore/ExploreScreen.kt | 88 +++++++++++++++++- .../app/explore/ExploreUiAction.kt | 1 + .../app/explore/ExploreUiState.kt | 5 +- .../app/explore/ExploreViewModel.kt | 93 +++++++++++++++++-- .../CreateStudySetAITab.kt | 32 +------ .../app/profile/ProfileViewModel.kt | 4 + .../login/email/LoginWithEmailViewModel.kt | 1 + .../com/pwhs/quickmem/util/ads/AdsUtil.kt | 35 +------ app/src/main/res/drawable/ic_coin.xml | 39 ++++++++ 26 files changed, 358 insertions(+), 80 deletions(-) create mode 100644 app/src/main/java/com/pwhs/quickmem/core/data/enums/CoinAction.kt create mode 100644 app/src/main/java/com/pwhs/quickmem/data/dto/user/UpdateCoinRequestDto.kt create mode 100644 app/src/main/java/com/pwhs/quickmem/data/dto/user/UpdateCoinResponseDto.kt create mode 100644 app/src/main/java/com/pwhs/quickmem/data/mapper/user/UpdateCoinRequestMapper.kt create mode 100644 app/src/main/java/com/pwhs/quickmem/data/mapper/user/UpdateCoinResponseMapper.kt create mode 100644 app/src/main/java/com/pwhs/quickmem/domain/model/users/UpdateCoinRequestModel.kt create mode 100644 app/src/main/java/com/pwhs/quickmem/domain/model/users/UpdateCoinResponseModel.kt create mode 100644 app/src/main/res/drawable/ic_coin.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e9e2e707..39c8fb2b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -40,7 +40,7 @@ android { localProperties.getProperty("REWARD_ADS_ID") ?: "ca-app-pub-5725743620724195/5188260450" val rewardedInterstitialAdsId: String = localProperties.getProperty("REWARDED_INTERSTITIAL_ADS_ID") - ?: "ca-app-pub-3940256099942544/5354046379" + ?: "ca-app-pub-5725743620724195/6760705307" val oneSignalAppId: String = localProperties.getProperty("ONESIGNAL_APP_ID") ?: "b2f7f966-d8cc-11e4-bed1-df8f05be55ba" val revenueCatApiKey: String = localProperties.getProperty("REVENUECAT_API_KEY") diff --git a/app/src/main/java/com/pwhs/quickmem/core/data/enums/CoinAction.kt b/app/src/main/java/com/pwhs/quickmem/core/data/enums/CoinAction.kt new file mode 100644 index 00000000..a7a11fa9 --- /dev/null +++ b/app/src/main/java/com/pwhs/quickmem/core/data/enums/CoinAction.kt @@ -0,0 +1,6 @@ +package com.pwhs.quickmem.core.data.enums + +enum class CoinAction (val action: String) { + ADD("add"), + SUBTRACT("subtract") +} \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/core/datastore/AppManager.kt b/app/src/main/java/com/pwhs/quickmem/core/datastore/AppManager.kt index 161a7e99..e2b59220 100644 --- a/app/src/main/java/com/pwhs/quickmem/core/datastore/AppManager.kt +++ b/app/src/main/java/com/pwhs/quickmem/core/datastore/AppManager.kt @@ -4,6 +4,7 @@ import android.content.Context import android.util.Patterns import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.intPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey import com.pwhs.quickmem.util.dataStore import kotlinx.coroutines.flow.Flow @@ -23,6 +24,7 @@ class AppManager(private val context: Context) { val USER_BIRTHDAY = stringPreferencesKey("USER_BIRTHDAY") val PUSH_NOTIFICATIONS = booleanPreferencesKey("PUSH_NOTIFICATIONS") val APP_PUSH_NOTIFICATIONS = booleanPreferencesKey("APP_PUSH_NOTIFICATIONS") + val USER_COINS = intPreferencesKey("USER_COINS") } val isFirstRun: Flow = context.dataStore.data @@ -72,6 +74,11 @@ class AppManager(private val context: Context) { preferences[USER_BIRTHDAY] ?: "" } + val userCoins: Flow = context.dataStore.data + .map { preferences -> + preferences[USER_COINS] ?: 0 + } + suspend fun saveIsFirstRun(isFirstRun: Boolean) { Timber.d("Saving is first run: $isFirstRun") context.dataStore.edit { preferences -> @@ -150,6 +157,13 @@ class AppManager(private val context: Context) { } } + suspend fun saveUserCoins(coins: Int) { + Timber.d("Saving user coins: $coins") + context.dataStore.edit { preferences -> + preferences[USER_COINS] = coins + } + } + suspend fun clearAllData() { context.dataStore.edit { preferences -> preferences.clear() diff --git a/app/src/main/java/com/pwhs/quickmem/data/dto/auth/AuthResponseDto.kt b/app/src/main/java/com/pwhs/quickmem/data/dto/auth/AuthResponseDto.kt index 8b885e44..e7c555e9 100644 --- a/app/src/main/java/com/pwhs/quickmem/data/dto/auth/AuthResponseDto.kt +++ b/app/src/main/java/com/pwhs/quickmem/data/dto/auth/AuthResponseDto.kt @@ -25,4 +25,6 @@ data class AuthResponseDto( val provider: String? = null, @SerializedName("isVerified") val isVerified: Boolean? = null, + @SerializedName("coin") + val coin: Int? = null, ) \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/data/dto/auth/GetUserProfileResponseDto.kt b/app/src/main/java/com/pwhs/quickmem/data/dto/auth/GetUserProfileResponseDto.kt index f752ad4f..60e01290 100644 --- a/app/src/main/java/com/pwhs/quickmem/data/dto/auth/GetUserProfileResponseDto.kt +++ b/app/src/main/java/com/pwhs/quickmem/data/dto/auth/GetUserProfileResponseDto.kt @@ -15,6 +15,8 @@ data class GetUserProfileResponseDto( val avatarUrl: String, @SerializedName("role") val role: String, + @SerializedName("coin") + val coin: Int, @SerializedName("createdAt") val createdAt: String, @SerializedName("updatedAt") diff --git a/app/src/main/java/com/pwhs/quickmem/data/dto/user/UpdateCoinRequestDto.kt b/app/src/main/java/com/pwhs/quickmem/data/dto/user/UpdateCoinRequestDto.kt new file mode 100644 index 00000000..2f771a4e --- /dev/null +++ b/app/src/main/java/com/pwhs/quickmem/data/dto/user/UpdateCoinRequestDto.kt @@ -0,0 +1,12 @@ +package com.pwhs.quickmem.data.dto.user + +import com.google.gson.annotations.SerializedName + +data class UpdateCoinRequestDto( + @SerializedName("userId") + val userId: String, + @SerializedName("coin") + val coin: Int, + @SerializedName("action") + val action: String +) \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/data/dto/user/UpdateCoinResponseDto.kt b/app/src/main/java/com/pwhs/quickmem/data/dto/user/UpdateCoinResponseDto.kt new file mode 100644 index 00000000..257d3f58 --- /dev/null +++ b/app/src/main/java/com/pwhs/quickmem/data/dto/user/UpdateCoinResponseDto.kt @@ -0,0 +1,12 @@ +package com.pwhs.quickmem.data.dto.user + +import com.google.gson.annotations.SerializedName + +data class UpdateCoinResponseDto( + @SerializedName("message") + val message: String, + @SerializedName("coinAction") + val coinAction: String, + @SerializedName("coins") + val coins: Int +) \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/data/mapper/auth/GetUserProfileResponseMapper.kt b/app/src/main/java/com/pwhs/quickmem/data/mapper/auth/GetUserProfileResponseMapper.kt index 7d53daaf..c6eda0b1 100644 --- a/app/src/main/java/com/pwhs/quickmem/data/mapper/auth/GetUserProfileResponseMapper.kt +++ b/app/src/main/java/com/pwhs/quickmem/data/mapper/auth/GetUserProfileResponseMapper.kt @@ -11,7 +11,8 @@ fun GetUserProfileResponseDto.toModel() = GetUserProfileResponseModel( username = username, avatarUrl = avatarUrl, createdAt = createdAt, - updatedAt = updatedAt + updatedAt = updatedAt, + coin = coin ) fun GetUserProfileResponseModel.toDto() = GetUserProfileResponseDto( @@ -22,5 +23,6 @@ fun GetUserProfileResponseModel.toDto() = GetUserProfileResponseDto( username = username, avatarUrl = avatarUrl, createdAt = createdAt, - updatedAt = updatedAt + updatedAt = updatedAt, + coin = coin ) \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/data/mapper/user/UpdateCoinRequestMapper.kt b/app/src/main/java/com/pwhs/quickmem/data/mapper/user/UpdateCoinRequestMapper.kt new file mode 100644 index 00000000..44068368 --- /dev/null +++ b/app/src/main/java/com/pwhs/quickmem/data/mapper/user/UpdateCoinRequestMapper.kt @@ -0,0 +1,16 @@ +package com.pwhs.quickmem.data.mapper.user + +import com.pwhs.quickmem.data.dto.user.UpdateCoinRequestDto +import com.pwhs.quickmem.domain.model.users.UpdateCoinRequestModel + +fun UpdateCoinRequestDto.toModel() = UpdateCoinRequestModel( + userId = userId, + coin = coin, + action = action +) + +fun UpdateCoinRequestModel.toDto() = UpdateCoinRequestDto( + userId = userId, + coin = coin, + action = action +) \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/data/mapper/user/UpdateCoinResponseMapper.kt b/app/src/main/java/com/pwhs/quickmem/data/mapper/user/UpdateCoinResponseMapper.kt new file mode 100644 index 00000000..57877df9 --- /dev/null +++ b/app/src/main/java/com/pwhs/quickmem/data/mapper/user/UpdateCoinResponseMapper.kt @@ -0,0 +1,16 @@ +package com.pwhs.quickmem.data.mapper.user + +import com.pwhs.quickmem.data.dto.user.UpdateCoinResponseDto +import com.pwhs.quickmem.domain.model.users.UpdateCoinResponseModel + +fun UpdateCoinResponseDto.toModel() = UpdateCoinResponseModel( + message = message, + coinAction = coinAction, + coins = coins +) + +fun UpdateCoinResponseModel.toDto() = UpdateCoinResponseDto( + message = message, + coinAction = coinAction, + coins = coins +) \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/data/remote/ApiService.kt b/app/src/main/java/com/pwhs/quickmem/data/remote/ApiService.kt index cd27dad4..1c214eda 100644 --- a/app/src/main/java/com/pwhs/quickmem/data/remote/ApiService.kt +++ b/app/src/main/java/com/pwhs/quickmem/data/remote/ApiService.kt @@ -81,6 +81,8 @@ import com.pwhs.quickmem.data.dto.subject.GetTop5SubjectResponseDto import com.pwhs.quickmem.data.dto.upload.DeleteImageDto import com.pwhs.quickmem.data.dto.upload.UploadImageResponseDto import com.pwhs.quickmem.data.dto.user.SearchUserResponseDto +import com.pwhs.quickmem.data.dto.user.UpdateCoinRequestDto +import com.pwhs.quickmem.data.dto.user.UpdateCoinResponseDto import com.pwhs.quickmem.data.dto.user.UserDetailResponseDto import okhttp3.MultipartBody import retrofit2.Response @@ -183,6 +185,12 @@ interface ApiService { @Query("page") page: Int? ): List + @POST("auth/coin") + suspend fun updateCoin( + @Header("Authorization") token: String, + @Body request: UpdateCoinRequestDto + ): UpdateCoinResponseDto + // Upload @Multipart @POST("upload") diff --git a/app/src/main/java/com/pwhs/quickmem/data/remote/repository/AuthRepositoryImpl.kt b/app/src/main/java/com/pwhs/quickmem/data/remote/repository/AuthRepositoryImpl.kt index 1733757e..5dc99d3f 100644 --- a/app/src/main/java/com/pwhs/quickmem/data/remote/repository/AuthRepositoryImpl.kt +++ b/app/src/main/java/com/pwhs/quickmem/data/remote/repository/AuthRepositoryImpl.kt @@ -8,6 +8,7 @@ import com.pwhs.quickmem.core.utils.Resources import com.pwhs.quickmem.data.dto.verify_email.EmailRequestDto import com.pwhs.quickmem.data.mapper.auth.toDto import com.pwhs.quickmem.data.mapper.auth.toModel +import com.pwhs.quickmem.data.mapper.user.toDto import com.pwhs.quickmem.data.mapper.user.toModel import com.pwhs.quickmem.data.paging.UserPagingSource import com.pwhs.quickmem.data.remote.ApiService @@ -40,6 +41,8 @@ import com.pwhs.quickmem.domain.model.auth.VerifyEmailResponseModel import com.pwhs.quickmem.domain.model.auth.VerifyPasswordRequestModel import com.pwhs.quickmem.domain.model.auth.VerifyPasswordResponseModel import com.pwhs.quickmem.domain.model.users.SearchUserResponseModel +import com.pwhs.quickmem.domain.model.users.UpdateCoinRequestModel +import com.pwhs.quickmem.domain.model.users.UpdateCoinResponseModel import com.pwhs.quickmem.domain.model.users.UserDetailResponseModel import com.pwhs.quickmem.domain.repository.AuthRepository import kotlinx.coroutines.flow.Flow @@ -363,4 +366,23 @@ class AuthRepositoryImpl @Inject constructor( } } } + + override suspend fun updateCoin( + token: String, + updateCoinRequestModel: UpdateCoinRequestModel + ): Flow> { + return flow { + emit(Resources.Loading()) + try { + val response = apiService.updateCoin( + token, + updateCoinRequestModel.toDto() + ) + emit(Resources.Success(response.toModel())) + } catch (e: Exception) { + Timber.e(e) + emit(Resources.Error(e.toString())) + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/domain/model/auth/AuthResponseModel.kt b/app/src/main/java/com/pwhs/quickmem/domain/model/auth/AuthResponseModel.kt index 51cfe1c2..6877baf3 100644 --- a/app/src/main/java/com/pwhs/quickmem/domain/model/auth/AuthResponseModel.kt +++ b/app/src/main/java/com/pwhs/quickmem/domain/model/auth/AuthResponseModel.kt @@ -12,4 +12,5 @@ data class AuthResponseModel( val refreshToken: String? = null, val provider: String? = null, val isVerified: Boolean? = null, + val coin: Int? = null, ) \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/domain/model/auth/GetUserProfileResponseModel.kt b/app/src/main/java/com/pwhs/quickmem/domain/model/auth/GetUserProfileResponseModel.kt index 1322c85b..98201dcd 100644 --- a/app/src/main/java/com/pwhs/quickmem/domain/model/auth/GetUserProfileResponseModel.kt +++ b/app/src/main/java/com/pwhs/quickmem/domain/model/auth/GetUserProfileResponseModel.kt @@ -7,6 +7,7 @@ data class GetUserProfileResponseModel( val email: String, val avatarUrl: String, val role: String, + val coin: Int, val createdAt: String, val updatedAt: String ) \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/domain/model/users/UpdateCoinRequestModel.kt b/app/src/main/java/com/pwhs/quickmem/domain/model/users/UpdateCoinRequestModel.kt new file mode 100644 index 00000000..002eb3de --- /dev/null +++ b/app/src/main/java/com/pwhs/quickmem/domain/model/users/UpdateCoinRequestModel.kt @@ -0,0 +1,7 @@ +package com.pwhs.quickmem.domain.model.users + +data class UpdateCoinRequestModel( + val userId: String, + val coin: Int, + val action: String +) \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/domain/model/users/UpdateCoinResponseModel.kt b/app/src/main/java/com/pwhs/quickmem/domain/model/users/UpdateCoinResponseModel.kt new file mode 100644 index 00000000..ee6f5741 --- /dev/null +++ b/app/src/main/java/com/pwhs/quickmem/domain/model/users/UpdateCoinResponseModel.kt @@ -0,0 +1,7 @@ +package com.pwhs.quickmem.domain.model.users + +data class UpdateCoinResponseModel( + val message: String, + val coinAction: String, + val coins: Int +) \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/domain/repository/AuthRepository.kt b/app/src/main/java/com/pwhs/quickmem/domain/repository/AuthRepository.kt index adf861ce..92b48edc 100644 --- a/app/src/main/java/com/pwhs/quickmem/domain/repository/AuthRepository.kt +++ b/app/src/main/java/com/pwhs/quickmem/domain/repository/AuthRepository.kt @@ -29,6 +29,8 @@ import com.pwhs.quickmem.domain.model.auth.VerifyEmailResponseModel import com.pwhs.quickmem.domain.model.auth.VerifyPasswordRequestModel import com.pwhs.quickmem.domain.model.auth.VerifyPasswordResponseModel import com.pwhs.quickmem.domain.model.users.SearchUserResponseModel +import com.pwhs.quickmem.domain.model.users.UpdateCoinRequestModel +import com.pwhs.quickmem.domain.model.users.UpdateCoinResponseModel import com.pwhs.quickmem.domain.model.users.UserDetailResponseModel import kotlinx.coroutines.flow.Flow @@ -110,4 +112,8 @@ interface AuthRepository { changeRoleRequestModel: ChangeRoleRequestModel ): Flow> + suspend fun updateCoin( + token: String, + updateCoinRequestModel: UpdateCoinRequestModel + ): Flow> } \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreScreen.kt index d5182b28..04476fcd 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreScreen.kt @@ -1,11 +1,21 @@ package com.pwhs.quickmem.presentation.app.explore import android.widget.Toast +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Refresh import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.typography @@ -23,25 +33,31 @@ import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import com.pwhs.quickmem.R import com.pwhs.quickmem.core.data.enums.DifficultyLevel import com.pwhs.quickmem.core.data.enums.QuestionType import com.pwhs.quickmem.domain.model.streak.GetTopStreakResponseModel import com.pwhs.quickmem.presentation.app.explore.create_study_set_ai.CreateStudySetAITab import com.pwhs.quickmem.presentation.app.explore.top_streak.TopStreakScreen import com.pwhs.quickmem.presentation.component.LoadingOverlay +import com.pwhs.quickmem.util.ads.AdsUtil import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.generated.destinations.StudySetDetailScreenDestination import com.ramcosta.composedestinations.generated.destinations.UserDetailScreenDestination import com.ramcosta.composedestinations.generated.destinations.UserDetailScreenDestination.invoke import com.ramcosta.composedestinations.navigation.DestinationsNavigator +import com.revenuecat.purchases.CustomerInfo @Composable @Destination @@ -105,7 +121,10 @@ fun ExploreScreen( onCreateStudySet = { viewModel.onEvent(ExploreUiAction.OnCreateStudySet) }, - errorMessage = uiState.errorMessage + errorMessage = uiState.errorMessage, + coins = uiState.coins, + onEarnCoins = { viewModel.onEvent(ExploreUiAction.OnEarnCoins) }, + customerInfo = uiState.customerInfo ) } @@ -132,13 +151,17 @@ fun Explore( onQuestionTypeChange: (QuestionType) -> Unit = {}, onDifficultyLevelChange: (DifficultyLevel) -> Unit = {}, onCreateStudySet: () -> Unit = {}, - errorMessage: String = "" + onEarnCoins: () -> Unit = {}, + errorMessage: String = "", + coins: Int = 0, + customerInfo: CustomerInfo? = null ) { var tabIndex by rememberSaveable { mutableIntStateOf(0) } val tabTitles = listOf( "Create Study Set AI", "Top Streak", ) + val context = LocalContext.current Scaffold( modifier = modifier, @@ -153,7 +176,54 @@ fun Explore( ) }, actions = { - + if (tabIndex == ExploreTabEnum.TOP_STREAK.index) { + IconButton( + onClick = onTopStreakRefresh + ) { + Icon( + imageVector = Icons.Default.Refresh, + contentDescription = "Refresh", + ) + } + } else { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier.padding(end = 16.dp) + ) { + Text( + text = when (customerInfo?.activeSubscriptions?.isNotEmpty()) { + true -> "Unlimited" + false -> coins.toString() + else -> "0" + }, + style = typography.titleMedium.copy( + fontWeight = FontWeight.Bold + ) + ) + Image( + painter = painterResource(id = R.drawable.ic_coin), + contentDescription = "Coins", + modifier = Modifier.size(24.dp), + contentScale = ContentScale.Crop + ) + if (customerInfo?.activeSubscriptions?.isNotEmpty() == false) { + Icon( + imageVector = Icons.Default.Add, + contentDescription = "Add", + tint = colorScheme.primary, + modifier = Modifier + .size(24.dp) + .clickable { + AdsUtil.rewardedInterstitialAd( + context, + onEarnCoins + ) + }, + ) + } + } + } } ) } @@ -202,7 +272,17 @@ fun Explore( onLanguageChange = onLanguageChange, onQuestionTypeChange = onQuestionTypeChange, onDifficultyLevelChange = onDifficultyLevelChange, - onCreateStudySet = onCreateStudySet + onCreateStudySet = { + if (coins > 0) { + onCreateStudySet() + } else { + Toast.makeText( + context, + "You need at least 1 coin to create a study set", + Toast.LENGTH_SHORT + ).show() + } + } ) ExploreTabEnum.TOP_STREAK.index -> TopStreakScreen( diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreUiAction.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreUiAction.kt index 70956200..3d57221f 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreUiAction.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreUiAction.kt @@ -12,4 +12,5 @@ sealed class ExploreUiAction { data class OnQuestionTypeChanged(val questionType: QuestionType) : ExploreUiAction() data class OnDifficultyLevelChanged(val difficultyLevel: DifficultyLevel) : ExploreUiAction() data object OnCreateStudySet : ExploreUiAction() + data object OnEarnCoins : ExploreUiAction() } \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreUiState.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreUiState.kt index 3818a6c9..403e342f 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreUiState.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreUiState.kt @@ -4,12 +4,14 @@ import com.pwhs.quickmem.core.data.enums.DifficultyLevel import com.pwhs.quickmem.core.data.enums.LanguageCode import com.pwhs.quickmem.core.data.enums.QuestionType import com.pwhs.quickmem.domain.model.streak.GetTopStreakResponseModel +import com.revenuecat.purchases.CustomerInfo data class ExploreUiState( val isLoading: Boolean = false, val ownerId: String = "", val topStreaks: List = emptyList(), val streakOwner: GetTopStreakResponseModel? = null, + val customerInfo: CustomerInfo? = null, val rankOwner: Int? = null, // AI val description: String = "", @@ -18,5 +20,6 @@ data class ExploreUiState( val numberOfFlashcards: Int = 15, val questionType: QuestionType = QuestionType.MULTIPLE_CHOICE, val title: String = "", - val errorMessage: String = "" + val errorMessage: String = "", + val coins: Int = 0 ) \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreViewModel.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreViewModel.kt index df4d55f5..46a5d9cd 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreViewModel.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreViewModel.kt @@ -3,24 +3,33 @@ package com.pwhs.quickmem.presentation.app.explore import android.app.Application import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope +import com.pwhs.quickmem.core.data.enums.CoinAction import com.pwhs.quickmem.core.data.enums.DifficultyLevel import com.pwhs.quickmem.core.data.enums.QuestionType import com.pwhs.quickmem.core.datastore.AppManager import com.pwhs.quickmem.core.datastore.TokenManager import com.pwhs.quickmem.core.utils.Resources import com.pwhs.quickmem.domain.model.study_set.CreateStudySetByAIRequestModel +import com.pwhs.quickmem.domain.model.users.UpdateCoinRequestModel +import com.pwhs.quickmem.domain.repository.AuthRepository import com.pwhs.quickmem.domain.repository.StreakRepository import com.pwhs.quickmem.domain.repository.StudySetRepository import com.pwhs.quickmem.util.getLanguageCode +import com.revenuecat.purchases.CustomerInfo +import com.revenuecat.purchases.Purchases +import com.revenuecat.purchases.PurchasesError +import com.revenuecat.purchases.interfaces.ReceiveCustomerInfoCallback import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject @HiltViewModel @@ -29,6 +38,7 @@ class ExploreViewModel @Inject constructor( private val appManager: AppManager, private val streakRepository: StreakRepository, private val studySetRepository: StudySetRepository, + private val authRepository: AuthRepository, application: Application ) : AndroidViewModel(application) { private val _uiState = MutableStateFlow(ExploreUiState()) @@ -40,15 +50,19 @@ class ExploreViewModel @Inject constructor( init { val languageCode = getApplication().getLanguageCode() viewModelScope.launch { - val userId = appManager.userId.firstOrNull() ?: "" - _uiState.update { - it.copy( - ownerId = userId, - language = languageCode - ) + combine(appManager.userId, appManager.userCoins) { userId, coins -> + _uiState.update { + it.copy( + ownerId = userId, + coins = coins, + language = languageCode + ) + } + }.collectLatest { + getTopStreaks() + getCustomerInfo() } } - getTopStreaks() } fun onEvent(event: ExploreUiAction) { @@ -94,6 +108,10 @@ class ExploreViewModel @Inject constructor( createStudySet() } } + + is ExploreUiAction.OnEarnCoins -> { + updateCoins(coinAction = CoinAction.ADD, coin = 1) + } } } @@ -170,7 +188,14 @@ class ExploreViewModel @Inject constructor( language = getApplication().getLanguageCode() ) } - _uiEvent.send(ExploreUiEvent.CreatedStudySet(resource.data?.id ?: "")) + if (_uiState.value.customerInfo?.activeSubscriptions?.isNotEmpty() == false) { + updateCoins(coinAction = CoinAction.SUBTRACT, coin = 1) + } + _uiEvent.send( + ExploreUiEvent.CreatedStudySet( + studySetId = resource.data?.id ?: "" + ) + ) } is Resources.Error -> { @@ -186,4 +211,54 @@ class ExploreViewModel @Inject constructor( } } } + + private fun updateCoins( + coinAction: CoinAction, + coin: Int = 1 + ) { + viewModelScope.launch { + val token = tokenManager.accessToken.firstOrNull() ?: "" + val userId = appManager.userId.firstOrNull() ?: "" + authRepository.updateCoin( + token, UpdateCoinRequestModel( + userId = userId, + action = coinAction.action, + coin = coin + ) + ).collect { coin -> + when (coin) { + is Resources.Error -> { + Timber.e("Too many requests, please wait 1 minute") + } + + is Resources.Loading -> { + // do nothing + } + + is Resources.Success -> { + appManager.saveUserCoins(coin.data?.coins ?: 0) + _uiState.update { + it.copy(coins = coin.data?.coins ?: 0) + } + } + } + } + } + } + + private fun getCustomerInfo() { + Purchases.sharedInstance.getCustomerInfo(object : ReceiveCustomerInfoCallback { + override fun onReceived(customerInfo: CustomerInfo) { + _uiState.update { + it.copy( + customerInfo = customerInfo + ) + } + } + + override fun onError(error: PurchasesError) { + Timber.e(error.message) + } + }) + } } \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/create_study_set_ai/CreateStudySetAITab.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/create_study_set_ai/CreateStudySetAITab.kt index 67227987..8d763109 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/create_study_set_ai/CreateStudySetAITab.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/create_study_set_ai/CreateStudySetAITab.kt @@ -31,7 +31,6 @@ import androidx.compose.material3.TextFieldDefaults.colors import androidx.compose.material3.VerticalDivider import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -51,12 +50,6 @@ import com.pwhs.quickmem.core.data.enums.DifficultyLevel import com.pwhs.quickmem.core.data.enums.LanguageCode import com.pwhs.quickmem.core.data.enums.QuestionType import com.pwhs.quickmem.ui.theme.QuickMemTheme -import com.pwhs.quickmem.util.ads.AdsUtil -import com.revenuecat.purchases.CustomerInfo -import com.revenuecat.purchases.Purchases -import com.revenuecat.purchases.PurchasesError -import com.revenuecat.purchases.interfaces.ReceiveCustomerInfoCallback -import timber.log.Timber @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -82,34 +75,11 @@ fun CreateStudySetAITab( mutableStateOf(false) } val context = LocalContext.current - var customer: CustomerInfo? by remember { mutableStateOf(null) } - LaunchedEffect(key1 = true) { - Purchases.sharedInstance.getCustomerInfo(object : ReceiveCustomerInfoCallback { - override fun onError(error: PurchasesError) { - Timber.e("Error getting customer info: $error") - } - - override fun onReceived(customerInfo: CustomerInfo) { - Timber.d("Customer info: $customerInfo") - customer = customerInfo - } - - }) - } Scaffold( floatingActionButton = { if (title.isNotEmpty()) { FloatingActionButton( - onClick = { - val isSubscribed = customer?.activeSubscriptions?.isNotEmpty() == true - if (isSubscribed) { - onCreateStudySet() - } else { - AdsUtil.rewardedAd(context) { - onCreateStudySet() - } - } - }, + onClick = onCreateStudySet, ) { Column( horizontalAlignment = Alignment.CenterHorizontally, diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/profile/ProfileViewModel.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/profile/ProfileViewModel.kt index 14ac1fdb..0220ee6f 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/profile/ProfileViewModel.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/profile/ProfileViewModel.kt @@ -119,6 +119,10 @@ class ProfileViewModel @Inject constructor( appManager.saveUserName(data.username) appManager.saveUserAvatar(data.avatarUrl) appManager.saveUserRole(data.role) + appManager.saveUserFullName(data.fullname) + appManager.saveUserEmail(data.email) + appManager.saveUserId(data.id) + appManager.saveUserCoins(data.coin) } } diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/auth/login/email/LoginWithEmailViewModel.kt b/app/src/main/java/com/pwhs/quickmem/presentation/auth/login/email/LoginWithEmailViewModel.kt index 53e086f8..290854bd 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/auth/login/email/LoginWithEmailViewModel.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/auth/login/email/LoginWithEmailViewModel.kt @@ -140,6 +140,7 @@ class LoginWithEmailViewModel @Inject constructor( appManager.saveUserBirthday(login.data?.birthday ?: "") appManager.saveUserName(login.data?.username ?: "") appManager.saveUserRole(login.data?.role ?: "") + appManager.saveUserCoins(login.data?.coin ?: 0) Purchases.sharedInstance.apply { setEmail(login.data?.email) setDisplayName(login.data?.fullName) diff --git a/app/src/main/java/com/pwhs/quickmem/util/ads/AdsUtil.kt b/app/src/main/java/com/pwhs/quickmem/util/ads/AdsUtil.kt index 5df99f9c..68b5ea87 100644 --- a/app/src/main/java/com/pwhs/quickmem/util/ads/AdsUtil.kt +++ b/app/src/main/java/com/pwhs/quickmem/util/ads/AdsUtil.kt @@ -16,6 +16,7 @@ import com.pwhs.quickmem.MainActivity import com.pwhs.quickmem.core.utils.AppConstant.INTERSTITIAL_ADS_ID import com.pwhs.quickmem.core.utils.AppConstant.REWARDED_INTERSTITIAL_ADS_ID import com.pwhs.quickmem.core.utils.AppConstant.REWARD_ADS_ID +import timber.log.Timber object AdsUtil { fun interstitialAds( @@ -89,7 +90,7 @@ object AdsUtil { ) } - fun rewardedInterstitialTestAd(context: Context, onAdWatched: () -> Unit) { + fun rewardedInterstitialAd(context: Context, onAdWatched: () -> Unit) { // Load an ad RewardedInterstitialAd.load( context, @@ -99,47 +100,17 @@ object AdsUtil { override fun onAdFailedToLoad(error: LoadAdError) { super.onAdFailedToLoad(error) Toast.makeText(context, error.message, Toast.LENGTH_SHORT).show() - - } override fun onAdLoaded(adLoaded: RewardedInterstitialAd) { super.onAdLoaded(adLoaded) - Toast.makeText(context, "Ad Loaded", Toast.LENGTH_SHORT).show() - if (context is MainActivity) { adLoaded.show(context) { // The user earned the reward + Timber.d("User earned the reward") onAdWatched() } } - - adLoaded.fullScreenContentCallback = object : FullScreenContentCallback() { - override fun onAdDismissedFullScreenContent() { - super.onAdDismissedFullScreenContent() - Toast.makeText(context, "Ad Dismissed", Toast.LENGTH_SHORT).show() - } - - override fun onAdShowedFullScreenContent() { - super.onAdShowedFullScreenContent() - Toast.makeText(context, "Ad Showed", Toast.LENGTH_SHORT).show() - } - - override fun onAdClicked() { - super.onAdClicked() - Toast.makeText(context, "Ad Clicked", Toast.LENGTH_SHORT).show() - } - - override fun onAdImpression() { - super.onAdImpression() - Toast.makeText(context, "Ad Impression", Toast.LENGTH_SHORT).show() - } - - override fun onAdFailedToShowFullScreenContent(p0: AdError) { - super.onAdFailedToShowFullScreenContent(p0) - Toast.makeText(context, p0.message, Toast.LENGTH_SHORT).show() - } - } } } diff --git a/app/src/main/res/drawable/ic_coin.xml b/app/src/main/res/drawable/ic_coin.xml new file mode 100644 index 00000000..5d51693f --- /dev/null +++ b/app/src/main/res/drawable/ic_coin.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + From c6f7b0d5eb29a511616e9f1b64aa6b6d6e7b1f04 Mon Sep 17 00:00:00 2001 From: Nguyen Quang Minh Date: Sat, 30 Nov 2024 13:56:39 +0700 Subject: [PATCH 7/7] =?UTF-8?q?feat(ux):=20c=E1=BA=A3i=20thi=E1=BB=87n=20h?= =?UTF-8?q?i=E1=BB=87u=20su=E1=BA=A5t=20c=E1=BB=A7a=20list=20-=20s?= =?UTF-8?q?=E1=BB=ADa=20m=E1=BB=99t=20v=C3=A0i=20l=E1=BB=97i=20giao=20di?= =?UTF-8?q?=E1=BB=87n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../classes/add_folder/component/AddFolderToClassList.kt | 6 +----- .../add_study_set/component/AddStudySetToClassList.kt | 5 +---- .../app/classes/detail/folders/FoldersTabScreen.kt | 2 +- .../app/classes/detail/members/MembersTabScreen.kt | 2 +- .../app/classes/detail/study_sets/StudySetsTabScreen.kt | 2 +- .../quickmem/presentation/app/explore/ExploreViewModel.kt | 7 +++++++ .../app/explore/top_streak/TopStreakScreen.kt | 2 +- .../add_study_set/component/AddStudySetToFolderList.kt | 5 +---- .../folder/detail/component/FolderDetailStudySetList.kt | 2 +- .../com/pwhs/quickmem/presentation/app/home/HomeScreen.kt | 8 ++++---- .../app/home/components/NotificationListBottomSheet.kt | 2 +- .../presentation/app/library/classes/ListClassesScreen.kt | 2 +- .../presentation/app/library/folder/ListFolderScreen.kt | 2 +- .../app/library/study_set/ListStudySetScreen.kt | 4 ++-- .../add_to_class/component/AddStudySetToClassesList.kt | 2 +- .../add_to_folder/component/AddStudySetToFoldersList.kt | 4 +--- .../app/study_set/detail/material/MaterialTabScreen.kt | 4 ++-- 17 files changed, 28 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/add_folder/component/AddFolderToClassList.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/add_folder/component/AddFolderToClassList.kt index 375bd6c2..f3bcd887 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/add_folder/component/AddFolderToClassList.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/add_folder/component/AddFolderToClassList.kt @@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.typography @@ -35,9 +34,7 @@ import com.pwhs.quickmem.domain.model.folder.GetFolderResponseModel import com.pwhs.quickmem.presentation.ads.BannerAds import com.pwhs.quickmem.presentation.app.library.component.SearchTextField import com.pwhs.quickmem.ui.theme.QuickMemTheme -import timber.log.Timber -@OptIn(ExperimentalMaterial3Api::class) @Composable fun AddFolderToClassList( modifier: Modifier = Modifier, @@ -109,8 +106,7 @@ fun AddFolderToClassList( ) } } - items(filterFolders) { folder -> - Timber.d("List folder ID: $folderImportedIds") + items(items = filterFolders, key = {it.id}) { folder -> AddFolderToClassItem( folder = folder, onAddFolderToClass = { diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/add_study_set/component/AddStudySetToClassList.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/add_study_set/component/AddStudySetToClassList.kt index 12eba821..3508139a 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/add_study_set/component/AddStudySetToClassList.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/add_study_set/component/AddStudySetToClassList.kt @@ -36,7 +36,6 @@ import com.pwhs.quickmem.domain.model.subject.SubjectModel import com.pwhs.quickmem.domain.model.users.UserResponseModel import com.pwhs.quickmem.presentation.app.library.component.SearchTextField import com.pwhs.quickmem.ui.theme.QuickMemTheme -import timber.log.Timber @Composable fun AddStudySetToClassList( @@ -127,12 +126,10 @@ fun AddStudySetToClassList( } } } - items(filterStudySets) { studySet -> - Timber.d("Check isAdd: ${studySetImportedIds.contains(studySet.id)}") + items(items = filterStudySets, key = {it.id}) { studySet -> AddStudySetToClassItem( studySet = studySet, onAddStudySetToClass = { - Timber.d("Study set added: $it") onAddStudySetToClass(it) }, isAdded = studySetImportedIds.contains(studySet.id) diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/folders/FoldersTabScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/folders/FoldersTabScreen.kt index c0f91000..c9b92019 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/folders/FoldersTabScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/folders/FoldersTabScreen.kt @@ -48,7 +48,7 @@ fun FoldersTabScreen( modifier = Modifier .fillMaxSize(), ) { - items(folder) { folders -> + items(items = folder, key = {it.id}) { folders -> FolderItem( modifier = Modifier.padding(horizontal = 16.dp), title = folders.title, diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/members/MembersTabScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/members/MembersTabScreen.kt index b5f7644d..34dea048 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/members/MembersTabScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/members/MembersTabScreen.kt @@ -46,7 +46,7 @@ fun MembersTabScreen( modifier = Modifier .fillMaxSize(), ) { - items(member) { member -> + items(items = member, key = {it.id}) { member -> ClassMemberItem( modifier = Modifier.padding(horizontal = 16.dp), classMemberModel = member, diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/study_sets/StudySetsTabScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/study_sets/StudySetsTabScreen.kt index d1bef4ca..838f7737 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/study_sets/StudySetsTabScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/study_sets/StudySetsTabScreen.kt @@ -47,7 +47,7 @@ fun StudySetsTabScreen( modifier = Modifier .fillMaxSize(), ) { - items(studySets) { studySet -> + items(items = studySets, key = {it.id}) { studySet -> StudySetItem( modifier = Modifier.padding(horizontal = 16.dp), studySet = studySet, diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreViewModel.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreViewModel.kt index 46a5d9cd..52b305ee 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreViewModel.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreViewModel.kt @@ -1,6 +1,7 @@ package com.pwhs.quickmem.presentation.app.explore import android.app.Application +import android.widget.Toast import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import com.pwhs.quickmem.core.data.enums.CoinAction @@ -229,6 +230,7 @@ class ExploreViewModel @Inject constructor( when (coin) { is Resources.Error -> { Timber.e("Too many requests, please wait 1 minute") + _uiEvent.send(ExploreUiEvent.Error("Too many requests, please wait 1 minute")) } is Resources.Loading -> { @@ -240,6 +242,11 @@ class ExploreViewModel @Inject constructor( _uiState.update { it.copy(coins = coin.data?.coins ?: 0) } + Toast.makeText( + getApplication(), + "You have earned 1 coin", + Toast.LENGTH_SHORT + ).show() } } } diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/top_streak/TopStreakScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/top_streak/TopStreakScreen.kt index b7f5f69d..166e7fae 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/top_streak/TopStreakScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/top_streak/TopStreakScreen.kt @@ -126,7 +126,7 @@ fun TopStreakScreen( modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally ) { - items(topStreaks.size) { index -> + items(topStreaks.size, key = { it }) { index -> val topStreak = topStreaks[index] StreakItem( rank = index + 1, diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/folder/add_study_set/component/AddStudySetToFolderList.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/folder/add_study_set/component/AddStudySetToFolderList.kt index a7e5ab56..ada9efc2 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/folder/add_study_set/component/AddStudySetToFolderList.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/folder/add_study_set/component/AddStudySetToFolderList.kt @@ -36,7 +36,6 @@ import com.pwhs.quickmem.domain.model.subject.SubjectModel import com.pwhs.quickmem.domain.model.users.UserResponseModel import com.pwhs.quickmem.presentation.app.library.component.SearchTextField import com.pwhs.quickmem.ui.theme.QuickMemTheme -import timber.log.Timber @Composable fun AddStudySetToFolderList( @@ -127,12 +126,10 @@ fun AddStudySetToFolderList( } } } - items(filterStudySets) { studySet -> - Timber.d("Check isAdd: ${studySetImportedIds.contains(studySet.id)}") + items(items = filterStudySets, key = {it.id}) { studySet -> AddStudySetToFolderItem( studySet = studySet, onAddStudySetToFolder = { - Timber.d("Study set added: $it") onAddStudySetToFolder(it) }, isAdded = studySetImportedIds.contains(studySet.id) diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/folder/detail/component/FolderDetailStudySetList.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/folder/detail/component/FolderDetailStudySetList.kt index 23052cec..2e23ff92 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/folder/detail/component/FolderDetailStudySetList.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/folder/detail/component/FolderDetailStudySetList.kt @@ -90,7 +90,7 @@ fun FolderDetailStudySetList( LazyColumn( horizontalAlignment = CenterHorizontally, ) { - items(studySets) { studySet -> + items(items = studySets, key = { it.id }) { studySet -> StudySetItem( studySet = studySet, onStudySetClick = { onStudySetClick(studySet.id) } diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/home/HomeScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/home/HomeScreen.kt index 2b0a1f56..d2d74d67 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/home/HomeScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/home/HomeScreen.kt @@ -475,7 +475,7 @@ private fun Home( modifier = Modifier .fillMaxWidth() ) { - items(studySets) { studySet -> + items(items = studySets, key = { it.id }) { studySet -> StudySetHomeItem( studySet = studySet, onStudySetClick = { onStudySetClick(studySet) } @@ -503,7 +503,7 @@ private fun Home( modifier = Modifier .fillMaxWidth() ) { - items(folders) { folder -> + items(items = folders, key = { it.id }) { folder -> FolderHomeItem( title = folder.title, numOfStudySets = folder.studySetCount, @@ -533,7 +533,7 @@ private fun Home( modifier = Modifier .fillMaxWidth() ) { - items(classes) { classItem -> + items(items = classes, key = {it.id}) { classItem -> ClassHomeItem( classItem = classItem, onClick = { onClassClicked(classItem) } @@ -554,7 +554,7 @@ private fun Home( modifier = Modifier.padding(top = 24.dp) ) } - items(subjects, key = { it.id }) { subject -> + items(items = subjects, key = { it.id }) { subject -> SubjectItem( subject = subject, onSearchStudySetBySubject = { diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/home/components/NotificationListBottomSheet.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/home/components/NotificationListBottomSheet.kt index b7cb5725..0cde75ce 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/home/components/NotificationListBottomSheet.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/home/components/NotificationListBottomSheet.kt @@ -88,7 +88,7 @@ fun NotificationListBottomSheet( else -> { LazyColumn { - items(notifications, key = { it.id }) { notification -> + items(items = notifications, key = { it.id }) { notification -> NotificationItem( notification = notification, onMarkAsRead = { diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/library/classes/ListClassesScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/library/classes/ListClassesScreen.kt index 9a38804a..fa2cd22a 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/library/classes/ListClassesScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/library/classes/ListClassesScreen.kt @@ -142,7 +142,7 @@ fun ListClassesScreen( modifier = Modifier.padding(8.dp) ) } - items(filterClass) { classItem -> + items(items = filterClass, key = {it.id}) { classItem -> ClassItem( modifier = Modifier.padding(horizontal = 16.dp), classItem = classItem, diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/library/folder/ListFolderScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/library/folder/ListFolderScreen.kt index d2f46332..db9dcc2c 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/library/folder/ListFolderScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/library/folder/ListFolderScreen.kt @@ -142,7 +142,7 @@ fun ListFolderScreen( modifier = Modifier.padding(8.dp) ) } - items(filterFolders) { folder -> + items(items = filterFolders, key = {it.id}) { folder -> FolderItem( modifier = Modifier.padding(horizontal = 16.dp), title = folder.title, diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/library/study_set/ListStudySetScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/library/study_set/ListStudySetScreen.kt index 6ecce094..a70a9276 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/library/study_set/ListStudySetScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/library/study_set/ListStudySetScreen.kt @@ -50,7 +50,7 @@ fun ListStudySetScreen( isLoading: Boolean = false, studySets: List = emptyList(), onStudySetClick: (GetStudySetResponseModel) -> Unit = {}, - onStudySetRefresh: () -> Unit = {}, + onStudySetRefresh: () -> Unit = {}, avatarUrl: String = "", username: String = "", isOwner: Boolean = false @@ -146,7 +146,7 @@ fun ListStudySetScreen( modifier = Modifier.padding(8.dp) ) } - items(filterStudySets) { studySet -> + items(items = filterStudySets, key = { it.id }) { studySet -> StudySetItem( modifier = Modifier.padding(horizontal = 16.dp), studySet = studySet, diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/study_set/add_to_class/component/AddStudySetToClassesList.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/study_set/add_to_class/component/AddStudySetToClassesList.kt index b2b24c59..e09b14a1 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/study_set/add_to_class/component/AddStudySetToClassesList.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/study_set/add_to_class/component/AddStudySetToClassesList.kt @@ -136,7 +136,7 @@ fun AddStudySetToClassesList( ) } } - items(filterClass) { classItem -> + items(items = filterClass, key = { it.id }) { classItem -> AddStudySetToClassesItem( classItem = classItem, onAddStudySetToClasses = { diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/study_set/add_to_folder/component/AddStudySetToFoldersList.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/study_set/add_to_folder/component/AddStudySetToFoldersList.kt index d632c83b..e32d89ca 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/study_set/add_to_folder/component/AddStudySetToFoldersList.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/study_set/add_to_folder/component/AddStudySetToFoldersList.kt @@ -34,7 +34,6 @@ import com.pwhs.quickmem.domain.model.folder.GetFolderResponseModel import com.pwhs.quickmem.presentation.ads.BannerAds import com.pwhs.quickmem.presentation.app.library.component.SearchTextField import com.pwhs.quickmem.ui.theme.QuickMemTheme -import timber.log.Timber @Composable fun AddStudySetToFoldersList( @@ -107,8 +106,7 @@ fun AddStudySetToFoldersList( ) } } - items(filterFolders) { folder -> - Timber.d("List folder ID: $folderImportedIds") + items(items = filterFolders, key = { it.id }) { folder -> AddStudySetToFoldersItem( folder = folder, onAddStudySetToFolders = { diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/study_set/detail/material/MaterialTabScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/study_set/detail/material/MaterialTabScreen.kt index 13f15b42..00109407 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/study_set/detail/material/MaterialTabScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/study_set/detail/material/MaterialTabScreen.kt @@ -187,7 +187,7 @@ fun MaterialTabScreen( LazyRow( modifier = Modifier.fillMaxWidth(), ) { - items(flashCards) { flashCard -> + items(items = flashCards, key = {it.id}) { flashCard -> StudySetFlipCard( frontText = flashCard.term, backText = flashCard.definition, @@ -279,7 +279,7 @@ fun MaterialTabScreen( } } - items(flashCards) { flashCards -> + items(items = flashCards, key = {it.id}) { flashCards -> CardDetail( isOwner = isOwner, color = studySetColor,