diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4e071d9..6c8049f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -33,7 +33,6 @@ android { setConfigVariable(variableName = "CURRENCY_CODE", variableSource = "CURRENCY_CODE") setConfigVariable(variableName = "API_BASE_URL", variableSource = "API_BASE_URL") - setConfigVariable(variableName = "ADMIN_LIVE", variableSource = "ADMIN_LIVE") setConfigVariable(variableName = "ADMIN_STAGING", variableSource = "ADMIN_STAGING") setConfigVariable(variableName = "PATH_USER_IMAGES", variableSource = "PATH_USER_IMAGES") setConfigVariable(variableName = "PATH_PAYMENT", variableSource = "PATH_PAYMENT") @@ -109,7 +108,7 @@ android { detekt { toolVersion = libs.versions.detekt.get() config.setFrom(rootProject.file("detekt.yml")) - buildUponDefaultConfig = true + buildUponDefaultConfig = false } packaging { diff --git a/app/src/main/kotlin/com/ndemi/garden/gym/di/RepositoryModule.kt b/app/src/main/kotlin/com/ndemi/garden/gym/di/RepositoryModule.kt index 38be914..06f9e5f 100644 --- a/app/src/main/kotlin/com/ndemi/garden/gym/di/RepositoryModule.kt +++ b/app/src/main/kotlin/com/ndemi/garden/gym/di/RepositoryModule.kt @@ -15,7 +15,7 @@ val repositoryModule = module { single { AuthRepositoryImp( - adminEmails = listOf(BuildConfig.ADMIN_LIVE, BuildConfig.ADMIN_STAGING), + pathUser = BuildConfig.PATH_USER, logger = get() ) } diff --git a/app/src/main/kotlin/com/ndemi/garden/gym/di/ViewModelModule.kt b/app/src/main/kotlin/com/ndemi/garden/gym/di/ViewModelModule.kt index 2bebeaa..231c235 100644 --- a/app/src/main/kotlin/com/ndemi/garden/gym/di/ViewModelModule.kt +++ b/app/src/main/kotlin/com/ndemi/garden/gym/di/ViewModelModule.kt @@ -18,7 +18,7 @@ import org.koin.dsl.module val viewModelModule = module { - viewModel { MainScreenViewModel(get(), get()) } + viewModel { MainScreenViewModel(get(), get(), get()) } viewModel { LoginScreenViewModel(get(), get(), get()) } @@ -26,17 +26,17 @@ val viewModelModule = viewModel { AttendanceScreenViewModel(get(), get()) } - viewModel { PaymentsScreenViewModel(get(), get(), get()) } + viewModel { PaymentsScreenViewModel(get(), get(), get(), get()) } viewModel { PaymentAddScreenViewModel(get(), get(), get()) } viewModel { LiveAttendanceScreenViewModel(get(), get()) } - viewModel { MembersScreenViewModel(get(), get(), get()) } + viewModel { MembersScreenViewModel(get(), get(), get(), get()) } - viewModel { MemberEditScreenViewModel(get(), get(), get(), get()) } + viewModel { MemberEditScreenViewModel(get(), get(), get(), get(), get()) } - viewModel { MembersAttendancesScreenViewModel(get(), get(), get()) } + viewModel { MembersAttendancesScreenViewModel(get(), get(), get(), get()) } viewModel { RegisterScreenViewModel(get(), get(), get(), get()) } diff --git a/app/src/main/kotlin/com/ndemi/garden/gym/navigation/NavigationServiceImp.kt b/app/src/main/kotlin/com/ndemi/garden/gym/navigation/NavigationServiceImp.kt index d4f335e..5b34a1f 100644 --- a/app/src/main/kotlin/com/ndemi/garden/gym/navigation/NavigationServiceImp.kt +++ b/app/src/main/kotlin/com/ndemi/garden/gym/navigation/NavigationServiceImp.kt @@ -18,7 +18,7 @@ class NavigationServiceImp( override fun setNavController(navController: NavController) { this.navController = navController - val initialRoute = Route.getInitialRoute(authUseCase.isAuthenticated(), authUseCase.isAdmin()) + val initialRoute = Route.getInitialRoute(authUseCase.isAuthenticated(), authUseCase.isNotMember()) this.initialRoute = initialRoute } @@ -56,7 +56,7 @@ class NavigationServiceImp( override fun getCurrentRoute(): Route = navController.currentDestination?.route?.toRoute() ?: - Route.getInitialRoute(authUseCase.isAuthenticated(), authUseCase.isAdmin()) + Route.getInitialRoute(authUseCase.isAuthenticated(), authUseCase.isNotMember()) override fun getInitialRoute(): Route = initialRoute } diff --git a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/login/LoginScreen.kt b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/login/LoginScreen.kt index acaef5d..c34dc14 100644 --- a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/login/LoginScreen.kt +++ b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/login/LoginScreen.kt @@ -36,9 +36,9 @@ fun LoginScreen( initial = UiState.Waiting ) - if (uiState.value is UiState.Success){ - viewModel.navigateLogInSuccess() - } +// if (uiState.value is UiState.Success){ +// viewModel.navigateLogInSuccess() +// } Column( modifier = Modifier diff --git a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/main/MainDetailsScreen.kt b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/main/MainDetailsScreen.kt new file mode 100644 index 0000000..3fdf61e --- /dev/null +++ b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/main/MainDetailsScreen.kt @@ -0,0 +1,69 @@ +package com.ndemi.garden.gym.ui.screens.main + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.ImageShader +import androidx.compose.ui.graphics.ShaderBrush +import androidx.compose.ui.graphics.TileMode +import androidx.compose.ui.res.imageResource +import androidx.navigation.NavHostController +import com.ndemi.garden.gym.R +import com.ndemi.garden.gym.navigation.NavigationHost +import com.ndemi.garden.gym.navigation.NavigationService +import com.ndemi.garden.gym.ui.theme.AppTheme +import com.ndemi.garden.gym.ui.theme.padding_screen +import com.ndemi.garden.gym.ui.widgets.BottomNavItem +import com.ndemi.garden.gym.ui.widgets.BottomNavigationWidget + + +@Composable +fun MainDetailsScreen( + isAuthenticated: Boolean, + isAdmin: Boolean, + navController: NavHostController, + navigationService: NavigationService, +){ + Scaffold( + topBar = {}, + bottomBar = { + val bottomNavItems = if (isAuthenticated) { + if (isAdmin) { + BottomNavItem.getAdminBottomItems() + } else { + BottomNavItem.getMemberBottomItems() + } + } else { + BottomNavItem.getLoginBottomItems() + } + BottomNavigationWidget( + navController, + bottomNavItems + ) + }, + ) { innerPadding -> + val image = ImageBitmap.imageResource(R.drawable.bg_pattern) + val brush = remember(image) { ShaderBrush(ImageShader(image, TileMode.Repeated, TileMode.Repeated)) } + Column( + modifier = + Modifier + .padding(innerPadding) + .fillMaxSize() + .background(AppTheme.colors.backgroundScreen) + .background(brush, alpha = 0.05f), + verticalArrangement = Arrangement.spacedBy(padding_screen), + ) { + NavigationHost( + navController = navController, + navigationService = navigationService, + ) + } + } +} diff --git a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/main/MainScreen.kt b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/main/MainScreen.kt index 3b8c1e3..40b8c65 100644 --- a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/main/MainScreen.kt +++ b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/main/MainScreen.kt @@ -1,30 +1,11 @@ package com.ndemi.garden.gym.ui.screens.main -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.ImageBitmap -import androidx.compose.ui.graphics.ImageShader -import androidx.compose.ui.graphics.ShaderBrush -import androidx.compose.ui.graphics.TileMode -import androidx.compose.ui.res.imageResource -import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.compose.runtime.livedata.observeAsState import androidx.navigation.compose.rememberNavController -import com.ndemi.garden.gym.R -import com.ndemi.garden.gym.navigation.NavigationHost -import com.ndemi.garden.gym.navigation.Route -import com.ndemi.garden.gym.navigation.Route.Companion.toRoute -import com.ndemi.garden.gym.ui.theme.AppTheme -import com.ndemi.garden.gym.ui.theme.padding_screen -import com.ndemi.garden.gym.ui.widgets.BottomNavItem -import com.ndemi.garden.gym.ui.widgets.BottomNavigationWidget +import com.ndemi.garden.gym.ui.widgets.LoadingScreenWidget +import com.ndemi.garden.gym.ui.widgets.WarningWidget +import cv.domain.entities.MemberType import org.koin.androidx.compose.koinViewModel @Composable @@ -33,45 +14,48 @@ fun MainScreen( ) { val navController = rememberNavController() viewModel.setNavController(navController) - val navBackStackEntry by navController.currentBackStackEntryAsState() - val currentRoute = navBackStackEntry?.destination?.route?.toRoute() + val authState = viewModel.authState.observeAsState() - Scaffold( - topBar = {}, - bottomBar = { - val bottomNavItems = when (currentRoute) { - Route.LoginScreen -> BottomNavItem.getLoginBottomItems() - else -> { - if (viewModel.isAuthenticated() && viewModel.isAdmin()){ - BottomNavItem.getAdminBottomItems() - } else if (viewModel.isAuthenticated()){ - BottomNavItem.getMemberBottomItems() - } else { - BottomNavItem.getLoginBottomItems() - } + when(authState.value){ + MainScreenViewModel.AuthState.Authorised -> { + val data = viewModel.loggedInMember.observeAsState() + when(val response = data.value){ + + is MainScreenViewModel.UiState.Error -> { + WarningWidget(title = response.message) + } + + MainScreenViewModel.UiState.Loading -> { + LoadingScreenWidget() + } + + is MainScreenViewModel.UiState.Success -> { + MainDetailsScreen( + isAuthenticated = true, + isAdmin = response.member.memberType != MemberType.MEMBER, + navController = navController, + navigationService = viewModel.getNavigationService() + ) + } + + null -> { + LoadingScreenWidget() } } - BottomNavigationWidget( - navController, - bottomNavItems - ) - }, - ) { innerPadding -> - val image = ImageBitmap.imageResource(R.drawable.bg_pattern) - val brush = remember(image) { ShaderBrush(ImageShader(image, TileMode.Repeated, TileMode.Repeated)) } - Column( - modifier = - Modifier - .padding(innerPadding) - .fillMaxSize() - .background(AppTheme.colors.backgroundScreen) - .background(brush, alpha = 0.05f), - verticalArrangement = Arrangement.spacedBy(padding_screen), - ) { - NavigationHost( + } + + MainScreenViewModel.AuthState.UnAuthorised -> { + MainDetailsScreen( + isAuthenticated = false, + isAdmin = false, navController = navController, - navigationService = viewModel.getNavigationService(), + navigationService = viewModel.getNavigationService() ) } + + null -> { + LoadingScreenWidget() + } } } + diff --git a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/main/MainScreenViewModel.kt b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/main/MainScreenViewModel.kt index 8ea39db..6186b03 100644 --- a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/main/MainScreenViewModel.kt +++ b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/main/MainScreenViewModel.kt @@ -1,20 +1,67 @@ package com.ndemi.garden.gym.ui.screens.main +import androidx.compose.runtime.Immutable import androidx.lifecycle.ViewModel +import androidx.lifecycle.liveData import androidx.navigation.NavHostController import com.ndemi.garden.gym.navigation.NavigationService +import com.ndemi.garden.gym.ui.utils.ErrorCodeConverter +import cv.domain.DomainResult +import cv.domain.entities.MemberEntity import cv.domain.usecase.AuthUseCase +import kotlinx.coroutines.Dispatchers class MainScreenViewModel( private val navigationService: NavigationService, private val authUseCase: AuthUseCase, + private val errorCodeConverter: ErrorCodeConverter, ) : ViewModel() { fun setNavController(navController: NavHostController) = navigationService.setNavController(navController) fun getNavigationService(): NavigationService = navigationService - fun isAuthenticated() = authUseCase.isAuthenticated() + val authState = liveData(Dispatchers.IO) { + emit(AuthState.UnAuthorised) + authUseCase.getAuthState().collect{ + when(it){ + is DomainResult.Success -> + emit(AuthState.Authorised) - fun isAdmin() = authUseCase.isAdmin() + is DomainResult.Error -> + emit(AuthState.UnAuthorised) + } + } + + } + + val loggedInMember = liveData(Dispatchers.IO) { + emit(UiState.Loading) + authUseCase.getLoggedInUser().collect { + + when (it) { + is DomainResult.Success -> + emit(UiState.Success(it.data)) + + is DomainResult.Error -> + emit(UiState.Error(errorCodeConverter.getMessage(it.error))) + } + } + } + + @Immutable + sealed interface AuthState { + data object Authorised : AuthState + + data object UnAuthorised : AuthState + } + + @Immutable + sealed interface UiState { + data object Loading : UiState + + data class Error(val message: String) : UiState + + data class Success(val member: MemberEntity) : UiState + } } diff --git a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/memberedit/MemberEditDetailsScreen.kt b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/memberedit/MemberEditDetailsScreen.kt index 60a2e94..b1f27c0 100644 --- a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/memberedit/MemberEditDetailsScreen.kt +++ b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/memberedit/MemberEditDetailsScreen.kt @@ -38,6 +38,7 @@ import org.joda.time.DateTime @Composable fun MemberEditDetailsScreen( + hasAdminRights: Boolean, uiState: State, memberEntity: MemberEntity, onSetString: (String, InputType) -> Unit = { _, _ -> }, @@ -66,7 +67,8 @@ fun MemberEditDetailsScreen( EditTextWidget( hint = stringResource(R.string.txt_first_name), textInput = memberEntity.firstName, - isError = currentInputType == InputType.FIRST_NAME + isError = currentInputType == InputType.FIRST_NAME, + isEnabled = hasAdminRights ) { onSetString.invoke(it, InputType.FIRST_NAME) } @@ -74,7 +76,8 @@ fun MemberEditDetailsScreen( EditTextWidget( hint = stringResource(R.string.txt_last_name), textInput = memberEntity.lastName, - isError = currentInputType == InputType.LAST_NAME + isError = currentInputType == InputType.LAST_NAME, + isEnabled = hasAdminRights ) { onSetString.invoke(it, InputType.LAST_NAME) } @@ -82,7 +85,8 @@ fun MemberEditDetailsScreen( EditTextWidget( hint = stringResource(R.string.txt_apartment_number), textInput = memberEntity.apartmentNumber.orEmpty(), - isError = currentInputType == InputType.APARTMENT_NUMBER + isError = currentInputType == InputType.APARTMENT_NUMBER, + isEnabled = hasAdminRights ) { onSetString.invoke(it, InputType.APARTMENT_NUMBER) } @@ -91,7 +95,8 @@ fun MemberEditDetailsScreen( hint = stringResource(R.string.txt_phone_number), textInput = memberEntity.phoneNumber.toPhoneNumberString(), isError = currentInputType == InputType.PHONE_NUMBER, - keyboardType = KeyboardType.Phone + keyboardType = KeyboardType.Phone, + isEnabled = hasAdminRights ) { onSetString.invoke(it, InputType.PHONE_NUMBER) } @@ -108,6 +113,7 @@ fun MemberEditDetailsScreen( TextRegular(text = stringResource(id = R.string.txt_training_coach_assigned)) Switch( checked = initialHasCoach, + enabled = hasAdminRights, onCheckedChange = { initialHasCoach = it onSetString.invoke(initialHasCoach.toString(), InputType.HAS_COACH) @@ -117,7 +123,7 @@ fun MemberEditDetailsScreen( ButtonWidget( title = stringResource(R.string.txt_update), - isEnabled = uiState.value is UiState.ReadyToUpdate, + isEnabled = uiState.value is UiState.ReadyToUpdate && hasAdminRights, isLoading = uiState.value is UiState.Loading ) { onUpdateTapped.invoke() @@ -132,6 +138,7 @@ fun MemberEditDetailsScreenPreview() { AppThemeComposable { Column(modifier = Modifier.verticalScroll(rememberScrollState())) { MemberEditDetailsScreen( + hasAdminRights = false, memberEntity = getMockRegisteredMemberEntity(), uiState = MutableStateFlow(UiState.Loading) .collectAsState(initial = UiState.Loading), diff --git a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/memberedit/MemberEditScreen.kt b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/memberedit/MemberEditScreen.kt index a61d936..c51f675 100644 --- a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/memberedit/MemberEditScreen.kt +++ b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/memberedit/MemberEditScreen.kt @@ -62,7 +62,7 @@ fun MemberEditScreen( ToolBarWidget( title = stringResource(R.string.txt_edit_member), canNavigateBack = true, - secondaryIcon = Icons.Default.DeleteForever, + secondaryIcon = if (viewModel.hasAdminRights()) Icons.Default.DeleteForever else null, onSecondaryIconPressed = { showDialog = true }, onBackPressed = viewModel::navigateBack ) @@ -84,6 +84,7 @@ fun MemberEditScreen( ) { memberEntity.value?.let { MemberProfileWidget( + isEnabled = viewModel.hasAdminRights(), imageUrl = it.profileImageUrl, onImageSelect = { galleryLauncher.launch("image/*") @@ -94,6 +95,7 @@ fun MemberEditScreen( ) MemberEditDetailsScreen( + hasAdminRights = viewModel.hasAdminRights(), uiState = uiState, memberEntity = it, onSetString = viewModel::setString, diff --git a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/memberedit/MemberEditScreenViewModel.kt b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/memberedit/MemberEditScreenViewModel.kt index cc6956d..fa5cca5 100644 --- a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/memberedit/MemberEditScreenViewModel.kt +++ b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/memberedit/MemberEditScreenViewModel.kt @@ -17,6 +17,7 @@ import com.ndemi.garden.gym.ui.utils.isValidApartmentNumber import com.ndemi.garden.gym.ui.utils.isValidPhoneNumber import cv.domain.DomainResult import cv.domain.entities.MemberEntity +import cv.domain.usecase.AuthUseCase import cv.domain.usecase.MemberUseCase import cv.domain.usecase.StorageUseCase import cv.domain.usecase.UpdateType @@ -25,6 +26,7 @@ import kotlinx.coroutines.launch class MemberEditScreenViewModel( private val converter: ErrorCodeConverter, private val memberUseCase: MemberUseCase, + private val authUseCase: AuthUseCase, private val storageUseCase: StorageUseCase, private val navigationService: NavigationService, ) : BaseViewModel(UiState.Loading) { @@ -169,6 +171,8 @@ class MemberEditScreenViewModel( } } + fun hasAdminRights() = authUseCase.hasAdminRights() + enum class InputType { NONE, FIRST_NAME, diff --git a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/members/MembersListScreen.kt b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/members/MembersListScreen.kt index 71eee13..e758c07 100644 --- a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/members/MembersListScreen.kt +++ b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/members/MembersListScreen.kt @@ -12,6 +12,7 @@ import cv.domain.entities.MemberEntity @Composable fun MembersListScreen( + hasAdminRights: Boolean, members: List, onMemberTapped: (memberEntity: MemberEntity) -> Unit = {}, onPaymentsTapped: (memberEntity: MemberEntity) -> Unit = {}, @@ -23,6 +24,7 @@ fun MembersListScreen( MemberStatusWidget( memberEntity = members[it], showDetails = true, + hasAdminRights = hasAdminRights, onMemberTapped = onMemberTapped, onPaymentsTapped = onPaymentsTapped, onAttendanceTapped = onAttendanceTapped, @@ -37,6 +39,7 @@ fun MembersListScreen( fun MembersListScreenPreview() { AppThemeComposable { MembersListScreen( + hasAdminRights = true, members = listOf( getMockRegisteredMemberEntity(), diff --git a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/members/MembersScreen.kt b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/members/MembersScreen.kt index fa748a0..3338d54 100644 --- a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/members/MembersScreen.kt +++ b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/members/MembersScreen.kt @@ -34,7 +34,7 @@ fun MembersScreen ( Column { ToolBarWidget( title = stringResource(R.string.txt_members), - secondaryIcon = Icons.Default.PersonAdd, + secondaryIcon = if (viewModel.hasAdminRights()) Icons.Default.PersonAdd else null, onSecondaryIconPressed = viewModel::onRegisterMember ) @@ -53,6 +53,7 @@ fun MembersScreen ( TextRegular(text = stringResource(R.string.txt_no_members)) } else { MembersListScreen( + hasAdminRights = viewModel.hasAdminRights(), members = (uiState.value as UiState.Success).members, onMemberTapped = viewModel::onMemberTapped, onPaymentsTapped = viewModel::onPaymentsTapped, diff --git a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/members/MembersScreenViewModel.kt b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/members/MembersScreenViewModel.kt index b189e1c..19df3a8 100644 --- a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/members/MembersScreenViewModel.kt +++ b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/members/MembersScreenViewModel.kt @@ -13,6 +13,7 @@ import com.ndemi.garden.gym.ui.utils.ErrorCodeConverter import cv.domain.DomainError import cv.domain.DomainResult import cv.domain.entities.MemberEntity +import cv.domain.usecase.AuthUseCase import cv.domain.usecase.MemberUseCase import cv.domain.usecase.UpdateType import kotlinx.coroutines.launch @@ -21,6 +22,7 @@ import org.joda.time.DateTime class MembersScreenViewModel ( private val errorCodeConverter: ErrorCodeConverter, private val memberUseCase: MemberUseCase, + private val authUseCase: AuthUseCase, private val navigationService: NavigationService, ) : BaseViewModel(UiState.Loading) { @@ -38,6 +40,8 @@ class MembersScreenViewModel ( } } + fun hasAdminRights() = authUseCase.hasAdminRights() + fun onMemberTapped(memberEntity: MemberEntity) { navigationService.open(Route.MemberEditScreen(memberEntity.id)) } diff --git a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/membersattendances/MembersAttendancesScreen.kt b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/membersattendances/MembersAttendancesScreen.kt index 9d9f8b2..0750155 100644 --- a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/membersattendances/MembersAttendancesScreen.kt +++ b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/membersattendances/MembersAttendancesScreen.kt @@ -67,7 +67,7 @@ fun MembersAttendancesScreen ( } else { AttendanceListScreen( attendances = result.attendances, - canDeleteAttendance = true, + canDeleteAttendance = viewModel.hasAdminRights(), totalMinutes = result.totalMinutes ){ viewModel.deleteAttendance(it) diff --git a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/membersattendances/MembersAttendancesScreenViewModel.kt b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/membersattendances/MembersAttendancesScreenViewModel.kt index 268f351..91135c6 100644 --- a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/membersattendances/MembersAttendancesScreenViewModel.kt +++ b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/membersattendances/MembersAttendancesScreenViewModel.kt @@ -12,6 +12,7 @@ import com.ndemi.garden.gym.ui.utils.ErrorCodeConverter import cv.domain.DomainError import cv.domain.DomainResult import cv.domain.entities.AttendanceEntity +import cv.domain.usecase.AuthUseCase import cv.domain.usecase.MemberUseCase import kotlinx.coroutines.launch import org.joda.time.DateTime @@ -19,6 +20,7 @@ import org.joda.time.DateTime class MembersAttendancesScreenViewModel( private val errorCodeConverter: ErrorCodeConverter, private val membersUseCase: MemberUseCase, + private val authUseCase: AuthUseCase, private val navigationService: NavigationService, ) : BaseViewModel(UiState.Loading) { @@ -68,6 +70,8 @@ class MembersAttendancesScreenViewModel( navigationService.popBack() } + fun hasAdminRights() = authUseCase.hasAdminRights() + @Immutable sealed interface UiState : BaseState { diff --git a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/payments/PaymentsScreen.kt b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/payments/PaymentsScreen.kt index cac0cc1..d2885e1 100644 --- a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/payments/PaymentsScreen.kt +++ b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/payments/PaymentsScreen.kt @@ -53,7 +53,7 @@ fun PaymentsScreen( } else { stringResource(R.string.txt_payments_by, memberName) }, - secondaryIcon = if (memberId.isEmpty()) null else Icons.Default.AddCircle, + secondaryIcon = if (viewModel.hasAdminRights()) Icons.Default.AddCircle else null, onSecondaryIconPressed = { if (canAddPayment.value == true){ viewModel.navigateToPaymentAddScreen() @@ -90,7 +90,7 @@ fun PaymentsScreen( PaymentsListScreen( payments = result.payments, totalAmount = result.totalAmount, - canDeletePayment = memberId.isNotEmpty() + canDeletePayment = viewModel.hasAdminRights() ){ viewModel.deletePayment(it) } diff --git a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/payments/PaymentsScreenViewModel.kt b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/payments/PaymentsScreenViewModel.kt index 44b9278..29eb837 100644 --- a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/payments/PaymentsScreenViewModel.kt +++ b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/payments/PaymentsScreenViewModel.kt @@ -15,6 +15,7 @@ import com.ndemi.garden.gym.ui.utils.ErrorCodeConverter import cv.domain.DomainError import cv.domain.DomainResult import cv.domain.entities.PaymentEntity +import cv.domain.usecase.AuthUseCase import cv.domain.usecase.MemberUseCase import kotlinx.coroutines.launch import org.joda.time.DateTime @@ -22,6 +23,7 @@ import org.joda.time.DateTime class PaymentsScreenViewModel( private val errorCodeConverter: ErrorCodeConverter, private val membersUseCase: MemberUseCase, + private val authUseCase: AuthUseCase, private val navigationService: NavigationService, ) : BaseViewModel(UiState.Loading) { @@ -80,6 +82,8 @@ class PaymentsScreenViewModel( navigationService.open(Route.PaymentAddScreen(memberId)) } + fun hasAdminRights() = authUseCase.hasAdminRights() + @Immutable sealed interface UiState : BaseState { diff --git a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/profile/ProfileDetailsScreen.kt b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/profile/ProfileDetailsScreen.kt index b9dcb64..425e2b0 100644 --- a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/profile/ProfileDetailsScreen.kt +++ b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/profile/ProfileDetailsScreen.kt @@ -6,12 +6,9 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import com.ndemi.garden.gym.R import com.ndemi.garden.gym.ui.mock.getMockRegisteredMemberEntity import com.ndemi.garden.gym.ui.theme.AppThemeComposable import com.ndemi.garden.gym.ui.utils.AppPreview -import com.ndemi.garden.gym.ui.widgets.ButtonWidget import com.ndemi.garden.gym.ui.widgets.member.MemberInfoWidget import com.ndemi.garden.gym.ui.widgets.member.MemberSessionWidget import cv.domain.entities.MemberEntity @@ -25,7 +22,6 @@ fun ProfileDetailsScreen( sessionStartTime: DateTime? = null, onSessionStarted: () -> Unit = {}, onSessionCompleted: (DateTime, DateTime) -> Unit = { _, _ -> }, - onRegisterMember: () -> Unit = {} ) { Column( modifier = Modifier @@ -43,14 +39,6 @@ fun ProfileDetailsScreen( onSessionCompleted = onSessionCompleted, ) } - - if (isAdmin){ - ButtonWidget( - title = stringResource(R.string.txt_register_new_member), - ) { - onRegisterMember.invoke() - } - } } } @@ -60,7 +48,7 @@ fun ProfileDetailsScreenPreview() { AppThemeComposable { ProfileDetailsScreen( memberEntity = getMockRegisteredMemberEntity(), - isAdmin = true + isAdmin = false ) } } diff --git a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/profile/ProfileScreen.kt b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/profile/ProfileScreen.kt index c5303b3..246f240 100644 --- a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/profile/ProfileScreen.kt +++ b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/profile/ProfileScreen.kt @@ -77,7 +77,6 @@ fun ProfileScreen( sessionStartTime = sessionStartTime, onSessionStarted = viewModel::setStartedSession, onSessionCompleted = viewModel::setAttendance, - onRegisterMember = viewModel::onRegisterMember, ) ButtonWidget( title = stringResource(R.string.txt_logout), @@ -89,4 +88,3 @@ fun ProfileScreen( } } } - diff --git a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/profile/ProfileScreenViewModel.kt b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/profile/ProfileScreenViewModel.kt index cd212a6..9f21ae9 100644 --- a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/profile/ProfileScreenViewModel.kt +++ b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/profile/ProfileScreenViewModel.kt @@ -110,17 +110,13 @@ class ProfileScreenViewModel( } } - fun isAdmin() = authUseCase.isAdmin() + fun isAdmin() = authUseCase.isNotMember() fun onLogOutTapped() { authUseCase.logOut() navigationService.open(Route.LoginScreen, true) } - fun onRegisterMember() { - navigationService.open(Route.RegisterNewScreen) - } - fun deleteMemberImage() { sendAction(Action.SetLoading) viewModelScope.launch { diff --git a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/register/RegisterScreen.kt b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/register/RegisterScreen.kt index b933bac..dd4a398 100644 --- a/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/register/RegisterScreen.kt +++ b/app/src/main/kotlin/com/ndemi/garden/gym/ui/screens/register/RegisterScreen.kt @@ -17,9 +17,6 @@ fun RegisterScreen( val uiState = viewModel.uiStateFlow.collectAsState( initial = UiState.Waiting ) - if (uiState.value is UiState.Success) { - viewModel.navigateLogInSuccess() - } Column { ToolBarWidget(title = stringResource(id = R.string.txt_register)) diff --git a/app/src/main/kotlin/com/ndemi/garden/gym/ui/widgets/LoadingScreenWidget.kt b/app/src/main/kotlin/com/ndemi/garden/gym/ui/widgets/LoadingScreenWidget.kt new file mode 100644 index 0000000..4956f94 --- /dev/null +++ b/app/src/main/kotlin/com/ndemi/garden/gym/ui/widgets/LoadingScreenWidget.kt @@ -0,0 +1,44 @@ +package com.ndemi.garden.gym.ui.widgets + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import com.ndemi.garden.gym.ui.theme.AppThemeComposable +import com.ndemi.garden.gym.ui.theme.icon_image_size +import com.ndemi.garden.gym.ui.theme.padding_screen +import com.ndemi.garden.gym.ui.utils.AppPreview + +@Composable +fun LoadingScreenWidget() { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + CircularProgressIndicator( + modifier = Modifier + .width(icon_image_size) + .height(icon_image_size) + ) + TextRegular( + modifier = Modifier.padding(top = padding_screen), + text = "Loading... Please wait..." + ) + } +} + + +@AppPreview +@Composable +fun LoadingScreenWidgetPreview() { + AppThemeComposable { + LoadingScreenWidget() + } +} diff --git a/app/src/main/kotlin/com/ndemi/garden/gym/ui/widgets/member/MemberProfileWidget.kt b/app/src/main/kotlin/com/ndemi/garden/gym/ui/widgets/member/MemberProfileWidget.kt index 4e4d02c..acce137 100644 --- a/app/src/main/kotlin/com/ndemi/garden/gym/ui/widgets/member/MemberProfileWidget.kt +++ b/app/src/main/kotlin/com/ndemi/garden/gym/ui/widgets/member/MemberProfileWidget.kt @@ -34,6 +34,7 @@ import com.ndemi.garden.gym.ui.utils.AppPreview @Composable fun MemberProfileWidget( + isEnabled: Boolean = true, imageUrl: String, onImageSelect: () -> Unit = {}, onImageDelete: () -> Unit = {}, @@ -51,25 +52,27 @@ fun MemberProfileWidget( .width(icon_image_size_profile), horizontalArrangement = Arrangement.SpaceBetween ) { - Image( - imageVector = Icons.Rounded.CameraAlt, - contentDescription = "Select Picture", - contentScale = ContentScale.Inside, - modifier = Modifier - .clip(RoundedCornerShape(border_radius)) - .border( - border = BorderStroke(line_thickness, AppTheme.colors.highLight), - shape = RoundedCornerShape(border_radius) - ) - .background(AppTheme.colors.highLight) - .width(icon_image_size_large) - .height(icon_image_size_large) - .clickable { - onImageSelect.invoke() - } - ) + if (isEnabled) { + Image( + imageVector = Icons.Rounded.CameraAlt, + contentDescription = "Select Picture", + contentScale = ContentScale.Inside, + modifier = Modifier + .clip(RoundedCornerShape(border_radius)) + .border( + border = BorderStroke(line_thickness, AppTheme.colors.highLight), + shape = RoundedCornerShape(border_radius) + ) + .background(AppTheme.colors.highLight) + .width(icon_image_size_large) + .height(icon_image_size_large) + .clickable { + onImageSelect.invoke() + } + ) + } - if (imageUrl.isNotEmpty()) { + if (imageUrl.isNotEmpty() && isEnabled) { Image( imageVector = Icons.Rounded.DeleteForever, contentDescription = "Delete Picture", diff --git a/app/src/main/kotlin/com/ndemi/garden/gym/ui/widgets/member/MemberStatusWidget.kt b/app/src/main/kotlin/com/ndemi/garden/gym/ui/widgets/member/MemberStatusWidget.kt index 8f04a9f..d9e1937 100644 --- a/app/src/main/kotlin/com/ndemi/garden/gym/ui/widgets/member/MemberStatusWidget.kt +++ b/app/src/main/kotlin/com/ndemi/garden/gym/ui/widgets/member/MemberStatusWidget.kt @@ -50,6 +50,7 @@ fun MemberStatusWidget( modifier: Modifier = Modifier, memberEntity: MemberEntity, showDetails: Boolean = false, + hasAdminRights: Boolean = false, onMemberTapped: (memberEntity: MemberEntity) -> Unit = {}, onPaymentsTapped: (memberEntity: MemberEntity) -> Unit = {}, onAttendanceTapped: (memberEntity: MemberEntity) -> Unit = {}, @@ -193,20 +194,22 @@ fun MemberStatusWidget( ButtonOutlineWidget(text = stringResource(R.string.txt_attendance)) { onAttendanceTapped.invoke(memberEntity) } - ButtonOutlineWidget( - text = if (memberEntity.isActiveNow()) { - stringResource(R.string.txt_end_session) - } else { - stringResource(R.string.txt_start_session) - }, - hasOutline = !memberEntity.isActiveNow(), - backgroundColor = if (memberEntity.isActiveNow()) { - AppTheme.colors.backgroundError - } else { - Color.Transparent + if (hasAdminRights){ + ButtonOutlineWidget( + text = if (memberEntity.isActiveNow()) { + stringResource(R.string.txt_end_session) + } else { + stringResource(R.string.txt_start_session) + }, + hasOutline = !memberEntity.isActiveNow(), + backgroundColor = if (memberEntity.isActiveNow()) { + AppTheme.colors.backgroundError + } else { + Color.Transparent + } + ) { + onSessionTapped.invoke(memberEntity) } - ) { - onSessionTapped.invoke(memberEntity) } } } diff --git a/data/build.gradle.kts b/data/build.gradle.kts index 1036ed7..152fe7e 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -27,7 +27,7 @@ android { detekt { toolVersion = libs.versions.detekt.get() config.setFrom(rootProject.file("detekt.yml")) - buildUponDefaultConfig = true + buildUponDefaultConfig = false } } diff --git a/data/src/main/kotlin/cv/data/mappers/MemberMapper.kt b/data/src/main/kotlin/cv/data/mappers/MemberMapper.kt index 7e98d27..b623d97 100644 --- a/data/src/main/kotlin/cv/data/mappers/MemberMapper.kt +++ b/data/src/main/kotlin/cv/data/mappers/MemberMapper.kt @@ -3,6 +3,7 @@ package cv.data.mappers import com.google.firebase.Timestamp import cv.data.models.MemberModel import cv.domain.entities.MemberEntity +import cv.domain.entities.MemberType import org.joda.time.DateTime import java.util.Date @@ -19,6 +20,7 @@ fun MemberEntity.toMemberModel() = MemberModel( hasCoach = hasCoach, amountDue = amountDue, phoneNumber = phoneNumber, + memberType = memberType.name ) @@ -35,8 +37,16 @@ fun MemberModel.toMemberEntity() = MemberEntity( hasCoach = hasCoach, amountDue = amountDue, phoneNumber = phoneNumber, + memberType = memberType.toMemberType() ) +private fun String.toMemberType(): MemberType = + when (this) { + "ADMIN" -> MemberType.ADMIN + "SUPERVISOR" -> MemberType.SUPERVISOR + else -> MemberType.MEMBER + } + private fun isAfterNow(millis: Long?): Long? { if (millis == null) return null if (DateTime(millis).isBefore(DateTime.now())) return null diff --git a/data/src/main/kotlin/cv/data/models/MemberModel.kt b/data/src/main/kotlin/cv/data/models/MemberModel.kt index 1e9a698..9b05c0e 100644 --- a/data/src/main/kotlin/cv/data/models/MemberModel.kt +++ b/data/src/main/kotlin/cv/data/models/MemberModel.kt @@ -17,5 +17,6 @@ data class MemberModel( val profileImageUrl: String? = null, val hasCoach: Boolean = false, val amountDue: Double = 0.0, - val phoneNumber: String = "" + val phoneNumber: String = "", + val memberType: String = "", ) diff --git a/data/src/main/kotlin/cv/data/repository/AuthRepositoryImp.kt b/data/src/main/kotlin/cv/data/repository/AuthRepositoryImp.kt index 67f9653..15aa44d 100644 --- a/data/src/main/kotlin/cv/data/repository/AuthRepositoryImp.kt +++ b/data/src/main/kotlin/cv/data/repository/AuthRepositoryImp.kt @@ -1,22 +1,37 @@ package cv.data.repository import com.google.firebase.auth.ktx.auth +import com.google.firebase.firestore.ktx.firestore +import com.google.firebase.firestore.toObject import com.google.firebase.ktx.Firebase +import cv.data.mappers.toMemberEntity +import cv.data.models.MemberModel import cv.data.retrofit.toDomainError import cv.domain.DomainError import cv.domain.DomainResult +import cv.domain.entities.MemberEntity +import cv.domain.entities.MemberType import cv.domain.repositories.AppLogLevel import cv.domain.repositories.AppLoggerRepository import cv.domain.repositories.AuthRepository +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow class AuthRepositoryImp( - private val adminEmails: List, + private val pathUser: String, private val logger: AppLoggerRepository, ) : AuthRepository { + private lateinit var memberEntity: MemberEntity + override fun isAuthenticated() = Firebase.auth.currentUser != null - override fun isAdmin() = adminEmails.contains(Firebase.auth.currentUser?.email) + override fun getMemberType() = if (this::memberEntity.isInitialized) { + memberEntity.memberType + } else { + MemberType.MEMBER + } override fun logOut() = Firebase.auth.signOut() @@ -57,4 +72,45 @@ class AuthRepositoryImp( callback.invoke(DomainResult.Error(it.toDomainError())) } } + + override suspend fun getAuthState(): Flow> = callbackFlow { + Firebase.auth.addAuthStateListener { + if(it.uid.isNullOrEmpty()){ + trySend(DomainResult.Error(DomainError.UNAUTHORISED)).isSuccess + } else { + trySend(DomainResult.Success(Unit)).isSuccess + } + } + awaitClose { } + } + + + override suspend fun getLoggedInUser(): Flow> = callbackFlow { + Firebase.auth.currentUser?.uid?.let { + val eventDocument = Firebase.firestore.collection(pathUser).document(it) + + val subscription = eventDocument.addSnapshotListener { snapshot, error -> + snapshot?.let { response -> + logger.log("Data received: $response") + val memberModel = response.toObject() + memberModel?.let { + memberEntity = memberModel.toMemberEntity() + trySend(DomainResult.Success(memberEntity)).isSuccess + } ?: run { + trySend(DomainResult.Error(DomainError.UNAUTHORISED)).isSuccess + } + } + error?.let { + logger.log("Exception: $error", AppLogLevel.ERROR) + trySend(DomainResult.Error(error.toDomainError())).isSuccess + } + } + + awaitClose{subscription.remove()} + }.run { + logger.log("Member UID is null or empty", AppLogLevel.ERROR) + trySend(DomainResult.Error(DomainError.UNAUTHORISED)).isSuccess + awaitClose { } + } + } } diff --git a/data/src/main/kotlin/cv/data/repository/MemberRepositoryImp.kt b/data/src/main/kotlin/cv/data/repository/MemberRepositoryImp.kt index 6bb6c74..0453156 100644 --- a/data/src/main/kotlin/cv/data/repository/MemberRepositoryImp.kt +++ b/data/src/main/kotlin/cv/data/repository/MemberRepositoryImp.kt @@ -20,6 +20,7 @@ import cv.domain.DomainError import cv.domain.DomainResult import cv.domain.entities.AttendanceEntity import cv.domain.entities.MemberEntity +import cv.domain.entities.MemberType import cv.domain.entities.PaymentEntity import cv.domain.repositories.AppLogLevel import cv.domain.repositories.AppLoggerRepository @@ -93,27 +94,19 @@ class MemberRepositoryImp( .addOnSuccessListener { document -> logger.log("Data received: $document") val response = document.toObjects() - if (isLive) { - completable.complete(DomainResult.Success( - response.filter { - it.activeNowDate != null - }.map { - it.toMemberEntity() - }.sortedByDescending { - it.registrationDateMillis - }) - ) - } else { - completable.complete(DomainResult.Success( - response.filter { - it.id != Firebase.auth.currentUser?.uid - }.map { - it.toMemberEntity() - }.sortedByDescending { - it.registrationDateMillis - }) - ) - } + completable.complete(DomainResult.Success( + response.map { + it.toMemberEntity() + }.filter { + if (isLive) { + it.activeNowDateMillis != null + } else { + it.memberType == MemberType.MEMBER + } + }.sortedByDescending { + it.registrationDateMillis + }) + ) }.addOnFailureListener { logger.log("Exception: $it", AppLogLevel.ERROR) diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index 4f84605..634f395 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -9,6 +9,12 @@ java { targetCompatibility = JavaVersion.VERSION_17 } +detekt { + toolVersion = libs.versions.detekt.get() + config.setFrom(rootProject.file("detekt.yml")) + buildUponDefaultConfig = false +} + dependencies { // Coroutines implementation(libs.kotlinx.coroutines.android) diff --git a/domain/src/main/kotlin/cv/domain/entities/MemberEntity.kt b/domain/src/main/kotlin/cv/domain/entities/MemberEntity.kt index ecd40dc..3b668cf 100644 --- a/domain/src/main/kotlin/cv/domain/entities/MemberEntity.kt +++ b/domain/src/main/kotlin/cv/domain/entities/MemberEntity.kt @@ -13,6 +13,7 @@ data class MemberEntity( val hasCoach: Boolean = false, val amountDue: Double = 0.0, val phoneNumber: String = "", + val memberType: MemberType = MemberType.MEMBER, ) { fun getFullName(): String = "$firstName $lastName" @@ -37,3 +38,9 @@ data class MemberEntity( memberEntity.hasCoach != hasCoach } } + +enum class MemberType { + ADMIN, + SUPERVISOR, + MEMBER, +} diff --git a/domain/src/main/kotlin/cv/domain/repositories/AuthRepository.kt b/domain/src/main/kotlin/cv/domain/repositories/AuthRepository.kt index 7b27054..972d2d2 100644 --- a/domain/src/main/kotlin/cv/domain/repositories/AuthRepository.kt +++ b/domain/src/main/kotlin/cv/domain/repositories/AuthRepository.kt @@ -1,11 +1,14 @@ package cv.domain.repositories import cv.domain.DomainResult +import cv.domain.entities.MemberEntity +import cv.domain.entities.MemberType +import kotlinx.coroutines.flow.Flow interface AuthRepository { fun isAuthenticated(): Boolean - fun isAdmin(): Boolean + fun getMemberType(): MemberType fun logOut() @@ -14,4 +17,8 @@ interface AuthRepository { fun login(email: String, password: String, callback: (DomainResult) -> Unit) fun resetPasswordForEmail(email: String, callback: (DomainResult) -> Unit) + + suspend fun getLoggedInUser(): Flow> + + suspend fun getAuthState(): Flow> } diff --git a/domain/src/main/kotlin/cv/domain/usecase/AuthUseCase.kt b/domain/src/main/kotlin/cv/domain/usecase/AuthUseCase.kt index 1b9b5eb..77d197d 100644 --- a/domain/src/main/kotlin/cv/domain/usecase/AuthUseCase.kt +++ b/domain/src/main/kotlin/cv/domain/usecase/AuthUseCase.kt @@ -1,6 +1,7 @@ package cv.domain.usecase import cv.domain.DomainResult +import cv.domain.entities.MemberType import cv.domain.repositories.AuthRepository class AuthUseCase( @@ -15,9 +16,16 @@ class AuthUseCase( fun resetPasswordForEmail(email: String, callback: (DomainResult) -> Unit) = authRepository.resetPasswordForEmail(email, callback) + fun logOut() = authRepository.logOut() + + suspend fun getLoggedInUser() = authRepository.getLoggedInUser() + + suspend fun getAuthState() = authRepository.getAuthState() + fun isAuthenticated() = authRepository.isAuthenticated() - fun isAdmin() = authRepository.isAdmin() + fun isNotMember() = authRepository.getMemberType() != MemberType.MEMBER + + fun hasAdminRights() = authRepository.getMemberType() == MemberType.ADMIN - fun logOut() = authRepository.logOut() } diff --git a/domain/src/main/kotlin/cv/domain/usecase/MemberUseCase.kt b/domain/src/main/kotlin/cv/domain/usecase/MemberUseCase.kt index a8fa883..5eca00c 100644 --- a/domain/src/main/kotlin/cv/domain/usecase/MemberUseCase.kt +++ b/domain/src/main/kotlin/cv/domain/usecase/MemberUseCase.kt @@ -150,13 +150,6 @@ class MemberUseCase( return memberRepository.deleteMember(memberEntity) } - suspend fun getAllPaymentPlans(year: Int) = - memberRepository.getPayments( - isMembersPayment = false, - memberId = "", - year = year - ) - suspend fun getPaymentPlanForMember(memberId: String, year: Int) = memberRepository.getPayments( isMembersPayment = true, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 02684a5..fc685f3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,8 +1,8 @@ [versions] appName = "Ndemi Garden Gym" appNamespaceId = "com.ndemi.garden.gym" -appVersionCode = "110" -appVersionName = "11.0" +appVersionCode = "120" +appVersionName = "12.0" appCompileSdk = "34" appTargetSdk = "34" appMinSdk = "24"