From b603608ba0b2e370e9c7d5e85494d162ca6cc2f0 Mon Sep 17 00:00:00 2001 From: Sneh Date: Mon, 9 Dec 2024 11:59:29 +0530 Subject: [PATCH 1/5] Add functionality to check group members and show relevant content on PlacesList screen. --- .../ui/component/NoMemberEmptyContent.kt | 63 +++++++++++++++++++ .../flow/geofence/places/PlacesListScreen.kt | 26 +++++++- .../geofence/places/PlacesListViewModel.kt | 46 +++++++++++++- .../ui/flow/messages/thread/ThreadsScreen.kt | 44 +------------ 4 files changed, 133 insertions(+), 46 deletions(-) create mode 100644 app/src/main/java/com/canopas/yourspace/ui/component/NoMemberEmptyContent.kt diff --git a/app/src/main/java/com/canopas/yourspace/ui/component/NoMemberEmptyContent.kt b/app/src/main/java/com/canopas/yourspace/ui/component/NoMemberEmptyContent.kt new file mode 100644 index 00000000..3a101dcf --- /dev/null +++ b/app/src/main/java/com/canopas/yourspace/ui/component/NoMemberEmptyContent.kt @@ -0,0 +1,63 @@ +package com.canopas.yourspace.ui.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.canopas.yourspace.R +import com.canopas.yourspace.ui.theme.AppTheme + +@Composable +fun NoMemberEmptyContent( + loadingInviteCode: Boolean, + addMember: () -> Unit +) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + painter = painterResource(id = R.drawable.ic_thread_no_member), + contentDescription = null, + modifier = Modifier.size(64.dp), + tint = AppTheme.colorScheme.textPrimary + ) + Spacer(modifier = Modifier.height(24.dp)) + Text( + text = stringResource(id = R.string.threads_screen_no_members_title), + style = AppTheme.appTypography.header4 + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = stringResource(id = R.string.threads_screen_no_members_subtitle), + style = AppTheme.appTypography.subTitle1, + color = AppTheme.colorScheme.textDisabled, + textAlign = TextAlign.Center + ) + + Spacer(modifier = Modifier.height(24.dp)) + + PrimaryButton( + modifier = Modifier.fillMaxWidth(), + label = stringResource(id = R.string.thread_screen_add_new_member), + onClick = addMember, + showLoader = loadingInviteCode + ) + } +} diff --git a/app/src/main/java/com/canopas/yourspace/ui/flow/geofence/places/PlacesListScreen.kt b/app/src/main/java/com/canopas/yourspace/ui/flow/geofence/places/PlacesListScreen.kt index f3205026..1aa7482a 100644 --- a/app/src/main/java/com/canopas/yourspace/ui/flow/geofence/places/PlacesListScreen.kt +++ b/app/src/main/java/com/canopas/yourspace/ui/flow/geofence/places/PlacesListScreen.kt @@ -2,7 +2,9 @@ package com.canopas.yourspace.ui.flow.geofence.places import androidx.compose.foundation.background 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.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -48,6 +50,7 @@ import com.canopas.yourspace.ui.component.AppAlertDialog import com.canopas.yourspace.ui.component.AppBanner import com.canopas.yourspace.ui.component.AppProgressIndicator import com.canopas.yourspace.ui.component.NoInternetScreen +import com.canopas.yourspace.ui.component.NoMemberEmptyContent import com.canopas.yourspace.ui.flow.geofence.add.components.PlaceAddedPopup import com.canopas.yourspace.ui.theme.AppTheme @@ -83,7 +86,7 @@ fun PlacesListScreen() { contentColor = AppTheme.colorScheme.textPrimary, containerColor = AppTheme.colorScheme.surface, floatingActionButton = { - if (!state.placesLoading && state.connectivityStatus == ConnectivityObserver.Status.Available) { + if (!state.placesLoading && state.connectivityStatus == ConnectivityObserver.Status.Available && state.hasMembers) { FloatingActionButton( onClick = { viewModel.navigateToAddPlace() }, containerColor = AppTheme.colorScheme.primary, @@ -97,7 +100,15 @@ fun PlacesListScreen() { } ) { if (state.connectivityStatus == ConnectivityObserver.Status.Available) { - PlacesListContent(modifier = Modifier.padding(it)) + if (state.loadingSpace || state.placesLoading) { + LoadingContent(modifier = Modifier.padding(it)) + } else if (state.hasMembers) { + PlacesListContent(modifier = Modifier.padding(it)) + } else { + NoMemberEmptyContent(state.loadingInviteCode) { + viewModel.addMember() + } + } } else { NoInternetScreen(viewModel::checkInternetConnection) } @@ -136,6 +147,17 @@ fun PlacesListScreen() { } } +@Composable +fun LoadingContent(modifier: Modifier) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + modifier = modifier.fillMaxSize() + ) { + AppProgressIndicator() + } +} + @Composable private fun AddPlaceButton() { Row( diff --git a/app/src/main/java/com/canopas/yourspace/ui/flow/geofence/places/PlacesListViewModel.kt b/app/src/main/java/com/canopas/yourspace/ui/flow/geofence/places/PlacesListViewModel.kt index 7fcedbf4..7cbf535d 100644 --- a/app/src/main/java/com/canopas/yourspace/ui/flow/geofence/places/PlacesListViewModel.kt +++ b/app/src/main/java/com/canopas/yourspace/ui/flow/geofence/places/PlacesListViewModel.kt @@ -3,6 +3,7 @@ package com.canopas.yourspace.ui.flow.geofence.places import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.canopas.yourspace.data.models.place.ApiPlace +import com.canopas.yourspace.data.models.space.SpaceInfo import com.canopas.yourspace.data.models.user.ApiUser import com.canopas.yourspace.data.repository.SpaceRepository import com.canopas.yourspace.data.service.auth.AuthService @@ -37,6 +38,44 @@ class PlacesListViewModel @Inject constructor( init { checkInternetConnection() loadPlaces() + getCurrentSpace() + } + + private fun getCurrentSpace() = viewModelScope.launch(appDispatcher.IO) { + try { + _state.emit(_state.value.copy(loadingSpace = true)) + val space = spaceRepository.getCurrentSpaceInfo() + val members = space?.members ?: emptyList() + + _state.emit( + _state.value.copy( + currentSpace = space, + loadingSpace = false, + hasMembers = members.size > 1 + ) + ) + } catch (e: Exception) { + Timber.e(e, "Failed to fetch current space") + _state.emit(_state.value.copy(error = e.message, loadingSpace = false)) + } + } + + fun addMember() = viewModelScope.launch(appDispatcher.IO) { + try { + val space = spaceRepository.getCurrentSpace() ?: return@launch + var inviteCode = _state.value.inviteCode + if (inviteCode.isEmpty()) { + _state.emit(_state.value.copy(loadingInviteCode = true)) + inviteCode = spaceRepository.getInviteCode(space.id) ?: return@launch + _state.emit(_state.value.copy(loadingInviteCode = false, inviteCode = inviteCode)) + } + appNavigator.navigateTo( + AppDestinations.SpaceInvitation.spaceInvitation(inviteCode, space.name).path + ) + } catch (e: Exception) { + Timber.e(e, "Failed to get invite code") + _state.emit(_state.value.copy(error = e.message, loadingInviteCode = false)) + } } private fun loadPlaces() = viewModelScope.launch(appDispatcher.IO) { @@ -146,5 +185,10 @@ data class PlacesListScreenState( val currentUser: ApiUser? = null, val placesLoading: Boolean = false, val places: List = emptyList(), - val error: String? = null + val error: String? = null, + val loadingInviteCode: Boolean = false, + val inviteCode: String = "", + val loadingSpace: Boolean = false, + val currentSpace: SpaceInfo? = null, + val hasMembers: Boolean = false ) diff --git a/app/src/main/java/com/canopas/yourspace/ui/flow/messages/thread/ThreadsScreen.kt b/app/src/main/java/com/canopas/yourspace/ui/flow/messages/thread/ThreadsScreen.kt index 6c376afb..2d9879a6 100644 --- a/app/src/main/java/com/canopas/yourspace/ui/flow/messages/thread/ThreadsScreen.kt +++ b/app/src/main/java/com/canopas/yourspace/ui/flow/messages/thread/ThreadsScreen.kt @@ -71,7 +71,7 @@ import com.canopas.yourspace.ui.component.AppAlertDialog import com.canopas.yourspace.ui.component.AppBanner import com.canopas.yourspace.ui.component.AppProgressIndicator import com.canopas.yourspace.ui.component.NoInternetScreen -import com.canopas.yourspace.ui.component.PrimaryButton +import com.canopas.yourspace.ui.component.NoMemberEmptyContent import com.canopas.yourspace.ui.component.UserProfile import com.canopas.yourspace.ui.component.motionClickEvent import com.canopas.yourspace.ui.flow.messages.chat.toFormattedTitle @@ -441,45 +441,3 @@ fun ThreadProfile(members: List) { } } } - -@Composable -private fun NoMemberEmptyContent( - loadingInviteCode: Boolean, - addMember: () -> Unit -) { - Column( - modifier = Modifier - .fillMaxSize() - .padding(horizontal = 16.dp), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally - ) { - Icon( - painter = painterResource(id = R.drawable.ic_thread_no_member), - contentDescription = null, - modifier = Modifier.size(64.dp), - tint = AppTheme.colorScheme.textPrimary - ) - Spacer(modifier = Modifier.height(24.dp)) - Text( - text = stringResource(id = R.string.threads_screen_no_members_title), - style = AppTheme.appTypography.header4 - ) - Spacer(modifier = Modifier.height(16.dp)) - Text( - text = stringResource(id = R.string.threads_screen_no_members_subtitle), - style = AppTheme.appTypography.subTitle1, - color = AppTheme.colorScheme.textDisabled, - textAlign = TextAlign.Center - ) - - Spacer(modifier = Modifier.height(24.dp)) - - PrimaryButton( - modifier = Modifier.fillMaxWidth(), - label = stringResource(id = R.string.thread_screen_add_new_member), - onClick = addMember, - showLoader = loadingInviteCode - ) - } -} From 0c6a7145c93d365c87291d8ffd5399d1404f1e6e Mon Sep 17 00:00:00 2001 From: Sneh Date: Mon, 9 Dec 2024 12:32:53 +0530 Subject: [PATCH 2/5] minor changes. --- .../canopas/yourspace/ui/component/NoMemberEmptyContent.kt | 6 ++++-- .../yourspace/ui/flow/geofence/places/PlacesListScreen.kt | 6 +++++- .../yourspace/ui/flow/messages/thread/ThreadsScreen.kt | 6 +++++- app/src/main/res/values/strings.xml | 3 +++ 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/canopas/yourspace/ui/component/NoMemberEmptyContent.kt b/app/src/main/java/com/canopas/yourspace/ui/component/NoMemberEmptyContent.kt index 3a101dcf..b421c751 100644 --- a/app/src/main/java/com/canopas/yourspace/ui/component/NoMemberEmptyContent.kt +++ b/app/src/main/java/com/canopas/yourspace/ui/component/NoMemberEmptyContent.kt @@ -23,6 +23,8 @@ import com.canopas.yourspace.ui.theme.AppTheme @Composable fun NoMemberEmptyContent( loadingInviteCode: Boolean, + title: Int, + subtitle: Int, addMember: () -> Unit ) { Column( @@ -40,12 +42,12 @@ fun NoMemberEmptyContent( ) Spacer(modifier = Modifier.height(24.dp)) Text( - text = stringResource(id = R.string.threads_screen_no_members_title), + text = stringResource(id = title), style = AppTheme.appTypography.header4 ) Spacer(modifier = Modifier.height(16.dp)) Text( - text = stringResource(id = R.string.threads_screen_no_members_subtitle), + text = stringResource(id = subtitle), style = AppTheme.appTypography.subTitle1, color = AppTheme.colorScheme.textDisabled, textAlign = TextAlign.Center diff --git a/app/src/main/java/com/canopas/yourspace/ui/flow/geofence/places/PlacesListScreen.kt b/app/src/main/java/com/canopas/yourspace/ui/flow/geofence/places/PlacesListScreen.kt index 1aa7482a..39e17854 100644 --- a/app/src/main/java/com/canopas/yourspace/ui/flow/geofence/places/PlacesListScreen.kt +++ b/app/src/main/java/com/canopas/yourspace/ui/flow/geofence/places/PlacesListScreen.kt @@ -105,7 +105,11 @@ fun PlacesListScreen() { } else if (state.hasMembers) { PlacesListContent(modifier = Modifier.padding(it)) } else { - NoMemberEmptyContent(state.loadingInviteCode) { + NoMemberEmptyContent( + loadingInviteCode = state.loadingInviteCode, + title = R.string.place_list_screen_no_members_title, + subtitle = R.string.place_list_screen_no_members_subtitle + ) { viewModel.addMember() } } diff --git a/app/src/main/java/com/canopas/yourspace/ui/flow/messages/thread/ThreadsScreen.kt b/app/src/main/java/com/canopas/yourspace/ui/flow/messages/thread/ThreadsScreen.kt index 2d9879a6..e4245ce8 100644 --- a/app/src/main/java/com/canopas/yourspace/ui/flow/messages/thread/ThreadsScreen.kt +++ b/app/src/main/java/com/canopas/yourspace/ui/flow/messages/thread/ThreadsScreen.kt @@ -160,7 +160,11 @@ private fun ThreadsContent(modifier: Modifier) { deleteThread = { viewModel.deleteThread(it) } ) } else { - NoMemberEmptyContent(state.loadingInviteCode) { + NoMemberEmptyContent( + loadingInviteCode = state.loadingInviteCode, + title = R.string.threads_screen_no_members_title, + subtitle = R.string.threads_screen_no_members_subtitle + ) { viewModel.addMember() } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 539f8f8d..132ccd56 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -293,4 +293,7 @@ Failed to open navigation Invalid coordinates Failed to share location + + Add members to add places + At least one member needs to join your group to be able to add places. \ No newline at end of file From 66773d967aa5833f735561e6f9d53a6259377718 Mon Sep 17 00:00:00 2001 From: Sneh Date: Fri, 13 Dec 2024 18:14:04 +0530 Subject: [PATCH 3/5] minor changes. --- .../ui/flow/geofence/add/locate/LocateOnMapScreen.kt | 7 +++++++ .../flow/geofence/add/locate/LocateOnMapViewModel.kt | 11 +++++++++++ .../geofence/add/placename/ChoosePlaceNameScreen.kt | 10 ++++++++-- .../add/placename/ChoosePlaceNameViewModel.kt | 7 +++++++ 4 files changed, 33 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/canopas/yourspace/ui/flow/geofence/add/locate/LocateOnMapScreen.kt b/app/src/main/java/com/canopas/yourspace/ui/flow/geofence/add/locate/LocateOnMapScreen.kt index 0cec84af..434588dd 100644 --- a/app/src/main/java/com/canopas/yourspace/ui/flow/geofence/add/locate/LocateOnMapScreen.kt +++ b/app/src/main/java/com/canopas/yourspace/ui/flow/geofence/add/locate/LocateOnMapScreen.kt @@ -36,6 +36,7 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.canopas.yourspace.R import com.canopas.yourspace.domain.utils.ConnectivityObserver +import com.canopas.yourspace.ui.component.AppBanner import com.canopas.yourspace.ui.component.AppProgressIndicator import com.canopas.yourspace.ui.component.NoInternetScreen import com.canopas.yourspace.ui.flow.geofence.component.PlaceNameContent @@ -145,6 +146,12 @@ fun LocateOnMapScreen() { } else { NoInternetScreen(viewModel::checkInternetConnection) } + + if (state.error != null) { + AppBanner(msg = state.error!!.message!!) { + viewModel.resetErrorState() + } + } } } diff --git a/app/src/main/java/com/canopas/yourspace/ui/flow/geofence/add/locate/LocateOnMapViewModel.kt b/app/src/main/java/com/canopas/yourspace/ui/flow/geofence/add/locate/LocateOnMapViewModel.kt index e41fb112..fe8e0703 100644 --- a/app/src/main/java/com/canopas/yourspace/ui/flow/geofence/add/locate/LocateOnMapViewModel.kt +++ b/app/src/main/java/com/canopas/yourspace/ui/flow/geofence/add/locate/LocateOnMapViewModel.kt @@ -77,6 +77,13 @@ class LocateOnMapViewModel @Inject constructor( val currentSpaceId = userPreferences.currentSpace ?: return@launch val currentUser = userPreferences.currentUser ?: return@launch + if (latitude == 0.0 || longitude == 0.0) { + _state.value = _state.value.copy( + error = Exception("Invalid location.") + ) + return@launch + } + _state.emit(state.value.copy(addingPlace = true)) try { val memberIds = @@ -131,6 +138,10 @@ class LocateOnMapViewModel @Inject constructor( } } } + + fun resetErrorState() { + _state.value = _state.value.copy(error = null) + } } data class LocateOnMapState( diff --git a/app/src/main/java/com/canopas/yourspace/ui/flow/geofence/add/placename/ChoosePlaceNameScreen.kt b/app/src/main/java/com/canopas/yourspace/ui/flow/geofence/add/placename/ChoosePlaceNameScreen.kt index 476962ca..cf57b9b3 100644 --- a/app/src/main/java/com/canopas/yourspace/ui/flow/geofence/add/placename/ChoosePlaceNameScreen.kt +++ b/app/src/main/java/com/canopas/yourspace/ui/flow/geofence/add/placename/ChoosePlaceNameScreen.kt @@ -48,8 +48,14 @@ fun ChoosePlaceNameScreen() { val state by viewModel.state.collectAsState() if (state.error != null) { - AppBanner(msg = state.error!!) { - viewModel.resetErrorState() + if (state.error!!.message != null) { + AppBanner(msg = state.error!!.message!!) { + viewModel.resetErrorState() + } + } else { + AppBanner(msg = state.error!!) { + viewModel.resetErrorState() + } } } diff --git a/app/src/main/java/com/canopas/yourspace/ui/flow/geofence/add/placename/ChoosePlaceNameViewModel.kt b/app/src/main/java/com/canopas/yourspace/ui/flow/geofence/add/placename/ChoosePlaceNameViewModel.kt index 9ce6f057..090d7b95 100644 --- a/app/src/main/java/com/canopas/yourspace/ui/flow/geofence/add/placename/ChoosePlaceNameViewModel.kt +++ b/app/src/main/java/com/canopas/yourspace/ui/flow/geofence/add/placename/ChoosePlaceNameViewModel.kt @@ -67,6 +67,13 @@ class ChoosePlaceNameViewModel @Inject constructor( val currentSpaceId = userPreferences.currentSpace ?: return@launch val currentUser = userPreferences.currentUser ?: return@launch + if (selectedLatitude == 0.0 || selectedLongitude == 0.0) { + _state.value = _state.value.copy( + error = Exception("Invalid location.") + ) + return@launch + } + _state.emit(state.value.copy(addingPlace = true)) try { val memberIds = spaceRepository.getMemberBySpaceId(currentSpaceId)?.map { it.user_id } From d3f1606dd7ffdaa169ca1bc9c4261fb9184a749b Mon Sep 17 00:00:00 2001 From: Sneh Date: Fri, 13 Dec 2024 18:21:45 +0530 Subject: [PATCH 4/5] minor changes. --- .../geofence/add/placename/ChoosePlaceNameScreen.kt | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/canopas/yourspace/ui/flow/geofence/add/placename/ChoosePlaceNameScreen.kt b/app/src/main/java/com/canopas/yourspace/ui/flow/geofence/add/placename/ChoosePlaceNameScreen.kt index cf57b9b3..58bc52b5 100644 --- a/app/src/main/java/com/canopas/yourspace/ui/flow/geofence/add/placename/ChoosePlaceNameScreen.kt +++ b/app/src/main/java/com/canopas/yourspace/ui/flow/geofence/add/placename/ChoosePlaceNameScreen.kt @@ -48,14 +48,9 @@ fun ChoosePlaceNameScreen() { val state by viewModel.state.collectAsState() if (state.error != null) { - if (state.error!!.message != null) { - AppBanner(msg = state.error!!.message!!) { - viewModel.resetErrorState() - } - } else { - AppBanner(msg = state.error!!) { - viewModel.resetErrorState() - } + val errorMessage = state.error?.message ?: state.error.toString() + AppBanner(msg = errorMessage) { + viewModel.resetErrorState() } } From afac3ae59a3439d9fbf439acaaeeaecfa606f3e2 Mon Sep 17 00:00:00 2001 From: Sneh Date: Mon, 16 Dec 2024 14:22:40 +0530 Subject: [PATCH 5/5] minor changes. --- app/src/main/res/values/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7cafd4d6..b488f1e9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -301,4 +301,7 @@ Change Admin To leave the group, you must assign another member as admin. This action is irreversible unless the new admin changes it. Change Admin + + Add members to add places + At least one member needs to join your group to be able to add places. \ No newline at end of file