diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7aaae700..204fb571 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -210,5 +210,8 @@ dependencies { // Desugaring coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.3") + // Gson + implementation("com.google.code.gson:gson:2.10.1") + implementation(project(":data")) } diff --git a/app/src/main/java/com/canopas/yourspace/ui/MainActivity.kt b/app/src/main/java/com/canopas/yourspace/ui/MainActivity.kt index 3ff87234..fb6a1c13 100644 --- a/app/src/main/java/com/canopas/yourspace/ui/MainActivity.kt +++ b/app/src/main/java/com/canopas/yourspace/ui/MainActivity.kt @@ -1,6 +1,7 @@ package com.canopas.yourspace.ui import android.content.Intent +import android.net.Uri import android.os.Bundle import android.view.Window import androidx.activity.ComponentActivity @@ -22,6 +23,7 @@ import androidx.lifecycle.ViewModelProvider import androidx.navigation.compose.NavHost import androidx.navigation.compose.rememberNavController import com.canopas.yourspace.R +import com.canopas.yourspace.data.models.space.SpaceInfo import com.canopas.yourspace.ui.component.AppAlertDialog import com.canopas.yourspace.ui.flow.auth.SignInMethodsScreen import com.canopas.yourspace.ui.flow.geofence.add.addnew.AddNewPlaceScreen @@ -48,13 +50,16 @@ import com.canopas.yourspace.ui.flow.permission.EnablePermissionsScreen import com.canopas.yourspace.ui.flow.settings.SettingsScreen import com.canopas.yourspace.ui.flow.settings.profile.EditProfileScreen import com.canopas.yourspace.ui.flow.settings.space.SpaceProfileScreen +import com.canopas.yourspace.ui.flow.settings.space.edit.ChangeAdminScreen import com.canopas.yourspace.ui.flow.settings.support.SupportScreen import com.canopas.yourspace.ui.navigation.AppDestinations +import com.canopas.yourspace.ui.navigation.AppDestinations.ChangeAdminScreen.KEY_SPACE_NAME import com.canopas.yourspace.ui.navigation.AppNavigator import com.canopas.yourspace.ui.navigation.KEY_RESULT import com.canopas.yourspace.ui.navigation.RESULT_OKAY import com.canopas.yourspace.ui.navigation.slideComposable import com.canopas.yourspace.ui.theme.CatchMeTheme +import com.google.gson.Gson import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint @@ -159,6 +164,15 @@ fun MainApp(viewModel: MainViewModel) { SpaceProfileScreen() } + slideComposable(AppDestinations.ChangeAdminScreen.path) { + val spaceInfo = it.arguments?.getString(KEY_SPACE_NAME)?.let { encodedInfo -> + Uri.decode(encodedInfo) + } ?: "" + + val space = Gson().fromJson(spaceInfo, SpaceInfo::class.java) + ChangeAdminScreen(space = space) + } + slideComposable(AppDestinations.spaceThreads.path) { ThreadsScreen() } diff --git a/app/src/main/java/com/canopas/yourspace/ui/flow/settings/space/SpaceProfileScreen.kt b/app/src/main/java/com/canopas/yourspace/ui/flow/settings/space/SpaceProfileScreen.kt index 38f969c0..f337d2b4 100644 --- a/app/src/main/java/com/canopas/yourspace/ui/flow/settings/space/SpaceProfileScreen.kt +++ b/app/src/main/java/com/canopas/yourspace/ui/flow/settings/space/SpaceProfileScreen.kt @@ -20,6 +20,9 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ExitToApp import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon @@ -105,6 +108,18 @@ fun SpaceProfileScreen() { ) } + if (state.showChangeAdminDialog) { + AppAlertDialog( + title = stringResource(R.string.space_setting_change_admin_title), + subTitle = stringResource(R.string.space_setting_change_admin_description), + confirmBtnText = stringResource(R.string.change_admin_button), + dismissBtnText = stringResource(id = R.string.common_btn_cancel), + isConfirmDestructive = true, + onConfirmClick = { viewModel.onChangeAdminClicked() }, + onDismissClick = { viewModel.showChangeAdminDialog(false) } + ) + } + if (state.showLeaveSpaceConfirmation) { AppAlertDialog( title = stringResource(id = R.string.space_settings_btn_leave_space), @@ -180,6 +195,29 @@ private fun SpaceProfileToolbar() { } ) ) + if (state.isAdmin && state.spaceMemberCount > 1) { + IconButton( + onClick = { viewModel.onAdminMenuExpanded(true) } + ) { + Icon( + imageVector = Icons.Default.MoreVert, + contentDescription = "" + ) + } + + DropdownMenu( + expanded = state.isMenuExpanded, + onDismissRequest = { viewModel.onAdminMenuExpanded(false) } + ) { + DropdownMenuItem( + text = { Text("Change Admin") }, + onClick = { + viewModel.onAdminMenuExpanded(false) + viewModel.navigateToChangeAdminScreen(state.spaceInfo) + } + ) + } + } } ) } @@ -276,7 +314,7 @@ private fun SpaceProfileContent() { ) } } - if (state.spaceInfo != null && state.currentUserId == state.spaceInfo?.space?.admin_id) { + if (state.spaceInfo != null && state.spaceMemberCount == 1) { FooterButton( title = stringResource(id = R.string.space_settings_btn_delete_space), onClick = { @@ -287,11 +325,15 @@ private fun SpaceProfileContent() { ) } - if (state.spaceInfo != null && state.currentUserId != state.spaceInfo?.space?.admin_id) { + if (state.spaceInfo != null && state.spaceMemberCount > 1) { FooterButton( title = stringResource(id = R.string.space_settings_btn_leave_space), onClick = { - viewModel.showLeaveSpaceConfirmation(true) + if (state.isAdmin) { + viewModel.showChangeAdminDialog(true) + } else { + viewModel.showLeaveSpaceConfirmation(true) + } }, showLoader = state.leavingSpace, icon = Icons.AutoMirrored.Filled.ExitToApp @@ -351,6 +393,9 @@ private fun UserItem( onCheckedChange: (Boolean) -> Unit, onMemberRemove: () -> Unit ) { + val viewModel = hiltViewModel() + val state by viewModel.state.collectAsState() + Row( modifier = Modifier .fillMaxWidth() @@ -360,13 +405,28 @@ private fun UserItem( ) { UserProfile(modifier = Modifier.size(40.dp), user = userInfo.user) Spacer(modifier = Modifier.width(8.dp)) - Text( - text = userInfo.user.fullName, - style = AppTheme.appTypography.subTitle2, - color = AppTheme.colorScheme.textPrimary, - textAlign = TextAlign.Start, + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Start, modifier = Modifier.weight(1f) - ) + ) { + Text( + text = userInfo.user.fullName, + style = AppTheme.appTypography.subTitle2, + color = AppTheme.colorScheme.textPrimary, + textAlign = TextAlign.Start + ) + + if (state.spaceInfo?.space?.admin_id == userInfo.user.id) { + Text( + text = stringResource(R.string.space_profile_screen_admin_text), + style = AppTheme.appTypography.subTitle3, + color = AppTheme.colorScheme.textDisabled, + modifier = Modifier.padding(start = 8.dp) + ) + } + } Switch( checked = isChecked, diff --git a/app/src/main/java/com/canopas/yourspace/ui/flow/settings/space/SpaceProfileViewModel.kt b/app/src/main/java/com/canopas/yourspace/ui/flow/settings/space/SpaceProfileViewModel.kt index 8aa9d2d6..1adfbf91 100644 --- a/app/src/main/java/com/canopas/yourspace/ui/flow/settings/space/SpaceProfileViewModel.kt +++ b/app/src/main/java/com/canopas/yourspace/ui/flow/settings/space/SpaceProfileViewModel.kt @@ -1,5 +1,6 @@ package com.canopas.yourspace.ui.flow.settings.space +import android.net.Uri import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -10,6 +11,7 @@ import com.canopas.yourspace.data.utils.AppDispatcher import com.canopas.yourspace.domain.utils.ConnectivityObserver import com.canopas.yourspace.ui.navigation.AppDestinations import com.canopas.yourspace.ui.navigation.AppNavigator +import com.google.gson.Gson import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -54,7 +56,8 @@ class SpaceProfileViewModel @Inject constructor( currentUserId = authService.currentUser?.id, isAdmin = spaceInfo?.space?.admin_id == authService.currentUser?.id, spaceName = spaceInfo?.space?.name, - locationEnabled = locationEnabled + locationEnabled = locationEnabled, + spaceMemberCount = spaceInfo?.members?.size ?: 1 ) ) } catch (e: Exception) { @@ -184,18 +187,27 @@ class SpaceProfileViewModel @Inject constructor( } } - fun checkInternetConnection() { - viewModelScope.launch(appDispatcher.IO) { - connectivityObserver.observe().collectLatest { status -> - _state.emit( - _state.value.copy( - connectivityStatus = status - ) - ) - } + fun onChangeAdminClicked() { + val spaceInfo = _state.value.spaceInfo + if (spaceInfo != null) { + navigateToChangeAdminScreen(spaceInfo) + _state.value = _state.value.copy(showChangeAdminDialog = false) } } + fun onAdminMenuExpanded(value: Boolean) { + _state.value = _state.value.copy(isMenuExpanded = value) + } + + fun showChangeAdminDialog(show: Boolean) { + _state.value = _state.value.copy(showChangeAdminDialog = show) + } + + fun navigateToChangeAdminScreen(spaceInfo: SpaceInfo?) { + val spaceDetail = Uri.encode(Gson().toJson(spaceInfo)) + navigator.navigateTo(AppDestinations.ChangeAdminScreen.getSpaceDetail(spaceDetail).path) + } + fun showRemoveMemberConfirmationWithId(show: Boolean, memberId: String) { _state.value = state.value.copy(showRemoveMemberConfirmation = show, memberToRemove = memberId) } @@ -220,6 +232,18 @@ class SpaceProfileViewModel @Inject constructor( ) } } + + fun checkInternetConnection() { + viewModelScope.launch(appDispatcher.IO) { + connectivityObserver.observe().collectLatest { status -> + _state.emit( + _state.value.copy( + connectivityStatus = status + ) + ) + } + } + } } data class SpaceProfileState( @@ -238,5 +262,8 @@ data class SpaceProfileState( val error: Exception? = null, val connectivityStatus: ConnectivityObserver.Status = ConnectivityObserver.Status.Available, val showRemoveMemberConfirmation: Boolean = false, - val memberToRemove: String? = null + val memberToRemove: String? = null, + val spaceMemberCount: Int = 1, + val showChangeAdminDialog: Boolean = false, + var isMenuExpanded: Boolean = false ) diff --git a/app/src/main/java/com/canopas/yourspace/ui/flow/settings/space/edit/ChangeAdminScreen.kt b/app/src/main/java/com/canopas/yourspace/ui/flow/settings/space/edit/ChangeAdminScreen.kt new file mode 100644 index 00000000..0a76ad36 --- /dev/null +++ b/app/src/main/java/com/canopas/yourspace/ui/flow/settings/space/edit/ChangeAdminScreen.kt @@ -0,0 +1,188 @@ +package com.canopas.yourspace.ui.flow.settings.space.edit + +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 +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.RadioButton +import androidx.compose.material3.RadioButtonDefaults +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +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 androidx.hilt.navigation.compose.hiltViewModel +import com.canopas.yourspace.R +import com.canopas.yourspace.data.models.space.SpaceInfo +import com.canopas.yourspace.data.models.user.UserInfo +import com.canopas.yourspace.domain.utils.ConnectivityObserver +import com.canopas.yourspace.ui.component.AppProgressIndicator +import com.canopas.yourspace.ui.component.NoInternetScreen +import com.canopas.yourspace.ui.component.UserProfile +import com.canopas.yourspace.ui.theme.AppTheme + +@Composable +fun ChangeAdminScreen(space: SpaceInfo) { + val viewModel = hiltViewModel() + val state by viewModel.state.collectAsState() + var selectedUserId by remember { mutableStateOf(space.space.admin_id) } + + LaunchedEffect(Unit) { + if (space.space.id.isNotEmpty()) { + viewModel.fetchSpaceDetail(space.space.id) + state.spaceID = space.space.id + selectedUserId = space.space.admin_id + } + } + + Scaffold( + topBar = { ChangeAdminAppBar(selectedUserId) } + ) { + Box( + modifier = Modifier + .padding(it) + .fillMaxSize(), + contentAlignment = Alignment.Center + ) { + if (state.connectivityStatus == ConnectivityObserver.Status.Available) { + SpaceMemberListContent( + userInfo = space.members, + onUserSelect = { userId -> selectedUserId = userId }, + selectedUserId = selectedUserId + ) + if (state.isLoading) { + AppProgressIndicator() + } + } else { + NoInternetScreen(viewModel::checkInternetConnection) + } + } + } +} + +@Composable +fun SpaceMemberListContent( + userInfo: List, + onUserSelect: (String) -> Unit, + selectedUserId: String? +) { + val scrollState = rememberScrollState() + Box(modifier = Modifier.fillMaxSize()) { + Column( + Modifier + .fillMaxSize() + .verticalScroll(scrollState) + .padding(bottom = 80.dp) + ) { + userInfo.forEach { user -> + UserItem( + userInfo = user, + isSelected = selectedUserId == user.user.id, + onUserSelect = onUserSelect + ) + } + } + } +} + +@Composable +private fun UserItem( + userInfo: UserInfo, + isSelected: Boolean, + onUserSelect: (String) -> Unit +) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + UserProfile(modifier = Modifier.size(40.dp), user = userInfo.user) + Spacer(modifier = Modifier.width(16.dp)) + Text( + text = userInfo.user.fullName, + style = AppTheme.appTypography.subTitle2, + color = AppTheme.colorScheme.textPrimary, + textAlign = TextAlign.Start, + modifier = Modifier.weight(1f) + ) + + RadioButton( + selected = isSelected, + onClick = { onUserSelect(userInfo.user.id) }, + enabled = true, + colors = RadioButtonDefaults.colors( + selectedColor = AppTheme.colorScheme.primary, + unselectedColor = AppTheme.colorScheme.textDisabled + ), + modifier = Modifier.padding(end = 4.dp) + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ChangeAdminAppBar(selectedUserId: String?) { + val viewModel = hiltViewModel() + + TopAppBar( + colors = TopAppBarDefaults.topAppBarColors(containerColor = AppTheme.colorScheme.surface), + title = {}, + navigationIcon = { + Row( + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically + ) { + IconButton(onClick = { viewModel.popBackStack() }) { + Icon( + painter = painterResource(id = R.drawable.ic_nav_back_arrow_icon), + contentDescription = "", + tint = AppTheme.colorScheme.primary + ) + } + Text( + text = stringResource(id = R.string.common_btn_back), + color = AppTheme.colorScheme.primary, + style = MaterialTheme.typography.titleMedium + ) + } + }, + actions = { + IconButton( + onClick = { selectedUserId?.let { viewModel.changeAdmin(it) } } + ) { + Icon( + imageVector = Icons.Default.Check, + contentDescription = "" + ) + } + } + ) +} diff --git a/app/src/main/java/com/canopas/yourspace/ui/flow/settings/space/edit/ChangeAdminViewModel.kt b/app/src/main/java/com/canopas/yourspace/ui/flow/settings/space/edit/ChangeAdminViewModel.kt new file mode 100644 index 00000000..a317c680 --- /dev/null +++ b/app/src/main/java/com/canopas/yourspace/ui/flow/settings/space/edit/ChangeAdminViewModel.kt @@ -0,0 +1,101 @@ +package com.canopas.yourspace.ui.flow.settings.space.edit + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.canopas.yourspace.data.models.space.SpaceInfo +import com.canopas.yourspace.data.models.user.UserInfo +import com.canopas.yourspace.data.repository.SpaceRepository +import com.canopas.yourspace.data.service.auth.AuthService +import com.canopas.yourspace.data.utils.AppDispatcher +import com.canopas.yourspace.domain.utils.ConnectivityObserver +import com.canopas.yourspace.ui.navigation.AppNavigator +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch +import timber.log.Timber +import javax.inject.Inject + +@HiltViewModel +class ChangeAdminViewModel @Inject constructor( + private val appDispatcher: AppDispatcher, + private val connectivityObserver: ConnectivityObserver, + private val appNavigator: AppNavigator, + private val spaceRepository: SpaceRepository, + private val authService: AuthService +) : ViewModel() { + + private val _state = MutableStateFlow(ChangeAdminState()) + val state = _state.asStateFlow() + + init { + checkInternetConnection() + } + + fun fetchSpaceDetail(spaceID: String) = viewModelScope.launch(appDispatcher.IO) { + _state.emit(_state.value.copy(isLoading = true)) + try { + val spaceInfo = spaceRepository.getSpaceInfo(spaceID) + _state.emit( + _state.value.copy( + isLoading = false, + spaceInfo = spaceInfo, + currentUserId = authService.currentUser?.id, + isAdmin = spaceInfo?.space?.admin_id == authService.currentUser?.id, + spaceName = spaceInfo?.space?.name, + members = spaceInfo?.members ?: emptyList() + ) + ) + } catch (e: Exception) { + Timber.e(e, "Failed to fetch space detail") + _state.emit(_state.value.copy(error = e, isLoading = false)) + } + } + + fun changeAdmin(newAdminId: String) { + viewModelScope.launch(appDispatcher.IO) { + try { + spaceRepository.changeSpaceAdmin(state.value.spaceID, newAdminId) + _state.emit( + _state.value.copy( + isAdmin = true, + currentUserId = newAdminId + ) + ) + popBackStack() + } catch (e: Exception) { + Timber.e(e, "Failed to change admin") + _state.emit(_state.value.copy(error = e)) + } + } + } + + fun popBackStack() { + appNavigator.navigateBack() + } + + fun checkInternetConnection() { + viewModelScope.launch(appDispatcher.IO) { + connectivityObserver.observe().collectLatest { status -> + _state.emit( + _state.value.copy( + connectivityStatus = status + ) + ) + } + } + } +} + +data class ChangeAdminState( + val isLoading: Boolean = false, + var spaceID: String = "", + val currentUserId: String? = null, + val isAdmin: Boolean = false, + val members: List? = null, + val spaceInfo: SpaceInfo? = null, + val spaceName: String? = null, + val error: Exception? = null, + val connectivityStatus: ConnectivityObserver.Status = ConnectivityObserver.Status.Available +) diff --git a/app/src/main/java/com/canopas/yourspace/ui/navigation/AppRoute.kt b/app/src/main/java/com/canopas/yourspace/ui/navigation/AppRoute.kt index 5efc4ed6..9de649c2 100644 --- a/app/src/main/java/com/canopas/yourspace/ui/navigation/AppRoute.kt +++ b/app/src/main/java/com/canopas/yourspace/ui/navigation/AppRoute.kt @@ -20,6 +20,24 @@ object AppDestinations { override val path: String = "settings" } + object ChangeAdminScreen { + const val KEY_SPACE_NAME = "space-name" + + private const val PATH = "change-admin" + const val path = "$PATH/{$KEY_SPACE_NAME}" + + fun getSpaceDetail( + spaceInfo: String + ) = object : AppRoute { + + override val arguments = listOf( + navArgument(KEY_SPACE_NAME) { type = NavType.StringType } + ) + + override val path = "$PATH/$spaceInfo" + } + } + val contactSupport = object : AppRoute { override val arguments: List = emptyList() override val path: String = "contact-support" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 539f8f8d..a7573f37 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -22,6 +22,7 @@ Reply Save Retry + Back Let\'s stay connected! Use invite code %s to join my Group.\nDownload the app. You\'ve been logged out @@ -293,4 +294,8 @@ Failed to open navigation Invalid coordinates Failed to share location + (Admin) + Change Admin + There are members in this group. Please change the admin before leaving. + Change Admin \ No newline at end of file diff --git a/app/src/test/java/com/canopas/yourspace/ui/flow/settings/space/SpaceProfileViewModelTest.kt b/app/src/test/java/com/canopas/yourspace/ui/flow/settings/space/SpaceProfileViewModelTest.kt index 79b7a1e4..fe0f1df4 100644 --- a/app/src/test/java/com/canopas/yourspace/ui/flow/settings/space/SpaceProfileViewModelTest.kt +++ b/app/src/test/java/com/canopas/yourspace/ui/flow/settings/space/SpaceProfileViewModelTest.kt @@ -29,10 +29,11 @@ import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +@OptIn(ExperimentalCoroutinesApi::class) class SpaceProfileViewModelTest { private val space = ApiSpace(id = "space1", admin_id = "user1", name = "space_name") - val user1 = ApiUser(id = "user1", first_name = "first_name", last_name = "last_name") + private val user1 = ApiUser(id = "user1", first_name = "first_name", last_name = "last_name") private val user2 = ApiUser(id = "user2", first_name = "first_name", last_name = "last_name") private val userInfo1 = UserInfo(user1, isLocationEnable = true) private val userInfo2 = UserInfo(user2) @@ -40,7 +41,6 @@ class SpaceProfileViewModelTest { val space_info1 = SpaceInfo(space = space, members = members) - @OptIn(ExperimentalCoroutinesApi::class) @get:Rule val mainCoroutineRule = MainCoroutineRule() @@ -53,7 +53,6 @@ class SpaceProfileViewModelTest { private val authService = mock() private val connectivityObserver = mock() - @OptIn(ExperimentalCoroutinesApi::class) private val testDispatcher = AppDispatcher(IO = UnconfinedTestDispatcher()) private lateinit var viewModel: SpaceProfileViewModel diff --git a/data/src/main/java/com/canopas/yourspace/data/repository/SpaceRepository.kt b/data/src/main/java/com/canopas/yourspace/data/repository/SpaceRepository.kt index bfcc43b4..7cb6c85e 100644 --- a/data/src/main/java/com/canopas/yourspace/data/repository/SpaceRepository.kt +++ b/data/src/main/java/com/canopas/yourspace/data/repository/SpaceRepository.kt @@ -228,4 +228,8 @@ class SpaceRepository @Inject constructor( userService.updateUser(it) } } + + suspend fun changeSpaceAdmin(spaceId: String, newAdminId: String) { + spaceService.changeAdmin(spaceId, newAdminId) + } } diff --git a/data/src/main/java/com/canopas/yourspace/data/service/space/ApiSpaceService.kt b/data/src/main/java/com/canopas/yourspace/data/service/space/ApiSpaceService.kt index c20f7fc2..6bbf1365 100644 --- a/data/src/main/java/com/canopas/yourspace/data/service/space/ApiSpaceService.kt +++ b/data/src/main/java/com/canopas/yourspace/data/service/space/ApiSpaceService.kt @@ -12,6 +12,7 @@ import com.canopas.yourspace.data.utils.Config.FIRESTORE_COLLECTION_SPACE_MEMBER import com.canopas.yourspace.data.utils.snapshotFlow import com.google.firebase.firestore.FirebaseFirestore import kotlinx.coroutines.tasks.await +import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -100,4 +101,14 @@ class ApiSpaceService @Inject constructor( suspend fun updateSpace(space: ApiSpace) { spaceRef.document(space.id).set(space).await() } + + suspend fun changeAdmin(spaceId: String, newAdminId: String) { + try { + val spaceRef = spaceRef.document(spaceId) + spaceRef.update("admin_id", newAdminId).await() + } catch (e: Exception) { + Timber.e(e, "Failed to change admin") + throw e + } + } }