diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/emailverification/EmailVerificationScreen.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/emailverification/EmailVerificationScreen.kt index 8304583a..35f20fb8 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/compose/emailverification/EmailVerificationScreen.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/emailverification/EmailVerificationScreen.kt @@ -21,11 +21,11 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavHostController import com.example.speechbuddy.R +import com.example.speechbuddy.compose.utils.AuthTopAppBarUi import com.example.speechbuddy.compose.utils.ButtonLevel import com.example.speechbuddy.compose.utils.ButtonUi import com.example.speechbuddy.compose.utils.TextFieldUi import com.example.speechbuddy.compose.utils.TitleUi -import com.example.speechbuddy.compose.utils.TopAppBarUi import com.example.speechbuddy.ui.models.EmailVerificationErrorType import com.example.speechbuddy.viewmodel.EmailVerificationViewModel @@ -47,7 +47,9 @@ fun EmailVerificationScreen( Surface( modifier = modifier.fillMaxSize() ) { - Scaffold(topBar = { TopAppBarUi(onBackClick = onBackClick) }) { + Scaffold( + topBar = { AuthTopAppBarUi(onBackClick = onBackClick) } + ) { Column( modifier = Modifier .fillMaxSize() @@ -72,7 +74,7 @@ fun EmailVerificationScreen( // Email Text Field TextFieldUi(value = viewModel.emailInput, onValueChange = { viewModel.setEmail(it) }, - label = { Text(text = stringResource(id = R.string.email_field)) }, + label = { Text(text = stringResource(id = R.string.email)) }, supportingButton = { ButtonUi( text = stringResource(id = R.string.send_validation_code), diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/home/HomeScreen.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/home/HomeScreen.kt index 780e451c..a37d5d61 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/compose/home/HomeScreen.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/home/HomeScreen.kt @@ -1,7 +1,6 @@ package com.example.speechbuddy.compose.home -import android.annotation.SuppressLint -import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -14,7 +13,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import androidx.navigation.NavController import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost @@ -26,6 +24,7 @@ import com.example.speechbuddy.compose.settings.SettingsScreen import com.example.speechbuddy.compose.symbolcreation.SymbolCreationScreen import com.example.speechbuddy.compose.symbolselection.SymbolSelectionScreen import com.example.speechbuddy.compose.texttospeech.TextToSpeechScreen +import com.example.speechbuddy.compose.utils.NoRippleInteractionSource import com.example.speechbuddy.ui.SpeechBuddyTheme data class BottomNavItem( @@ -34,22 +33,19 @@ data class BottomNavItem( val iconResId: Int ) -@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @OptIn(ExperimentalMaterial3Api::class) @Composable -fun HomeScreen( - -) { +fun HomeScreen() { val navController = rememberNavController() val navItems = listOf( BottomNavItem( "symbol_selection", - R.string.symbol_selection, + R.string.talk_with_symbols, R.drawable.outline_touch_app_24 ), BottomNavItem( "text_to_speech", - R.string.text_to_speech, + R.string.talk_with_speech, R.drawable.outline_volume_up_24 ), BottomNavItem( @@ -69,16 +65,15 @@ fun HomeScreen( BottomNavigationBar( items = navItems, navController = navController, - modifier = Modifier - .height(100.dp), onItemClick = { navController.navigate(it.route) } ) } - ) { + ) { paddingValues -> HomeScreenNavHost( - navController = navController + navController = navController, + bottomPaddingValues = paddingValues ) } } @@ -110,7 +105,8 @@ private fun BottomNavigationBar( colors = NavigationBarItemDefaults.colors( selectedIconColor = MaterialTheme.colorScheme.onPrimaryContainer, unselectedIconColor = MaterialTheme.colorScheme.outline - ) + ), + interactionSource = NoRippleInteractionSource() ) } } @@ -118,20 +114,29 @@ private fun BottomNavigationBar( @Composable private fun HomeScreenNavHost( - navController: NavHostController + navController: NavHostController, + bottomPaddingValues: PaddingValues ) { NavHost(navController = navController, startDestination = "symbol_selection") { composable("symbol_selection") { - SymbolSelectionScreen() + SymbolSelectionScreen( + bottomPaddingValues = bottomPaddingValues + ) } composable("text_to_speech") { - TextToSpeechScreen() + TextToSpeechScreen( + bottomPaddingValues = bottomPaddingValues + ) } composable("symbol_creation") { - SymbolCreationScreen() + SymbolCreationScreen( + bottomPaddingValues = bottomPaddingValues + ) } composable("settings") { - SettingsScreen() + SettingsScreen( + bottomPaddingValues = bottomPaddingValues + ) } } } diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/login/LoginScreen.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/login/LoginScreen.kt index 3e33f77a..6ee74c6b 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/compose/login/LoginScreen.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/login/LoginScreen.kt @@ -21,11 +21,11 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.example.speechbuddy.R +import com.example.speechbuddy.compose.utils.AuthTopAppBarUi import com.example.speechbuddy.compose.utils.ButtonLevel import com.example.speechbuddy.compose.utils.ButtonUi import com.example.speechbuddy.compose.utils.TextFieldUi import com.example.speechbuddy.compose.utils.TitleUi -import com.example.speechbuddy.compose.utils.TopAppBarUi import com.example.speechbuddy.ui.models.LoginErrorType import com.example.speechbuddy.viewmodel.LoginViewModel @@ -46,7 +46,7 @@ fun LoginScreen( Surface(modifier = modifier.fillMaxSize()) { Scaffold( - topBar = { TopAppBarUi(onBackClick = onBackClick) } + topBar = { AuthTopAppBarUi(onBackClick = onBackClick) } ) { Column( modifier = Modifier @@ -66,7 +66,7 @@ fun LoginScreen( TextFieldUi( value = viewModel.emailInput, onValueChange = { viewModel.setEmail(it) }, - label = { Text(stringResource(id = R.string.email_field)) }, + label = { Text(stringResource(id = R.string.email)) }, supportingText = { if (isEmailError) { Text(stringResource(id = uiState.error!!.messageId)) diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/resetpassword/ResetPasswordScreen.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/resetpassword/ResetPasswordScreen.kt index 9920cdae..08ea98bc 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/compose/resetpassword/ResetPasswordScreen.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/resetpassword/ResetPasswordScreen.kt @@ -25,7 +25,7 @@ import com.example.speechbuddy.compose.utils.ButtonLevel import com.example.speechbuddy.compose.utils.ButtonUi import com.example.speechbuddy.compose.utils.TextFieldUi import com.example.speechbuddy.compose.utils.TitleUi -import com.example.speechbuddy.compose.utils.TopAppBarUi +import com.example.speechbuddy.compose.utils.AuthTopAppBarUi import com.example.speechbuddy.ui.models.ResetPasswordErrorType import com.example.speechbuddy.viewmodel.ResetPasswordViewModel @@ -48,7 +48,7 @@ fun ResetPasswordScreen( ) { Scaffold( topBar = { - TopAppBarUi( + AuthTopAppBarUi( onBackClick = onBackClick ) } diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/AccountSettings.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/AccountSettings.kt new file mode 100644 index 00000000..e8eb96b0 --- /dev/null +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/AccountSettings.kt @@ -0,0 +1,141 @@ +package com.example.speechbuddy.compose.settings + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.example.speechbuddy.R +import com.example.speechbuddy.compose.utils.AlertDialogUi +import com.example.speechbuddy.compose.utils.ButtonLevel +import com.example.speechbuddy.compose.utils.ButtonUi +import com.example.speechbuddy.compose.utils.HomeTopAppBarUi +import com.example.speechbuddy.compose.utils.TitleUi +import com.example.speechbuddy.ui.models.AccountSettingsAlert +import com.example.speechbuddy.viewmodel.AccountSettingsViewModel + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AccountSettings( + modifier: Modifier = Modifier, + onBackClick: () -> Unit, + bottomPaddingValues: PaddingValues, + viewModel: AccountSettingsViewModel = hiltViewModel() +) { + val uiState by viewModel.uiState.collectAsState() + + Surface( + modifier = modifier.fillMaxSize() + ) { + Scaffold( + topBar = { + HomeTopAppBarUi( + title = stringResource(id = R.string.settings), + onBackClick = onBackClick, + isBackClickEnabled = true + ) + } + ) { topPaddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding( + top = topPaddingValues.calculateTopPadding(), + bottom = bottomPaddingValues.calculateBottomPadding() + ) + .padding(24.dp), + verticalArrangement = Arrangement.Center + ) { + TitleUi(title = stringResource(id = R.string.account)) + + Spacer(modifier = Modifier.height(20.dp)) + + Column( + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + SettingsRow( + label = stringResource(id = R.string.email), + content = { + SettingsRowText(text = uiState.email) + } + ) + + SettingsRow( + label = stringResource(id = R.string.nickname), + content = { + SettingsRowText(text = uiState.nickname) + } + ) + } + + Spacer(modifier = Modifier.height(80.dp)) + + Column( + verticalArrangement = Arrangement.spacedBy(14.dp) + ) { + ButtonUi( + text = stringResource(id = R.string.logout), + onClick = { viewModel.showAlert(AccountSettingsAlert.LOGOUT) } + ) + + ButtonUi( + text = stringResource(id = R.string.withdraw), + onClick = { viewModel.showAlert(AccountSettingsAlert.WITHDRAW) }, + level = ButtonLevel.QUATERNARY + ) + } + } + } + } + + uiState.alert?.let { alert -> + when (alert) { + AccountSettingsAlert.LOGOUT -> { + AlertDialogUi( + title = stringResource(id = R.string.logout), + text = stringResource(id = R.string.logout_warning), + dismissButtonText = stringResource(id = R.string.dismiss), + confirmButtonText = stringResource(id = R.string.logout), + onDismiss = { viewModel.hideAlert() }, + onConfirm = { viewModel.logout() } + ) + } + + AccountSettingsAlert.WITHDRAW -> { + AlertDialogUi( + title = stringResource(id = R.string.withdraw), + text = stringResource(id = R.string.withdraw_warning), + dismissButtonText = stringResource(id = R.string.dismiss), + confirmButtonText = stringResource(id = R.string.proceed), + onDismiss = { viewModel.hideAlert() }, + onConfirm = { + viewModel.showAlert(AccountSettingsAlert.WITHDRAW_PROCEED) + } + ) + } + + AccountSettingsAlert.WITHDRAW_PROCEED -> { + AlertDialogUi( + title = stringResource(id = R.string.withdraw), + text = stringResource(id = R.string.withdraw_proceed_warning), + dismissButtonText = stringResource(id = R.string.dismiss), + confirmButtonText = stringResource(id = R.string.withdraw), + onDismiss = { viewModel.hideAlert() }, + onConfirm = { viewModel.deleteAccount() } + ) + } + } + } +} \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/BackupSettings.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/BackupSettings.kt new file mode 100644 index 00000000..3c014954 --- /dev/null +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/BackupSettings.kt @@ -0,0 +1,92 @@ +package com.example.speechbuddy.compose.settings + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.material3.Switch +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.example.speechbuddy.R +import com.example.speechbuddy.compose.utils.ButtonUi +import com.example.speechbuddy.compose.utils.HomeTopAppBarUi +import com.example.speechbuddy.compose.utils.TitleUi +import com.example.speechbuddy.viewmodel.BackupSettingsViewModel + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun BackupSettings( + modifier: Modifier = Modifier, + onBackClick: () -> Unit, + bottomPaddingValues: PaddingValues, + viewModel: BackupSettingsViewModel = hiltViewModel() +) { + val uiState by viewModel.uiState.collectAsState() + + Surface( + modifier = modifier.fillMaxSize() + ) { + Scaffold(topBar = { + HomeTopAppBarUi( + title = stringResource(id = R.string.settings), + onBackClick = onBackClick, + isBackClickEnabled = true + ) + }) { topPaddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding( + top = topPaddingValues.calculateTopPadding(), + bottom = bottomPaddingValues.calculateBottomPadding() + ) + .padding(24.dp), + verticalArrangement = Arrangement.Center + ) { + TitleUi(title = stringResource(id = R.string.backup_to_server)) + + Spacer(modifier = Modifier.height(20.dp)) + + Column( + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + SettingsRow( + label = stringResource(id = R.string.last_backup_date), + content = { + SettingsRowText(text = uiState.lastBackupDate) + } + ) + + SettingsRow(label = stringResource(id = R.string.enable_auto_backup), + content = { + Switch( + checked = uiState.isAutoBackupEnabled, + onCheckedChange = { viewModel.setAutoBackup(it) }, + modifier = Modifier.heightIn(max = 32.dp) + ) + } + ) + } + + Spacer(modifier = Modifier.height(80.dp)) + + ButtonUi( + text = stringResource(id = R.string.backup_now), + onClick = { viewModel.backup() } + ) + } + } + } +} \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/DevelopersInfo.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/DevelopersInfo.kt new file mode 100644 index 00000000..3faf07cd --- /dev/null +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/DevelopersInfo.kt @@ -0,0 +1,64 @@ +package com.example.speechbuddy.compose.settings + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.example.speechbuddy.R +import com.example.speechbuddy.compose.utils.HomeTopAppBarUi +import com.example.speechbuddy.compose.utils.TitleUi + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun DevelopersInfo( + modifier: Modifier = Modifier, onBackClick: () -> Unit, bottomPaddingValues: PaddingValues +) { + Surface( + modifier = modifier.fillMaxSize() + ) { + Scaffold(topBar = { + HomeTopAppBarUi( + title = stringResource(id = R.string.settings), + onBackClick = onBackClick, + isBackClickEnabled = true + ) + }) { topPaddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding( + top = topPaddingValues.calculateTopPadding(), + bottom = bottomPaddingValues.calculateBottomPadding() + ) + .padding(24.dp), verticalArrangement = Arrangement.Center + ) { + TitleUi(title = stringResource(id = R.string.developers_info)) + + Spacer(modifier = modifier.height(20.dp)) + + Column( + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + /* TODO */ + SettingsRow(label = "서울대학교 소개원실 team 6") + SettingsRow(label = "김연정") + SettingsRow(label = "류명현") + SettingsRow(label = "오준형") + SettingsRow(label = "이민영") + SettingsRow(label = "이석찬") + SettingsRow(label = "주승민") + } + } + } + } +} \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/DisplaySettings.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/DisplaySettings.kt new file mode 100644 index 00000000..08d52712 --- /dev/null +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/DisplaySettings.kt @@ -0,0 +1,128 @@ +package com.example.speechbuddy.compose.settings + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.RadioButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.material3.Switch +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.example.speechbuddy.R +import com.example.speechbuddy.compose.utils.HomeTopAppBarUi +import com.example.speechbuddy.compose.utils.TitleUi +import com.example.speechbuddy.ui.models.InitialPage +import com.example.speechbuddy.viewmodel.DisplaySettingsViewModel + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun DisplaySettings( + modifier: Modifier = Modifier, + onBackClick: () -> Unit, + bottomPaddingValues: PaddingValues, + viewModel: DisplaySettingsViewModel = hiltViewModel() +) { + val uiState by viewModel.uiState.collectAsState() + + Surface( + modifier = modifier.fillMaxSize() + ) { + Scaffold( + topBar = { + HomeTopAppBarUi( + title = stringResource(id = R.string.settings), + onBackClick = onBackClick, + isBackClickEnabled = true + ) + } + ) { topPaddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding( + top = topPaddingValues.calculateTopPadding(), + bottom = bottomPaddingValues.calculateBottomPadding() + ) + .padding(24.dp), + verticalArrangement = Arrangement.Center + ) { + TitleUi(title = stringResource(id = R.string.display_settings)) + + Spacer(modifier = modifier.height(20.dp)) + + Column( + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + SettingsRow( + label = stringResource(id = R.string.dark_mode), + content = { + Switch( + checked = uiState.isDarkModeEnabled, + onCheckedChange = { viewModel.setDarkMode(it) }, + modifier = Modifier.heightIn(max = 32.dp) + ) + } + ) + + SettingsRow( + label = stringResource(id = R.string.initial_page), + content = { + InitialPageColumn( + initialPage = uiState.initialPage, + onSelectInitialPage = { + viewModel.setInitialPage(it) + } + ) + } + ) + } + } + } + } +} + +@Composable +fun InitialPageColumn( + initialPage: InitialPage, + onSelectInitialPage: (InitialPage) -> Unit +) { + Column( + modifier = Modifier.width(140.dp), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + SettingsRow( + label = stringResource(id = R.string.talk_with_symbols), + content = { + RadioButton( + selected = initialPage == InitialPage.SYMBOL_SELECTION, + onClick = { onSelectInitialPage(InitialPage.SYMBOL_SELECTION) }, + modifier = Modifier.heightIn(max = 32.dp) + ) + } + ) + + SettingsRow( + label = stringResource(id = R.string.talk_with_speech), + content = { + RadioButton( + selected = initialPage == InitialPage.TEXT_TO_SPEECH, + onClick = { onSelectInitialPage(InitialPage.TEXT_TO_SPEECH) }, + modifier = Modifier.heightIn(max = 32.dp) + ) + } + ) + } +} \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/MainSettings.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/MainSettings.kt new file mode 100644 index 00000000..c166829a --- /dev/null +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/MainSettings.kt @@ -0,0 +1,91 @@ +package com.example.speechbuddy.compose.settings + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import com.example.speechbuddy.R +import com.example.speechbuddy.compose.utils.HomeTopAppBarUi +import com.example.speechbuddy.compose.utils.NoRippleInteractionSource + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MainSettings( + modifier: Modifier = Modifier, + navController: NavHostController, + bottomPaddingValues: PaddingValues +) { + Surface( + modifier = modifier.fillMaxSize() + ) { + Scaffold(topBar = { + HomeTopAppBarUi(title = stringResource(id = R.string.settings)) + }) { topPaddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding( + top = topPaddingValues.calculateTopPadding(), + bottom = bottomPaddingValues.calculateBottomPadding() + ) + .padding(24.dp) + ) { + SettingsTextButton(text = stringResource(id = R.string.account), + onClick = { navController.navigate("account") }) + + SettingsTextButton(text = stringResource(id = R.string.backup_to_server), + onClick = { navController.navigate("backup") }) + + SettingsTextButton(text = stringResource(id = R.string.display), + onClick = { navController.navigate("display") }) + + SettingsTextButton(text = stringResource(id = R.string.manage_symbols), + onClick = { navController.navigate("my_symbol") }) + + SettingsTextButton(text = stringResource(id = R.string.version_info), + onClick = { navController.navigate("version") }) + + SettingsTextButton(text = stringResource(id = R.string.developers_info), + onClick = { navController.navigate("developers") }) + } + } + } +} + +@Composable +fun SettingsTextButton( + text: String, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + TextButton( + onClick = onClick, + modifier = modifier.fillMaxWidth(), + interactionSource = NoRippleInteractionSource() + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Start + ) { + Text( + text = text, + color = MaterialTheme.colorScheme.onBackground, + style = MaterialTheme.typography.headlineMedium + ) + } + } +} \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/MySymbolSettings.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/MySymbolSettings.kt new file mode 100644 index 00000000..88008645 --- /dev/null +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/MySymbolSettings.kt @@ -0,0 +1,55 @@ +package com.example.speechbuddy.compose.settings + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.example.speechbuddy.R +import com.example.speechbuddy.compose.utils.HomeTopAppBarUi + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MySymbolSettings( + modifier: Modifier = Modifier, + onBackClick: () -> Unit, + bottomPaddingValues: PaddingValues +) { + Surface( + modifier = modifier.fillMaxSize() + ) { + Scaffold( + topBar = { + HomeTopAppBarUi( + title = stringResource(id = R.string.settings), + onBackClick = onBackClick, + isBackClickEnabled = true + ) + } + ) { topPaddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding( + top = topPaddingValues.calculateTopPadding(), + bottom = bottomPaddingValues.calculateBottomPadding() + ) + .padding(24.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + /* TODO */ + Text(text = "My Symbol Settings") + } + } + } +} \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/SettingsRow.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/SettingsRow.kt new file mode 100644 index 00000000..1da30674 --- /dev/null +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/SettingsRow.kt @@ -0,0 +1,59 @@ +package com.example.speechbuddy.compose.settings + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.example.speechbuddy.ui.SpeechBuddyTheme + +@Composable +fun SettingsRow( + modifier: Modifier = Modifier, + label: String, + content: @Composable (() -> Unit) = {} +) { + Row( + modifier = modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + SettingsRowText(text = label) + content() + } +} + +@Composable +fun SettingsRowText( + modifier: Modifier = Modifier, + text: String +) { + Box( + modifier = modifier.height(32.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = text, + style = MaterialTheme.typography.bodyMedium + ) + } +} + +@Preview(showBackground = true) +@Composable +fun SettingsRowPreview() { + SpeechBuddyTheme { + SettingsRow( + label = "이메일", + content = { + SettingsRowText(text = "example@gmail.com") + } + ) + } +} \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/SettingsScreen.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/SettingsScreen.kt index 080d085c..b889da76 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/SettingsScreen.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/SettingsScreen.kt @@ -1,66 +1,72 @@ package com.example.speechbuddy.compose.settings -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.defaultMinSize -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Surface +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.example.speechbuddy.compose.utils.TitleUi -import com.example.speechbuddy.ui.SpeechBuddyTheme +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController -@OptIn(ExperimentalMaterial3Api::class) @Composable fun SettingsScreen( - + bottomPaddingValues: PaddingValues ) { - Surface( - modifier = Modifier - .fillMaxSize() - ) { - Column( - modifier = Modifier - .padding(25.dp) - .fillMaxSize(), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally - ) { - TitleUi( - title = "설정", - description = "소개원실 드랍하고 싶어요" - ) + val navController = rememberNavController() + SettingsScreenNavHost( + navController = navController, + bottomPaddingValues = bottomPaddingValues + ) +} - Spacer(modifier = Modifier.height(20.dp)) +@Composable +private fun SettingsScreenNavHost( + navController: NavHostController, + bottomPaddingValues: PaddingValues +) { + val navigateToMain = { navController.navigate("main") } - OutlinedTextField( - value = "", - onValueChange = {}, - modifier = Modifier - .fillMaxWidth() - .defaultMinSize(minHeight = 300.dp), - textStyle = MaterialTheme.typography.bodyMedium, - shape = RoundedCornerShape(10.dp) + NavHost(navController = navController, startDestination = "main") { + composable("main") { + MainSettings( + navController = navController, + bottomPaddingValues = bottomPaddingValues + ) + } + composable("account") { + AccountSettings( + onBackClick = navigateToMain, + bottomPaddingValues = bottomPaddingValues + ) + } + composable("backup") { + BackupSettings( + onBackClick = navigateToMain, + bottomPaddingValues = bottomPaddingValues + ) + } + composable("display") { + DisplaySettings( + onBackClick = navigateToMain, + bottomPaddingValues = bottomPaddingValues + ) + } + composable("my_symbol") { + MySymbolSettings( + onBackClick = navigateToMain, + bottomPaddingValues = bottomPaddingValues + ) + } + composable("version") { + VersionInfo( + onBackClick = navigateToMain, + bottomPaddingValues = bottomPaddingValues + ) + } + composable("developers") { + DevelopersInfo( + onBackClick = navigateToMain, + bottomPaddingValues = bottomPaddingValues ) } - } -} - -@Preview -@Composable -private fun SettingsScreenPreview() { - SpeechBuddyTheme { - SettingsScreen() } } \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/VersionInfoScreen.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/VersionInfoScreen.kt new file mode 100644 index 00000000..a3661b53 --- /dev/null +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/VersionInfoScreen.kt @@ -0,0 +1,76 @@ +package com.example.speechbuddy.compose.settings + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.example.speechbuddy.R +import com.example.speechbuddy.compose.utils.HomeTopAppBarUi +import com.example.speechbuddy.compose.utils.TitleUi + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun VersionInfo( + modifier: Modifier = Modifier, + onBackClick: () -> Unit, + bottomPaddingValues: PaddingValues +) { + Surface( + modifier = modifier.fillMaxSize() + ) { + Scaffold( + topBar = { + HomeTopAppBarUi( + title = stringResource(id = R.string.settings), + onBackClick = onBackClick, + isBackClickEnabled = true + ) + } + ) { topPaddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding( + top = topPaddingValues.calculateTopPadding(), + bottom = bottomPaddingValues.calculateBottomPadding() + ) + .padding(24.dp), + verticalArrangement = Arrangement.Center + ) { + TitleUi(title = stringResource(id = R.string.version_info)) + + Spacer(modifier = modifier.height(20.dp)) + + Column( + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + SettingsRow( + label = stringResource(id = R.string.version), + content = { + /* TODO */ + SettingsRowText(text = "1.0.0") + } + ) + + SettingsRow( + label = stringResource(id = R.string.email), + content = { + /* TODO */ + SettingsRowText(text = "speechbuddy@gmail.com") + } + ) + } + } + } + } +} \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/signup/SignupScreen.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/signup/SignupScreen.kt index da8383fe..9f51691d 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/compose/signup/SignupScreen.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/signup/SignupScreen.kt @@ -20,10 +20,10 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.example.speechbuddy.R +import com.example.speechbuddy.compose.utils.AuthTopAppBarUi import com.example.speechbuddy.compose.utils.ButtonUi import com.example.speechbuddy.compose.utils.TextFieldUi import com.example.speechbuddy.compose.utils.TitleUi -import com.example.speechbuddy.compose.utils.TopAppBarUi import com.example.speechbuddy.ui.models.SignupErrorType import com.example.speechbuddy.viewmodel.SignupViewModel @@ -42,9 +42,8 @@ fun SignupScreen( val isPasswordError = uiState.error?.type == SignupErrorType.PASSWORD val isPasswordCheckError = uiState.error?.type == SignupErrorType.PASSWORD_CHECK - Surface(modifier = modifier.fillMaxSize()) { - Scaffold(topBar = { TopAppBarUi(onBackClick = onBackClick) }) { + Scaffold(topBar = { AuthTopAppBarUi(onBackClick = onBackClick) }) { Column( modifier = Modifier .fillMaxSize() @@ -63,7 +62,7 @@ fun SignupScreen( TextFieldUi( value = email, onValueChange = {}, - label = { Text(text = stringResource(id = R.string.email_field)) }, + label = { Text(text = stringResource(id = R.string.email)) }, isEnabled = false ) @@ -123,4 +122,4 @@ fun SignupScreen( } } } -} +} \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/symbolcreation/SymbolCreationScreen.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/symbolcreation/SymbolCreationScreen.kt index dd0aa215..ab685765 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/compose/symbolcreation/SymbolCreationScreen.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/symbolcreation/SymbolCreationScreen.kt @@ -2,6 +2,7 @@ package com.example.speechbuddy.compose.symbolcreation import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxSize @@ -12,55 +13,59 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import com.example.speechbuddy.R +import com.example.speechbuddy.compose.utils.HomeTopAppBarUi import com.example.speechbuddy.compose.utils.TitleUi -import com.example.speechbuddy.ui.SpeechBuddyTheme @OptIn(ExperimentalMaterial3Api::class) @Composable fun SymbolCreationScreen( - + modifier: Modifier = Modifier, + bottomPaddingValues: PaddingValues ) { Surface( - modifier = Modifier - .fillMaxSize() + modifier = modifier.fillMaxSize() ) { - Column( - modifier = Modifier - .padding(25.dp) - .fillMaxSize(), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally - ) { - TitleUi( - title = "상징 추가하기", - description = "소개원실 좋아요" - ) + Scaffold( + topBar = { + HomeTopAppBarUi(title = stringResource(id = R.string.symbol_creation)) + } + ) { topPaddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding( + top = topPaddingValues.calculateTopPadding(), + bottom = bottomPaddingValues.calculateBottomPadding() + ) + .padding(24.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + TitleUi( + title = "상징 추가하기", + description = "소개원실 좋아요" + ) - Spacer(modifier = Modifier.height(20.dp)) + Spacer(modifier = Modifier.height(20.dp)) - OutlinedTextField( - value = "", - onValueChange = {}, - modifier = Modifier - .fillMaxWidth() - .defaultMinSize(minHeight = 300.dp), - textStyle = MaterialTheme.typography.bodyMedium, - shape = RoundedCornerShape(10.dp) - ) + OutlinedTextField( + value = "", + onValueChange = {}, + modifier = Modifier + .fillMaxWidth() + .defaultMinSize(minHeight = 300.dp), + textStyle = MaterialTheme.typography.bodyMedium, + shape = RoundedCornerShape(10.dp) + ) + } } } -} - -@Preview -@Composable -private fun SymbolCreationScreenPreview() { - SpeechBuddyTheme { - SymbolCreationScreen() - } } \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/symbolselection/SymbolSelectionScreen.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/symbolselection/SymbolSelectionScreen.kt index 45ee7729..5c5dbd4c 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/compose/symbolselection/SymbolSelectionScreen.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/symbolselection/SymbolSelectionScreen.kt @@ -1,18 +1,47 @@ package com.example.speechbuddy.compose.symbolselection import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.example.speechbuddy.R +import com.example.speechbuddy.compose.utils.HomeTopAppBarUi +@OptIn(ExperimentalMaterial3Api::class) @Composable -fun SymbolSelectionScreen() { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center +fun SymbolSelectionScreen( + modifier: Modifier = Modifier, + bottomPaddingValues: PaddingValues +) { + Surface( + modifier = modifier.fillMaxSize() ) { - Text(text = "Talk With Symbol") + Scaffold( + topBar = { + HomeTopAppBarUi(title = stringResource(id = R.string.talk_with_symbols)) + } + ) { topPaddingValues -> + Box( + modifier = Modifier + .fillMaxSize() + .padding( + top = topPaddingValues.calculateTopPadding(), + bottom = bottomPaddingValues.calculateBottomPadding() + ) + .padding(24.dp), + contentAlignment = Alignment.Center + ) { + Text(text = "Talk With Symbol") + } + } } } \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/texttospeech/TextToSpeechScreen.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/texttospeech/TextToSpeechScreen.kt index c24caa37..22f2684b 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/compose/texttospeech/TextToSpeechScreen.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/texttospeech/TextToSpeechScreen.kt @@ -2,6 +2,7 @@ package com.example.speechbuddy.compose.texttospeech import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -19,6 +20,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextFieldDefaults @@ -34,6 +36,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.example.speechbuddy.R +import com.example.speechbuddy.compose.utils.HomeTopAppBarUi import com.example.speechbuddy.compose.utils.TitleUi import com.example.speechbuddy.ui.models.ButtonStatusType import com.example.speechbuddy.viewmodel.TextToSpeechViewModel @@ -41,72 +44,75 @@ import com.example.speechbuddy.viewmodel.TextToSpeechViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable fun TextToSpeechScreen( + modifier: Modifier = Modifier, + bottomPaddingValues: PaddingValues, viewModel: TextToSpeechViewModel = hiltViewModel() ) { val uiState by viewModel.uiState.collectAsState() val context = LocalContext.current Surface( - modifier = Modifier - .fillMaxSize() + modifier = modifier.fillMaxSize() ) { - Column( - modifier = Modifier - .padding(24.dp) - .fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - ) { - TitleUi( - title = stringResource(id = R.string.tts_text), - description = stringResource(id = R.string.tts_explain) - ) + Scaffold(topBar = { + HomeTopAppBarUi(title = stringResource(id = R.string.talk_with_speech)) + }) { topPaddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding( + top = topPaddingValues.calculateTopPadding(), + bottom = bottomPaddingValues.calculateBottomPadding() + ) + .padding(24.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + TitleUi( + title = stringResource(id = R.string.tts_text), + description = stringResource(id = R.string.tts_explain) + ) - Spacer(modifier = Modifier.height(20.dp)) + Spacer(modifier = Modifier.height(20.dp)) - OutlinedTextField( - value = viewModel.textInput, - onValueChange = { - viewModel.setText(it) - }, - modifier = Modifier - .fillMaxWidth() - .verticalScroll(rememberScrollState()) - .height(300.dp), - textStyle = MaterialTheme.typography.bodyMedium, - shape = RoundedCornerShape(10.dp), - colors = TextFieldDefaults.outlinedTextFieldColors( - focusedBorderColor = MaterialTheme.colorScheme.tertiary, - unfocusedBorderColor = MaterialTheme.colorScheme.outline + OutlinedTextField( + value = viewModel.textInput, + onValueChange = { + viewModel.setText(it) + }, + modifier = Modifier + .fillMaxWidth() + .verticalScroll(rememberScrollState()) + .height(300.dp), + textStyle = MaterialTheme.typography.bodyMedium, + shape = RoundedCornerShape(10.dp), + colors = TextFieldDefaults.outlinedTextFieldColors( + focusedBorderColor = MaterialTheme.colorScheme.tertiary, + unfocusedBorderColor = MaterialTheme.colorScheme.outline + ) ) - ) - Spacer(modifier = Modifier.height(20.dp)) + Spacer(modifier = Modifier.height(20.dp)) - TextToSpeechButton( - buttonStatus = uiState.buttonStatus, - onPlay = { viewModel.ttsStart(context) }, - onStop = { viewModel.ttsStop() } - ) + TextToSpeechButton(buttonStatus = uiState.buttonStatus, + onPlay = { viewModel.ttsStart(context) }, + onStop = { viewModel.ttsStop() }) + } } } } @Composable private fun TextToSpeechButton( - buttonStatus: ButtonStatusType, - onPlay: () -> Unit, - onStop: () -> Unit + buttonStatus: ButtonStatusType, onPlay: () -> Unit, onStop: () -> Unit ) { val textToSpeechButtonColors = ButtonDefaults.buttonColors( - containerColor = Color.Transparent, - contentColor = MaterialTheme.colorScheme.onBackground + containerColor = Color.Transparent, contentColor = MaterialTheme.colorScheme.onBackground ) when (buttonStatus) { ButtonStatusType.PLAY -> Button( - onClick = onPlay, - colors = textToSpeechButtonColors + onClick = onPlay, colors = textToSpeechButtonColors ) { Text( style = MaterialTheme.typography.headlineMedium, @@ -120,8 +126,7 @@ private fun TextToSpeechButton( } ButtonStatusType.STOP -> Button( - onClick = onStop, - colors = textToSpeechButtonColors + onClick = onStop, colors = textToSpeechButtonColors ) { Text( style = MaterialTheme.typography.headlineMedium, diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/utils/AlertDialogUi.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/utils/AlertDialogUi.kt new file mode 100644 index 00000000..81b28a3b --- /dev/null +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/utils/AlertDialogUi.kt @@ -0,0 +1,68 @@ +package com.example.speechbuddy.compose.utils + +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.Preview +import com.example.speechbuddy.ui.SpeechBuddyTheme + +@Composable +fun AlertDialogUi( + title: String, + text: String, + dismissButtonText: String, + confirmButtonText: String, + onDismiss: () -> Unit, + onConfirm: () -> Unit +) { + AlertDialog( + onDismissRequest = onDismiss, + confirmButton = { + Button( + onClick = onConfirm, + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.error, + contentColor = MaterialTheme.colorScheme.onError, + ) + ) { + Text(confirmButtonText) + } + }, + dismissButton = { + Button( + onClick = onDismiss, + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.surfaceVariant, + contentColor = MaterialTheme.colorScheme.onSurfaceVariant + ) + ) { + Text(dismissButtonText) + } + }, + title = { + Text(text = title) + }, + text = { + Text(text = text) + }, + containerColor = MaterialTheme.colorScheme.inverseOnSurface + ) +} + +@Preview +@Composable +fun AlertDialogUiPreview() { + SpeechBuddyTheme { + AlertDialogUi( + title = "title", + text = "text", + dismissButtonText = "dismiss", + confirmButtonText = "confirm", + onDismiss = {}, + onConfirm = {} + ) + } +} \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/utils/TopAppBarUi.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/utils/AuthTopAppBarUi.kt similarity index 76% rename from frontend/app/src/main/java/com/example/speechbuddy/compose/utils/TopAppBarUi.kt rename to frontend/app/src/main/java/com/example/speechbuddy/compose/utils/AuthTopAppBarUi.kt index 9e968908..7979e785 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/compose/utils/TopAppBarUi.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/utils/AuthTopAppBarUi.kt @@ -12,20 +12,21 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.example.speechbuddy.R import com.example.speechbuddy.ui.SpeechBuddyTheme /** - * Custom UI designed for top app bars. + * Custom UI designed for top app bars used in auth activity. * * @param modifier the Modifier to be applied to this top app bar * @param onBackClick called when the back icon of this top app bar is clicked */ @OptIn(ExperimentalMaterial3Api::class) @Composable -fun TopAppBarUi( +fun AuthTopAppBarUi( modifier: Modifier = Modifier, onBackClick: () -> Unit ) { @@ -34,16 +35,16 @@ fun TopAppBarUi( title = { Image( painter = painterResource(id = R.drawable.top_app_bar_ic), - contentDescription = "app logo", - contentScale = ContentScale.Fit, - modifier = Modifier.size(148.dp) + contentDescription = stringResource(id = R.string.app_name), + modifier = Modifier.size(148.dp), + contentScale = ContentScale.Fit ) }, navigationIcon = { IconButton(onClick = onBackClick) { Icon( imageVector = Icons.Default.ArrowBack, - contentDescription = "Go back to landing page" + contentDescription = stringResource(id = R.string.back) ) } }, @@ -52,8 +53,8 @@ fun TopAppBarUi( @Preview @Composable -fun TopAppBarUiPreview() { +fun AuthTopAppBarUiPreview() { SpeechBuddyTheme { - TopAppBarUi(onBackClick = {}) + AuthTopAppBarUi(onBackClick = {}) } } diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/utils/ButtonUi.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/utils/ButtonUi.kt index 64cd3906..8f9eafa6 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/compose/utils/ButtonUi.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/utils/ButtonUi.kt @@ -17,7 +17,7 @@ import androidx.compose.ui.unit.dp import com.example.speechbuddy.ui.SpeechBuddyTheme enum class ButtonLevel { - PRIMARY, SECONDARY, TERTIARY + PRIMARY, SECONDARY, TERTIARY, QUATERNARY } /** @@ -108,6 +108,28 @@ fun ButtonUi( ) { Text(text = text, style = MaterialTheme.typography.bodyMedium) } + + ButtonLevel.QUATERNARY -> Button( + onClick = onClick, + modifier = modifier + .fillMaxWidth() + .height(48.dp), + enabled = isEnabled, + shape = RoundedCornerShape(10.dp), + colors = if (isError) { + ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.error, + contentColor = MaterialTheme.colorScheme.onError, + disabledContainerColor = MaterialTheme.colorScheme.error, + disabledContentColor = MaterialTheme.colorScheme.onError + ) + } else ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.inverseSurface, + contentColor = MaterialTheme.colorScheme.inverseOnSurface + ) + ) { + Text(text = text, style = MaterialTheme.typography.titleMedium) + } } } diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/utils/HomeTopAppBarUi.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/utils/HomeTopAppBarUi.kt new file mode 100644 index 00000000..6f3b0a7e --- /dev/null +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/utils/HomeTopAppBarUi.kt @@ -0,0 +1,84 @@ +package com.example.speechbuddy.compose.utils + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.example.speechbuddy.R +import com.example.speechbuddy.ui.SpeechBuddyTheme + +/** + * Custom UI designed for top app bars used in main activity. + * + * @param title title to be displayed in the center of this top app bar + * @param modifier the Modifier to be applied to this top app bar + * @param onBackClick called when the back icon of this top app bar is clicked + * @param isBackClickEnabled decides what to show in the left edge of this top app bar. If false, app logo is displayed instead. + * @param actions the actions displayed at the end of the top app bar + */ +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun HomeTopAppBarUi( + title: String, + modifier: Modifier = Modifier, + onBackClick: () -> Unit = {}, + isBackClickEnabled: Boolean = false, + actions: @Composable (RowScope.() -> Unit) = {} +) { + CenterAlignedTopAppBar( + modifier = modifier, + title = { + Text( + text = title, + style = MaterialTheme.typography.titleLarge + ) + }, + navigationIcon = { + if (isBackClickEnabled) + IconButton(onClick = onBackClick) { + Icon( + imageVector = Icons.Default.ArrowBack, + contentDescription = stringResource(id = R.string.back) + ) + } + else + Image( + painter = painterResource(id = R.drawable.speechbuddy_parrot), + contentDescription = stringResource(id = R.string.app_name), + modifier = Modifier + .padding(6.dp) + .size(40.dp), + contentScale = ContentScale.Fit + ) + }, + actions = actions, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + containerColor = MaterialTheme.colorScheme.secondaryContainer, + titleContentColor = MaterialTheme.colorScheme.onSecondaryContainer + ) + ) +} + +@Preview +@Composable +fun HomeTopAppBarUiPreview() { + SpeechBuddyTheme { + HomeTopAppBarUi(title = "title", onBackClick = {}) + } +} \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/utils/NoRippleInteractionSource.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/utils/NoRippleInteractionSource.kt new file mode 100644 index 00000000..07c5525d --- /dev/null +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/utils/NoRippleInteractionSource.kt @@ -0,0 +1,12 @@ +package com.example.speechbuddy.compose.utils + +import androidx.compose.foundation.interaction.Interaction +import androidx.compose.foundation.interaction.MutableInteractionSource +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow + +class NoRippleInteractionSource : MutableInteractionSource { + override val interactions: Flow = emptyFlow() + override suspend fun emit(interaction: Interaction) {} + override fun tryEmit(interaction: Interaction) = true +} \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/utils/TitleUi.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/utils/TitleUi.kt index 7244355d..61bfbd7a 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/compose/utils/TitleUi.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/utils/TitleUi.kt @@ -22,7 +22,7 @@ import com.example.speechbuddy.ui.SpeechBuddyTheme fun TitleUi( modifier: Modifier = Modifier, title: String, - description: String, + description: String? = null, ) { Column(modifier = modifier) { Text( @@ -30,11 +30,13 @@ fun TitleUi( text = title, style = MaterialTheme.typography.displayMedium, ) - Text( - modifier = Modifier.fillMaxWidth(), - text = description, - style = MaterialTheme.typography.bodyMedium, - ) + if (!description.isNullOrEmpty()) { + Text( + modifier = Modifier.fillMaxWidth(), + text = description, + style = MaterialTheme.typography.bodyMedium, + ) + } } } diff --git a/frontend/app/src/main/java/com/example/speechbuddy/ui/models/AccountSettingsUiState.kt b/frontend/app/src/main/java/com/example/speechbuddy/ui/models/AccountSettingsUiState.kt new file mode 100644 index 00000000..361cad36 --- /dev/null +++ b/frontend/app/src/main/java/com/example/speechbuddy/ui/models/AccountSettingsUiState.kt @@ -0,0 +1,17 @@ +package com.example.speechbuddy.ui.models + +data class AccountSettingsUiState( + /** + * TODO + * email, nickname은 나중에 user 모델 하나로 묶어야 할 듯 + */ + val email: String = "example@gmail.com", + val nickname: String = "nickname", + val alert: AccountSettingsAlert? = null +) + +enum class AccountSettingsAlert { + LOGOUT, + WITHDRAW, + WITHDRAW_PROCEED +} \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/ui/models/BackupSettingsUiState.kt b/frontend/app/src/main/java/com/example/speechbuddy/ui/models/BackupSettingsUiState.kt new file mode 100644 index 00000000..a83efc44 --- /dev/null +++ b/frontend/app/src/main/java/com/example/speechbuddy/ui/models/BackupSettingsUiState.kt @@ -0,0 +1,7 @@ +package com.example.speechbuddy.ui.models + +data class BackupSettingsUiState( + /* TODO */ + val lastBackupDate: String = "2023.09.09", + val isAutoBackupEnabled: Boolean = true +) \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/ui/models/DisplaySettingsUiState.kt b/frontend/app/src/main/java/com/example/speechbuddy/ui/models/DisplaySettingsUiState.kt new file mode 100644 index 00000000..db1d4277 --- /dev/null +++ b/frontend/app/src/main/java/com/example/speechbuddy/ui/models/DisplaySettingsUiState.kt @@ -0,0 +1,12 @@ +package com.example.speechbuddy.ui.models + +data class DisplaySettingsUiState( + /* TODO */ + val isDarkModeEnabled: Boolean = true, + val initialPage: InitialPage = InitialPage.SYMBOL_SELECTION +) + +enum class InitialPage { + SYMBOL_SELECTION, + TEXT_TO_SPEECH +} \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/utils/Constants.kt b/frontend/app/src/main/java/com/example/speechbuddy/utils/Constants.kt index a81f6621..e99154ab 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/utils/Constants.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/utils/Constants.kt @@ -3,6 +3,7 @@ package com.example.speechbuddy.utils class Constants { companion object { const val BASE_URL = "http://54.180.112.72:8000/" + const val MINIMUM_PASSWORD_LENGTH = 8 const val MAXIMUM_NICKNAME_LENGTH = 15 const val CODE_LENGTH = 6 diff --git a/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/AccountSettingsViewModel.kt b/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/AccountSettingsViewModel.kt new file mode 100644 index 00000000..62f3f138 --- /dev/null +++ b/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/AccountSettingsViewModel.kt @@ -0,0 +1,51 @@ +package com.example.speechbuddy.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.speechbuddy.repository.AuthRepository +import com.example.speechbuddy.ui.models.AccountSettingsAlert +import com.example.speechbuddy.ui.models.AccountSettingsUiState +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class AccountSettingsViewModel @Inject internal constructor( + private val repository: AuthRepository +) : ViewModel() { + + private val _uiState = MutableStateFlow(AccountSettingsUiState()) + val uiState: StateFlow = _uiState.asStateFlow() + + fun showAlert(alert: AccountSettingsAlert) { + _uiState.update { currentState -> + currentState.copy( + alert = alert + ) + } + } + + fun hideAlert() { + _uiState.update { currentState -> + currentState.copy( + alert = null + ) + } + } + + fun logout() { + viewModelScope.launch { + //repository.logout() + } + } + + fun deleteAccount() { + viewModelScope.launch { + //repository.deleteAccount() + } + } +} \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/BackupSettingsViewModel.kt b/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/BackupSettingsViewModel.kt new file mode 100644 index 00000000..6a76ad0b --- /dev/null +++ b/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/BackupSettingsViewModel.kt @@ -0,0 +1,36 @@ +package com.example.speechbuddy.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.speechbuddy.repository.AuthRepository +import com.example.speechbuddy.ui.models.BackupSettingsUiState +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class BackupSettingsViewModel @Inject internal constructor( + private val repository: AuthRepository +) : ViewModel() { + + private val _uiState = MutableStateFlow(BackupSettingsUiState()) + val uiState: StateFlow = _uiState.asStateFlow() + + fun setAutoBackup(value: Boolean) { + _uiState.update { currentState -> + currentState.copy( + isAutoBackupEnabled = value + ) + } + } + + fun backup() { + viewModelScope.launch { + //repository.backup() + } + } +} \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/DisplaySettingsViewModel.kt b/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/DisplaySettingsViewModel.kt new file mode 100644 index 00000000..4236ddf8 --- /dev/null +++ b/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/DisplaySettingsViewModel.kt @@ -0,0 +1,34 @@ +package com.example.speechbuddy.viewmodel + +import androidx.lifecycle.ViewModel +import com.example.speechbuddy.ui.models.DisplaySettingsUiState +import com.example.speechbuddy.ui.models.InitialPage +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import javax.inject.Inject + +@HiltViewModel +class DisplaySettingsViewModel @Inject internal constructor() : ViewModel() { + + private val _uiState = MutableStateFlow(DisplaySettingsUiState()) + val uiState: StateFlow = _uiState.asStateFlow() + + fun setDarkMode(value: Boolean) { + _uiState.update { currentState -> + currentState.copy( + isDarkModeEnabled = value + ) + } + } + + fun setInitialPage(page: InitialPage) { + _uiState.update { currentState -> + currentState.copy( + initialPage = page + ) + } + } +} \ No newline at end of file diff --git a/frontend/app/src/main/res/drawable/speechbuddy_parrot.png b/frontend/app/src/main/res/drawable/speechbuddy_parrot.png new file mode 100644 index 00000000..a61beac3 Binary files /dev/null and b/frontend/app/src/main/res/drawable/speechbuddy_parrot.png differ diff --git a/frontend/app/src/main/res/values/strings.xml b/frontend/app/src/main/res/values/strings.xml index 864c6f43..60c83f74 100644 --- a/frontend/app/src/main/res/values/strings.xml +++ b/frontend/app/src/main/res/values/strings.xml @@ -12,7 +12,7 @@ 이메일 주소가 올바르지 않습니다 비밀번호가 올바르지 않습니다 닉네임이 올바르지 않습니다 - 이메일 + 이메일 비밀번호 회원가입하기 이메일 인증 @@ -34,8 +34,8 @@ 새 비밀번호 확인 8자 이상 입력해주세요 비밀번호가 일치하지 않습니다 - 상징으로 말하기 - 음성으로 말하기 + 상징으로 말하기 + 음성으로 말하기 새 상징 만들기 설정 다른 사용자가 이미 사용하고 있는 이메일입니다 @@ -46,5 +46,32 @@ 소리로 말해보아요 키보드로 텍스트를 입력해주세요 재생하기 + 상징으로 말하기 + 음성으로 말하기 + 새 상징 만들기 + 설정 + 계정 + 서버에 백업하기 + 디스플레이 + 상징 목록 관리 + 버전 정보 + 개발자 정보 + 마지막 백업 날짜 + 자동 백업 활성화 + 지금 백업하기 + 디스플레이 설정 + 다크 모드 + 시작 페이지 + TTS 페이지 + 상징 페이지 + 로그아웃 + 회원탈퇴 + 정말로 로그아웃하시겠습니까? + 취소 + 회원탈퇴를 하면 지금까지의 이용 기록, 즐겨찾기, 새롭게 등록한 상징이 모두 사라지며 복구할 수 없습니다. SpeechBuddy를 다시 이용하시려면 게스트 모드를 이용하시거나 회원가입을 새로 해주셔야 합니다. 탈퇴하시겠습니까? + 진행 + 정말로 탈퇴하시겠습니까? 이 동작은 취소할 수 없습니다. + 버전 정지 + 뒤로가기 \ No newline at end of file diff --git a/frontend/app/src/test/java/com/example/speechbuddy/ExampleUnitTest.kt b/frontend/app/src/test/java/com/example/speechbuddy/ExampleUnitTest.kt index 6dd3e732..ed6c6db8 100644 --- a/frontend/app/src/test/java/com/example/speechbuddy/ExampleUnitTest.kt +++ b/frontend/app/src/test/java/com/example/speechbuddy/ExampleUnitTest.kt @@ -1,9 +1,8 @@ package com.example.speechbuddy +import org.junit.Assert.assertEquals import org.junit.Test -import org.junit.Assert.* - /** * Example local unit test, which will execute on the development machine (host). *