From b87f46da769afa8a14074c8cb0b8b04b1c6e31a0 Mon Sep 17 00:00:00 2001 From: Daocon <134820492+Daocon@users.noreply.github.com> Date: Sat, 30 Nov 2024 13:53:14 +0700 Subject: [PATCH] =?UTF-8?q?feat(invite-to-class):=20g=E1=BB=8Di=20api=20m?= =?UTF-8?q?=E1=BB=9Di=20th=C3=A0nh=20vi=C3=AAn=20v=C3=B4=20l=E1=BB=9Bp=20h?= =?UTF-8?q?=E1=BB=8Dc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/classes/InviteToClassRequestDto.kt | 10 ++ .../dto/classes/InviteToClassResponseDto.kt | 10 ++ .../classes/InviteToClassRequestMapper.kt | 14 +++ .../classes/InviteToClassResponseMapper.kt | 14 +++ .../pwhs/quickmem/data/remote/ApiService.kt | 8 ++ .../remote/repository/ClassRepositoryImpl.kt | 20 ++++ .../classes/InviteToClassRequestModel.kt | 6 ++ .../classes/InviteToClassResponseModel.kt | 6 ++ .../domain/repository/ClassRepository.kt | 7 ++ .../app/classes/detail/ClassDetailScreen.kt | 44 ++++++++- .../app/classes/detail/ClassDetailUiAction.kt | 2 + .../app/classes/detail/ClassDetailUiEvent.kt | 1 + .../app/classes/detail/ClassDetailUiState.kt | 3 + .../classes/detail/ClassDetailViewModel.kt | 88 +++++++++++++++++ .../component/ClassDetailBottomSheet.kt | 16 ++-- .../detail/component/ClassTextField.kt | 54 +++++++++++ .../component/InviteClassBottomSheet.kt | 96 +++++++++++++++++++ 17 files changed, 391 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/com/pwhs/quickmem/data/dto/classes/InviteToClassRequestDto.kt create mode 100644 app/src/main/java/com/pwhs/quickmem/data/dto/classes/InviteToClassResponseDto.kt create mode 100644 app/src/main/java/com/pwhs/quickmem/data/mapper/classes/InviteToClassRequestMapper.kt create mode 100644 app/src/main/java/com/pwhs/quickmem/data/mapper/classes/InviteToClassResponseMapper.kt create mode 100644 app/src/main/java/com/pwhs/quickmem/domain/model/classes/InviteToClassRequestModel.kt create mode 100644 app/src/main/java/com/pwhs/quickmem/domain/model/classes/InviteToClassResponseModel.kt create mode 100644 app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/component/ClassTextField.kt create mode 100644 app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/component/InviteClassBottomSheet.kt diff --git a/app/src/main/java/com/pwhs/quickmem/data/dto/classes/InviteToClassRequestDto.kt b/app/src/main/java/com/pwhs/quickmem/data/dto/classes/InviteToClassRequestDto.kt new file mode 100644 index 00000000..9bbe236a --- /dev/null +++ b/app/src/main/java/com/pwhs/quickmem/data/dto/classes/InviteToClassRequestDto.kt @@ -0,0 +1,10 @@ +package com.pwhs.quickmem.data.dto.classes + +import com.google.gson.annotations.SerializedName + +data class InviteToClassRequestDto( + @SerializedName("classId") + val classId: String, + @SerializedName("username") + val username: String +) \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/data/dto/classes/InviteToClassResponseDto.kt b/app/src/main/java/com/pwhs/quickmem/data/dto/classes/InviteToClassResponseDto.kt new file mode 100644 index 00000000..3072a29c --- /dev/null +++ b/app/src/main/java/com/pwhs/quickmem/data/dto/classes/InviteToClassResponseDto.kt @@ -0,0 +1,10 @@ +package com.pwhs.quickmem.data.dto.classes + +import com.google.gson.annotations.SerializedName + +data class InviteToClassResponseDto( + @SerializedName("message") + val message: String, + @SerializedName("status") + val status: Boolean, +) diff --git a/app/src/main/java/com/pwhs/quickmem/data/mapper/classes/InviteToClassRequestMapper.kt b/app/src/main/java/com/pwhs/quickmem/data/mapper/classes/InviteToClassRequestMapper.kt new file mode 100644 index 00000000..52d42937 --- /dev/null +++ b/app/src/main/java/com/pwhs/quickmem/data/mapper/classes/InviteToClassRequestMapper.kt @@ -0,0 +1,14 @@ +package com.pwhs.quickmem.data.mapper.classes + +import com.pwhs.quickmem.data.dto.classes.InviteToClassRequestDto +import com.pwhs.quickmem.domain.model.classes.InviteToClassRequestModel + +fun InviteToClassRequestModel.toDto() = InviteToClassRequestDto( + classId = classId, + username = username +) + +fun InviteToClassRequestDto.toModel() = InviteToClassRequestModel( + classId = classId, + username = username +) \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/data/mapper/classes/InviteToClassResponseMapper.kt b/app/src/main/java/com/pwhs/quickmem/data/mapper/classes/InviteToClassResponseMapper.kt new file mode 100644 index 00000000..9a45c37f --- /dev/null +++ b/app/src/main/java/com/pwhs/quickmem/data/mapper/classes/InviteToClassResponseMapper.kt @@ -0,0 +1,14 @@ +package com.pwhs.quickmem.data.mapper.classes + +import com.pwhs.quickmem.data.dto.classes.InviteToClassResponseDto +import com.pwhs.quickmem.domain.model.classes.InviteToClassResponseModel + +fun InviteToClassResponseDto.toModel() = InviteToClassResponseModel( + message = message, + status = status +) + +fun InviteToClassResponseModel.toDto() = InviteToClassResponseDto( + message = message, + status = status +) \ 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 1c214eda..8a1b4b4b 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 @@ -34,6 +34,8 @@ import com.pwhs.quickmem.data.dto.classes.DeleteStudySetsRequestDto import com.pwhs.quickmem.data.dto.classes.ExitClassRequestDto import com.pwhs.quickmem.data.dto.classes.GetClassByOwnerResponseDto import com.pwhs.quickmem.data.dto.classes.GetClassDetailResponseDto +import com.pwhs.quickmem.data.dto.classes.InviteToClassRequestDto +import com.pwhs.quickmem.data.dto.classes.InviteToClassResponseDto import com.pwhs.quickmem.data.dto.classes.JoinClassRequestDto import com.pwhs.quickmem.data.dto.classes.RemoveMembersRequestDto import com.pwhs.quickmem.data.dto.classes.SaveRecentAccessClassRequestDto @@ -549,6 +551,12 @@ interface ApiService { @Path("userId") userId: String ): List + @POST("class/invite") + suspend fun inviteUserToClass( + @Header("Authorization") token: String, + @Body inviteToClassRequestDto: InviteToClassRequestDto + ): InviteToClassResponseDto + // Streak @GET("streak/{userId}") suspend fun getStreaksByUserId( diff --git a/app/src/main/java/com/pwhs/quickmem/data/remote/repository/ClassRepositoryImpl.kt b/app/src/main/java/com/pwhs/quickmem/data/remote/repository/ClassRepositoryImpl.kt index ebf8bfda..293e3999 100644 --- a/app/src/main/java/com/pwhs/quickmem/data/remote/repository/ClassRepositoryImpl.kt +++ b/app/src/main/java/com/pwhs/quickmem/data/remote/repository/ClassRepositoryImpl.kt @@ -16,6 +16,8 @@ import com.pwhs.quickmem.domain.model.classes.DeleteStudySetsRequestModel import com.pwhs.quickmem.domain.model.classes.ExitClassRequestModel import com.pwhs.quickmem.domain.model.classes.GetClassByOwnerResponseModel import com.pwhs.quickmem.domain.model.classes.GetClassDetailResponseModel +import com.pwhs.quickmem.domain.model.classes.InviteToClassRequestModel +import com.pwhs.quickmem.domain.model.classes.InviteToClassResponseModel import com.pwhs.quickmem.domain.model.classes.JoinClassRequestModel import com.pwhs.quickmem.domain.model.classes.RemoveMembersRequestModel import com.pwhs.quickmem.domain.model.classes.SaveRecentAccessClassRequestModel @@ -279,4 +281,22 @@ class ClassRepositoryImpl @Inject constructor( } } } + + override suspend fun inviteToClass( + token: String, + inviteToClassRequestModel: InviteToClassRequestModel + ): Flow> { + return flow { + emit(Resources.Loading()) + try { + val response = apiService.inviteUserToClass( + token, inviteToClassRequestModel.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/classes/InviteToClassRequestModel.kt b/app/src/main/java/com/pwhs/quickmem/domain/model/classes/InviteToClassRequestModel.kt new file mode 100644 index 00000000..bdfed493 --- /dev/null +++ b/app/src/main/java/com/pwhs/quickmem/domain/model/classes/InviteToClassRequestModel.kt @@ -0,0 +1,6 @@ +package com.pwhs.quickmem.domain.model.classes + +data class InviteToClassRequestModel( + val classId: String, + val username: String +) diff --git a/app/src/main/java/com/pwhs/quickmem/domain/model/classes/InviteToClassResponseModel.kt b/app/src/main/java/com/pwhs/quickmem/domain/model/classes/InviteToClassResponseModel.kt new file mode 100644 index 00000000..7ceca2cb --- /dev/null +++ b/app/src/main/java/com/pwhs/quickmem/domain/model/classes/InviteToClassResponseModel.kt @@ -0,0 +1,6 @@ +package com.pwhs.quickmem.domain.model.classes + +data class InviteToClassResponseModel( + val message: String, + val status: Boolean, +) diff --git a/app/src/main/java/com/pwhs/quickmem/domain/repository/ClassRepository.kt b/app/src/main/java/com/pwhs/quickmem/domain/repository/ClassRepository.kt index 869f8a30..1ce12305 100644 --- a/app/src/main/java/com/pwhs/quickmem/domain/repository/ClassRepository.kt +++ b/app/src/main/java/com/pwhs/quickmem/domain/repository/ClassRepository.kt @@ -9,6 +9,8 @@ import com.pwhs.quickmem.domain.model.classes.DeleteStudySetsRequestModel import com.pwhs.quickmem.domain.model.classes.ExitClassRequestModel import com.pwhs.quickmem.domain.model.classes.GetClassByOwnerResponseModel import com.pwhs.quickmem.domain.model.classes.GetClassDetailResponseModel +import com.pwhs.quickmem.domain.model.classes.InviteToClassRequestModel +import com.pwhs.quickmem.domain.model.classes.InviteToClassResponseModel import com.pwhs.quickmem.domain.model.classes.JoinClassRequestModel import com.pwhs.quickmem.domain.model.classes.RemoveMembersRequestModel import com.pwhs.quickmem.domain.model.classes.SaveRecentAccessClassRequestModel @@ -91,4 +93,9 @@ interface ClassRepository { token: String, userId: String ): Flow>> + + suspend fun inviteToClass( + token: String, + inviteToClassRequestModel: InviteToClassRequestModel + ): Flow> } \ 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 11882376..9a339d74 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 @@ -40,6 +40,7 @@ import com.pwhs.quickmem.domain.model.users.ClassMemberModel import com.pwhs.quickmem.domain.model.users.UserResponseModel import com.pwhs.quickmem.presentation.app.classes.detail.component.ClassDetailBottomSheet import com.pwhs.quickmem.presentation.app.classes.detail.component.ClassDetailTopAppBar +import com.pwhs.quickmem.presentation.app.classes.detail.component.InviteClassBottomSheet import com.pwhs.quickmem.presentation.app.classes.detail.folders.FoldersTabScreen import com.pwhs.quickmem.presentation.app.classes.detail.members.MembersTabScreen import com.pwhs.quickmem.presentation.app.classes.detail.study_sets.StudySetsTabScreen @@ -204,6 +205,10 @@ fun ClassDetailScreen( ClassDetailUiEvent.OnNavigateToRemoveMembers -> { } + + ClassDetailUiEvent.InviteToClassSuccess -> { + Toast.makeText(context, "Invite to class success", Toast.LENGTH_SHORT).show() + } } } } @@ -220,6 +225,11 @@ fun ClassDetailScreen( resultNavigator.navigateBack(true) }, title = uiState.title, + username = uiState.username, + errorMessage = uiState.errorMessage, + onUsernameChanged = { + viewModel.onEvent(ClassDetailUiAction.OnChangeUsername(it)) + }, isLoading = uiState.isLoading, isAllowMember = uiState.allowMember, userResponseModel = uiState.userResponseModel, @@ -268,6 +278,9 @@ fun ClassDetailScreen( onJoinClass = { viewModel.onEvent(ClassDetailUiAction.OnJoinClass) }, + onInviteClass = { + viewModel.onEvent(ClassDetailUiAction.OnInviteClass) + }, onReportClass = { navigator.navigate( ReportScreenDestination( @@ -292,6 +305,9 @@ fun ClassDetail( modifier: Modifier = Modifier, isOwner: Boolean, title: String = "", + username: String = "", + onUsernameChanged: (String) -> Unit = {}, + errorMessage: String = "", isLoading: Boolean = false, isMember: Boolean = false, isAllowMember: Boolean = false, @@ -307,6 +323,7 @@ fun ClassDetail( onEditClass: () -> Unit = {}, onExitClass: () -> Unit = {}, onJoinClass: () -> Unit = {}, + onInviteClass: () -> Unit = {}, onRemoveMembers: (String) -> Unit = {}, onDeleteClass: () -> Unit = {}, onRefresh: () -> Unit = {}, @@ -324,6 +341,7 @@ fun ClassDetail( val sheetShowMoreState = rememberModalBottomSheetState() var showDeleteConfirmationDialog by remember { mutableStateOf(false) } var showExitConfirmationDialog by remember { mutableStateOf(false) } + var showInviteClassBottomSheet by remember { mutableStateOf(false) } val context = LocalContext.current Scaffold( @@ -456,6 +474,27 @@ fun ClassDetail( dismissButtonTitle = "Cancel", ) } + + if (showInviteClassBottomSheet) { + InviteClassBottomSheet( + username = username, + errorMessage = errorMessage, + onUsernameChanged = onUsernameChanged, + showInviteClassBottomSheet = showInviteClassBottomSheet, + sheetShowMoreState = sheetShowMoreState, + onDismissRequest = { + showInviteClassBottomSheet = false + onUsernameChanged("") + }, + onSubmitClick = { + onInviteClass() +// if (!isLoading && errorMessage.isEmpty()) { +// showInviteClassBottomSheet = false +// } + } + ) + } + ClassDetailBottomSheet( onAddStudySetToClass = onNavigateAddStudySets, onAddFolderToClass = onNavigateAddFolder, @@ -468,7 +507,10 @@ fun ClassDetail( showExitConfirmationDialog = true showMoreBottomSheet = false }, - onShareClass = {}, + onInviteClass = { + showInviteClassBottomSheet = true + showMoreBottomSheet = false + }, onReportClass = { onReportClass() showMoreBottomSheet = false diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/ClassDetailUiAction.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/ClassDetailUiAction.kt index 45857169..5e4c56e9 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/ClassDetailUiAction.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/ClassDetailUiAction.kt @@ -12,5 +12,7 @@ sealed class ClassDetailUiAction { data object OnNavigateToAddFolder : ClassDetailUiAction() data object OnNavigateToAddStudySets : ClassDetailUiAction() data object OnJoinClass : ClassDetailUiAction() + data class OnChangeUsername(val username: String) : ClassDetailUiAction() data object ExitClass : ClassDetailUiAction() + data object OnInviteClass : ClassDetailUiAction() } \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/ClassDetailUiEvent.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/ClassDetailUiEvent.kt index 441e989b..195ca1ca 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/ClassDetailUiEvent.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/ClassDetailUiEvent.kt @@ -10,4 +10,5 @@ sealed class ClassDetailUiEvent { data object ClassDeleted : ClassDetailUiEvent() data object NavigateToEditClass : ClassDetailUiEvent() data object ExitClass : ClassDetailUiEvent() + data object InviteToClassSuccess : ClassDetailUiEvent() } \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/ClassDetailUiState.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/ClassDetailUiState.kt index 3eb3ab29..525f381a 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/ClassDetailUiState.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/ClassDetailUiState.kt @@ -16,6 +16,9 @@ data class ClassDetailUiState( val allowSet: Boolean = false, val allowMember: Boolean = false, val isMember: Boolean = false, + val username: String = "", + val errorMessage: String = "", + val statusInvite : Boolean = false, val userResponseModel: UserResponseModel = UserResponseModel(), val studySets: List = emptyList(), val folders: List = emptyList(), 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 ca392ddf..6ddc35ce 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 @@ -9,6 +9,7 @@ import com.pwhs.quickmem.core.utils.Resources import com.pwhs.quickmem.domain.model.classes.DeleteFolderRequestModel import com.pwhs.quickmem.domain.model.classes.DeleteStudySetsRequestModel import com.pwhs.quickmem.domain.model.classes.ExitClassRequestModel +import com.pwhs.quickmem.domain.model.classes.InviteToClassRequestModel import com.pwhs.quickmem.domain.model.classes.JoinClassRequestModel import com.pwhs.quickmem.domain.model.classes.RemoveMembersRequestModel import com.pwhs.quickmem.domain.model.classes.SaveRecentAccessClassRequestModel @@ -113,6 +114,19 @@ class ClassDetailViewModel @Inject constructor( is ClassDetailUiAction.OnDeleteFolderInClass -> { deleteFolderInClass(event.folderId) } + + is ClassDetailUiAction.OnChangeUsername -> { + _uiState.update { + it.copy( + username = event.username, + errorMessage = "" + ) + } + } + + ClassDetailUiAction.OnInviteClass -> { + inviteToClass() + } } } @@ -415,4 +429,78 @@ class ClassDetailViewModel @Inject constructor( } } } + + private fun inviteToClass() { + viewModelScope.launch { + val token = tokenManager.accessToken.firstOrNull() ?: "" + val classId = _uiState.value.id + val username = _uiState.value.username + if (username.isEmpty()) { + _uiState.update { + it.copy( + errorMessage = "Username cannot be empty" + ) + } + return@launch + } + if (username.length < 4) { + _uiState.update { + it.copy( + errorMessage = "Username must be at least 4 characters" + ) + } + return@launch + } + + classRepository.inviteToClass( + token, + InviteToClassRequestModel(classId, username) + ).collectLatest { resource -> + when (resource) { + is Resources.Loading -> { + _uiState.update { + it.copy( + isLoading = true + ) + } + Timber.d("Loading") + } + + is Resources.Success -> { + _uiState.update { + it.copy( + isLoading = false, + statusInvite = resource.data?.status == true + ) + } + if (_uiState.value.statusInvite == true) { + _uiEvent.send(ClassDetailUiEvent.InviteToClassSuccess) + } else { + _uiState.update { + it.copy( + errorMessage = resource.data?.message ?: "An error occurred" + ) + } + } + Timber.d("Success") + } + + is Resources.Error -> { + _uiState.update { + it.copy( + errorMessage = resource.message ?: "An error occurred", + isLoading = false + ) + } + _uiEvent.send( + ClassDetailUiEvent.ShowError( + resource.message ?: "An error occurred" + ) + ) + Timber.d("Error") + } + } + } + } + } } diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/component/ClassDetailBottomSheet.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/component/ClassDetailBottomSheet.kt index 1aef205d..03e02ae8 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/component/ClassDetailBottomSheet.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/component/ClassDetailBottomSheet.kt @@ -7,7 +7,7 @@ import androidx.compose.material.icons.Icons.Default import androidx.compose.material.icons.Icons.Outlined import androidx.compose.material.icons.automirrored.filled.ExitToApp import androidx.compose.material.icons.filled.DeleteOutline -import androidx.compose.material.icons.filled.IosShare +import androidx.compose.material.icons.filled.PersonAdd import androidx.compose.material.icons.outlined.ContentCopy import androidx.compose.material.icons.outlined.Edit import androidx.compose.material.icons.outlined.Folder @@ -34,7 +34,7 @@ fun ClassDetailBottomSheet( onEditClass: () -> Unit = {}, onExitClass: () -> Unit = {}, onDeleteClass: () -> Unit = {}, - onShareClass: () -> Unit = {}, + onInviteClass: () -> Unit = {}, onReportClass: () -> Unit = {}, onJoinClass: () -> Unit = {}, showMoreBottomSheet: Boolean = false, @@ -78,11 +78,13 @@ fun ClassDetailBottomSheet( title = "Join Class" ) } - ItemMenuBottomSheet( - onClick = onShareClass, - icon = Default.IosShare, - title = "Share Class" - ) + if (isOwner) { + ItemMenuBottomSheet( + onClick = onInviteClass, + icon = Default.PersonAdd, + title = "Invite Class" + ) + } if (!isOwner && isMember) { ItemMenuBottomSheet( onClick = onExitClass, diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/component/ClassTextField.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/component/ClassTextField.kt new file mode 100644 index 00000000..f886f3c7 --- /dev/null +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/component/ClassTextField.kt @@ -0,0 +1,54 @@ +package com.pwhs.quickmem.presentation.app.classes.detail.component + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.MaterialTheme.shapes +import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight + +@Composable +fun ClassTextField( + modifier: Modifier = Modifier, + placeholder: String = "", + value: String = "", + onValueChange: (String) -> Unit = {}, + errorMessage: String = "", +) { + TextField( + modifier = modifier + .fillMaxWidth(), + value = value, + maxLines = 1, + onValueChange = onValueChange, + placeholder = { + Text( + text = placeholder, + style = typography.bodyMedium.copy( + color = colorScheme.onSurface.copy(alpha = 0.6f), + fontWeight = FontWeight.Bold + ) + ) + }, + colors = TextFieldDefaults.colors( + unfocusedIndicatorColor = Color.Transparent, + focusedContainerColor = Color.White, + unfocusedContainerColor = Color.White, + errorContainerColor = Color.White, + ), + shape = shapes.medium, + isError = errorMessage.isNotEmpty(), + supportingText = { + Text( + text = errorMessage, + color = colorScheme.error, + style = typography.bodyMedium + ) + } + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/component/InviteClassBottomSheet.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/component/InviteClassBottomSheet.kt new file mode 100644 index 00000000..3d0524a9 --- /dev/null +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/classes/detail/component/InviteClassBottomSheet.kt @@ -0,0 +1,96 @@ +package com.pwhs.quickmem.presentation.app.classes.detail.component + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.MaterialTheme.shapes +import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.SheetState +import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun InviteClassBottomSheet( + modifier: Modifier = Modifier, + username: String, + errorMessage: String, + onSubmitClick: () -> Unit = {}, + showInviteClassBottomSheet: Boolean = false, + onUsernameChanged: (String) -> Unit = {}, + sheetShowMoreState: SheetState = rememberModalBottomSheetState(), + onDismissRequest: () -> Unit = {}, +) { + if (showInviteClassBottomSheet) { + ModalBottomSheet( + modifier = modifier, + onDismissRequest = onDismissRequest, + sheetState = sheetShowMoreState + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) { + Text( + "Invite Members", + style = typography.titleLarge.copy( + fontWeight = FontWeight.Bold + ) + ) + Text( + "To invite members to this class, add their Quizlet usernames or emails below (separate by commas or line breaks).", + style = typography.bodyMedium, + modifier = Modifier.padding(top = 8.dp) + ) + ClassTextField( + modifier = Modifier.padding(top = 16.dp, bottom = 8.dp), + value = username, + onValueChange = onUsernameChanged, + errorMessage = errorMessage, + placeholder = "Type username to invite", + ) + Button( + enabled = username.isNotEmpty() && username.length > 4, + onClick = onSubmitClick, + modifier = Modifier + .fillMaxWidth(), + shape = shapes.medium + ) { + Text( + "Submit", style = typography.bodyMedium.copy( + fontWeight = FontWeight.Bold + ) + ) + } + OutlinedButton( + onClick = onDismissRequest, + colors = ButtonDefaults.outlinedButtonColors( + contentColor = colorScheme.onSurface.copy(alpha = 0.6f) + ), + modifier = Modifier + .padding(top = 8.dp) + .fillMaxWidth(), + shape = shapes.medium + ) { + Text( + "Cancel", + style = typography.bodyMedium.copy( + fontWeight = FontWeight.Bold + ) + ) + } + } + } + } +} \ No newline at end of file