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 a37d5d61..c5d7f75c 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,6 +1,9 @@ package com.example.speechbuddy.compose.home import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -13,6 +16,7 @@ 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 @@ -82,14 +86,14 @@ fun HomeScreen() { private fun BottomNavigationBar( items: List, navController: NavController, - modifier: Modifier = Modifier, onItemClick: (BottomNavItem) -> Unit ) { val backStackEntry = navController.currentBackStackEntryAsState() NavigationBar( - modifier = modifier, - containerColor = MaterialTheme.colorScheme.secondaryContainer + modifier = Modifier.height(100.dp), + containerColor = MaterialTheme.colorScheme.secondaryContainer, + windowInsets = WindowInsets(top = 20.dp) ) { items.forEach { item -> val selected = item.route == backStackEntry.value?.destination?.route @@ -102,6 +106,7 @@ private fun BottomNavigationBar( contentDescription = stringResource(id = item.nameResId) ) }, + modifier = Modifier.size(40.dp), colors = NavigationBarItemDefaults.colors( selectedIconColor = MaterialTheme.colorScheme.onPrimaryContainer, unselectedIconColor = MaterialTheme.colorScheme.outline diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/landing/LandingScreen.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/landing/LandingScreen.kt index 961ec29b..593a7a43 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/compose/landing/LandingScreen.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/landing/LandingScreen.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.Composable @@ -25,11 +26,19 @@ fun LandingScreen( onGuestClick: () -> Unit, onLoginClick: () -> Unit, ) { - Surface(modifier = modifier.fillMaxSize(), color = MaterialTheme.colorScheme.primaryContainer) { - Image( - painter = painterResource(id = R.mipmap.ic_launcher_foreground), - contentDescription = stringResource(id = R.string.app_name), - ) + Surface( + modifier = modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.primaryContainer + ) { + Box( + modifier = Modifier.size(200.dp), + contentAlignment = Alignment.Center + ) { + Image( + painter = painterResource(id = R.drawable.speechbuddy_parrot), + contentDescription = stringResource(id = R.string.app_name), + ) + } Box( modifier = Modifier.padding(horizontal = 24.dp, vertical = 64.dp), contentAlignment = Alignment.BottomCenter diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/symbolselection/SelectedSymbolUi.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/symbolselection/SelectedSymbolUi.kt new file mode 100644 index 00000000..e5f61947 --- /dev/null +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/symbolselection/SelectedSymbolUi.kt @@ -0,0 +1,112 @@ +package com.example.speechbuddy.compose.symbolselection + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Clear +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +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.runtime.Composable +import androidx.compose.ui.Alignment +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.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.example.speechbuddy.R +import com.example.speechbuddy.domain.models.Symbol +import com.example.speechbuddy.ui.SpeechBuddyTheme +import com.example.speechbuddy.utils.Constants + +@ExperimentalMaterial3Api +@Composable +fun SelectedSymbolUi( + symbol: Symbol, + modifier: Modifier = Modifier, + onClear: () -> Unit +) { + Card( + modifier = modifier.size(100.dp), + shape = RoundedCornerShape(10.dp), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.background + ), + border = BorderStroke(width = 1.dp, color = MaterialTheme.colorScheme.onBackground) + ) { + Box(contentAlignment = Alignment.TopEnd) { + IconButton( + onClick = onClear, + modifier = Modifier + .size(22.dp) + .padding(5.dp) + ) { + Icon( + imageVector = Icons.Default.Clear, + contentDescription = stringResource(id = R.string.symbol_unselect) + ) + } + + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Image( + painter = painterResource(id = symbol.imageResId), + contentDescription = symbol.text, + modifier = Modifier.height(65.dp), + contentScale = ContentScale.FillHeight + ) + + Box( + modifier = Modifier + .fillMaxWidth() + .height(35.dp) + .background(color = MaterialTheme.colorScheme.secondaryContainer) + .padding(horizontal = 8.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = symbol.text, + textAlign = TextAlign.Center, + maxLines = Constants.MAXIMUM_LINES_FOR_SYMBOL_TEXT, + style = MaterialTheme.typography.labelSmall + ) + } + } + } + } +} + +@Preview(showBackground = true) +@ExperimentalMaterial3Api +@Composable +fun SelectedSymbolUiPreview() { + val previewSymbol = Symbol( + id = 1, + text = "119에 전화해주세요", + imageResId = R.drawable.symbol_1, + categoryId = 1, + isFavorite = true, + isMine = false + ) + + SpeechBuddyTheme { + SelectedSymbolUi(symbol = previewSymbol, onClear = {}) + } +} \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/symbolselection/SelectedSymbolsBox.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/symbolselection/SelectedSymbolsBox.kt new file mode 100644 index 00000000..07875c8c --- /dev/null +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/symbolselection/SelectedSymbolsBox.kt @@ -0,0 +1,112 @@ +package com.example.speechbuddy.compose.symbolselection + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +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.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +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.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.example.speechbuddy.R +import com.example.speechbuddy.domain.models.Symbol +import com.example.speechbuddy.ui.SpeechBuddyTheme + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SelectedSymbolsBox( + selectedSymbols: List, + onClear: (Symbol) -> Unit, + onClearAll: () -> Unit, + modifier: Modifier = Modifier +) { + Row( + modifier = modifier + .fillMaxWidth() + .height(120.dp) + .background(color = MaterialTheme.colorScheme.surface) + .border( + width = 1.dp, + color = MaterialTheme.colorScheme.surfaceVariant, + shape = RoundedCornerShape(10.dp) + ) + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .weight(1f), + contentAlignment = Alignment.CenterStart + ) { + LazyRow( + contentPadding = PaddingValues(10.dp), + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + items(selectedSymbols) { symbol -> + SelectedSymbolUi(symbol = symbol, onClear = { onClear(symbol) }) + } + } + } + + Box( + modifier = Modifier + .width(50.dp) + .background(color = MaterialTheme.colorScheme.background) + ) { + Button( + onClick = onClearAll, + modifier = Modifier + .fillMaxSize() + .padding(6.dp), + enabled = selectedSymbols.isNotEmpty(), + shape = RoundedCornerShape(5.dp), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.tertiary, + contentColor = MaterialTheme.colorScheme.onTertiary + ), + contentPadding = PaddingValues(2.dp) + ) { + Text( + text = stringResource(id = R.string.clear_all), + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodyMedium + ) + } + } + } +} + +@Preview +@Composable +fun SelectedSymbolsBoxPreview() { + val previewSymbol = Symbol( + id = 1, + text = "119에 전화해주세요", + imageResId = R.drawable.symbol_1, + categoryId = 1, + isFavorite = true, + isMine = false + ) + val selectedSymbols = List(size = 10, init = { previewSymbol }) + + SpeechBuddyTheme { + SelectedSymbolsBox(selectedSymbols = selectedSymbols, onClear = {}, onClearAll = {}) + } +} \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/symbolselection/SymbolSearchTextField.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/symbolselection/SymbolSearchTextField.kt new file mode 100644 index 00000000..288e14fb --- /dev/null +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/symbolselection/SymbolSearchTextField.kt @@ -0,0 +1,63 @@ +package com.example.speechbuddy.compose.symbolselection + +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Search +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +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 + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SymbolSearchTextField( + value: String, + onValueChange: (String) -> Unit, + modifier: Modifier = Modifier +) { + OutlinedTextField( + value = value, + onValueChange = onValueChange, + modifier = modifier + .fillMaxWidth() + .defaultMinSize(minHeight = 48.dp), + textStyle = MaterialTheme.typography.bodyMedium, + placeholder = { Text(text = stringResource(id = R.string.search_box_placeholder)) }, + trailingIcon = { + Icon( + imageVector = Icons.Default.Search, + contentDescription = stringResource(id = R.string.search_box_placeholder) + ) + }, + singleLine = true, + shape = RoundedCornerShape(10.dp), + colors = TextFieldDefaults.outlinedTextFieldColors( + textColor = MaterialTheme.colorScheme.onSurface, + containerColor = MaterialTheme.colorScheme.surface, + focusedBorderColor = MaterialTheme.colorScheme.tertiary, + unfocusedBorderColor = MaterialTheme.colorScheme.surfaceVariant + ) + ) +} + +@Preview(showBackground = true) +@Composable +fun SymbolSearchTextFieldPreview() { + SpeechBuddyTheme { + SymbolSearchTextField( + value = "검색어", + onValueChange = {} + ) + } +} \ 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 5c5dbd4c..0fa77706 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,26 +1,39 @@ package com.example.speechbuddy.compose.symbolselection +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +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.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.shape.RoundedCornerShape 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.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 androidx.hilt.navigation.compose.hiltViewModel import com.example.speechbuddy.R +import com.example.speechbuddy.compose.utils.CategoryUi import com.example.speechbuddy.compose.utils.HomeTopAppBarUi +import com.example.speechbuddy.compose.utils.SymbolUi +import com.example.speechbuddy.domain.models.Category +import com.example.speechbuddy.domain.models.Symbol +import com.example.speechbuddy.viewmodel.SymbolSelectionViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable fun SymbolSelectionScreen( modifier: Modifier = Modifier, - bottomPaddingValues: PaddingValues + bottomPaddingValues: PaddingValues, + viewModel: SymbolSelectionViewModel = hiltViewModel() ) { Surface( modifier = modifier.fillMaxSize() @@ -30,7 +43,7 @@ fun SymbolSelectionScreen( HomeTopAppBarUi(title = stringResource(id = R.string.talk_with_symbols)) } ) { topPaddingValues -> - Box( + Column( modifier = Modifier .fillMaxSize() .padding( @@ -38,9 +51,50 @@ fun SymbolSelectionScreen( bottom = bottomPaddingValues.calculateBottomPadding() ) .padding(24.dp), - contentAlignment = Alignment.Center + verticalArrangement = Arrangement.spacedBy(20.dp) ) { - Text(text = "Talk With Symbol") + /* TODO: ViewModel 연결 */ + SymbolSearchTextField( + value = viewModel.queryInput, + onValueChange = { viewModel.setQuery(it) } + ) + + SelectedSymbolsBox( + selectedSymbols = viewModel.selectedSymbols, + onClear = { viewModel.clear(it) }, + onClearAll = { viewModel.clearAll() } + ) + + Box( + modifier = Modifier + .fillMaxSize() + .background( + color = MaterialTheme.colorScheme.surfaceVariant, + shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp) + ) + .padding(16.dp) + ) { + LazyVerticalGrid( + columns = GridCells.Adaptive(140.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + items(viewModel.entries) { entry -> + when (entry) { + is Symbol -> SymbolUi( + symbol = entry, + onSelect = { viewModel.selectSymbol(entry) }, + onFavoriteChange = { viewModel.toggleFavorite(entry, it) } + ) + + is Category -> CategoryUi( + category = entry, + onSelect = { viewModel.selectCategory(entry) } + ) + } + } + } + } } } } diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/utils/CategoryUi.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/utils/CategoryUi.kt new file mode 100644 index 00000000..317e95ff --- /dev/null +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/utils/CategoryUi.kt @@ -0,0 +1,93 @@ +package com.example.speechbuddy.compose.utils + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +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.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.example.speechbuddy.R +import com.example.speechbuddy.domain.models.Category +import com.example.speechbuddy.ui.SpeechBuddyTheme + +/** + * Custom UI designed for Category + * + * @param category data class Category to be passed to the UI + * @param modifier the Modifier to be applied to this outlined card + * @param onSelect called when this Category is clicked + */ +@ExperimentalMaterial3Api +@Composable +fun CategoryUi( + category: Category, modifier: Modifier = Modifier, onSelect: () -> Unit +) { + Card( + onClick = onSelect, + modifier = modifier.size(140.dp), + shape = RoundedCornerShape(10.dp), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.background + ) + ) { + Box { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Image( + painter = painterResource(id = category.imageResId), + contentDescription = category.text, + modifier = Modifier.height(95.dp), + contentScale = ContentScale.FillHeight + ) + + Box( + modifier = Modifier + .fillMaxWidth() + .height(45.dp) + .background(color = MaterialTheme.colorScheme.tertiaryContainer) + .padding(horizontal = 10.dp), contentAlignment = Alignment.Center + ) { + Text( + text = category.text, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodySmall + ) + } + } + } + } +} + +@Preview(showBackground = true, backgroundColor = 0) +@ExperimentalMaterial3Api +@Composable +fun CategoryUiPreview() { + val previewCategory = Category( + id = 1, + text = "인사사회어", + imageResId = R.drawable.category_1 + ) + + SpeechBuddyTheme { + CategoryUi(category = previewCategory, onSelect = {}) + } +} \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/utils/SymbolUi.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/utils/SymbolUi.kt new file mode 100644 index 00000000..e6f1dbd7 --- /dev/null +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/utils/SymbolUi.kt @@ -0,0 +1,130 @@ +package com.example.speechbuddy.compose.utils + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Favorite +import androidx.compose.material.icons.filled.FavoriteBorder +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButtonDefaults +import androidx.compose.material3.IconToggleButton +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.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.example.speechbuddy.R +import com.example.speechbuddy.domain.models.Symbol +import com.example.speechbuddy.ui.SpeechBuddyTheme +import com.example.speechbuddy.utils.Constants.Companion.MAXIMUM_LINES_FOR_SYMBOL_TEXT + +/** + * Custom UI designed for Symbol + * + * @param symbol data class Symbol to be passed to the UI + * @param modifier the Modifier to be applied to this outlined card + * @param onSelect called when this Symbol is clicked + * @param onFavoriteChange called when the upper left icon of this Symbol is clicked + */ +@ExperimentalMaterial3Api +@Composable +fun SymbolUi( + symbol: Symbol, + modifier: Modifier = Modifier, + onSelect: () -> Unit, + onFavoriteChange: (Boolean) -> Unit +) { + Card( + onClick = onSelect, + modifier = modifier.size(140.dp), + shape = RoundedCornerShape(10.dp), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.background + ) + ) { + Box(contentAlignment = Alignment.TopEnd) { + IconToggleButton( + checked = symbol.isFavorite, + onCheckedChange = onFavoriteChange, + modifier = Modifier + .size(24.dp) + .padding(4.dp), + colors = IconButtonDefaults.iconToggleButtonColors( + contentColor = MaterialTheme.colorScheme.onBackground, + checkedContentColor = MaterialTheme.colorScheme.error + ) + ) { + Icon( + imageVector = when (symbol.isFavorite) { + true -> Icons.Default.Favorite + false -> Icons.Default.FavoriteBorder + }, + contentDescription = stringResource(id = R.string.favorite) + ) + } + + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Image( + painter = painterResource(id = symbol.imageResId), + contentDescription = symbol.text, + modifier = Modifier.height(95.dp), + contentScale = ContentScale.FillHeight + ) + + Box( + modifier = Modifier + .fillMaxWidth() + .height(45.dp) + .background(color = MaterialTheme.colorScheme.secondaryContainer) + .padding(horizontal = 10.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = symbol.text, + textAlign = TextAlign.Center, + maxLines = MAXIMUM_LINES_FOR_SYMBOL_TEXT, + style = MaterialTheme.typography.bodySmall + ) + } + } + } + } +} + +@Preview(showBackground = true, backgroundColor = 0) +@ExperimentalMaterial3Api +@Composable +fun SymbolUiPreview() { + val previewSymbol = Symbol( + id = 1, + text = "119에 전화해주세요", + imageResId = R.drawable.symbol_1, + categoryId = 1, + isFavorite = true, + isMine = false + ) + + SpeechBuddyTheme { + SymbolUi(symbol = previewSymbol, onSelect = {}, onFavoriteChange = {}) + } +} \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/domain/models/Entry.kt b/frontend/app/src/main/java/com/example/speechbuddy/domain/models/Entry.kt new file mode 100644 index 00000000..84078cbc --- /dev/null +++ b/frontend/app/src/main/java/com/example/speechbuddy/domain/models/Entry.kt @@ -0,0 +1,27 @@ +package com.example.speechbuddy.domain.models + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +interface Entry { + val id: Int + val text: String + val imageResId: Int +} + +@Parcelize +data class Category( + override val id: Int, + override val text: String, + override val imageResId: Int +) : Parcelable, Entry + +@Parcelize +data class Symbol( + override val id: Int, + override val text: String, + override val imageResId: Int, + val categoryId: Int, + val isFavorite: Boolean, + val isMine: Boolean +) : Parcelable, Entry \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/ui/Type.kt b/frontend/app/src/main/java/com/example/speechbuddy/ui/Type.kt index f96cf614..e9a80441 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/ui/Type.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/ui/Type.kt @@ -94,8 +94,8 @@ val Typography = Typography( ), labelSmall = TextStyle( fontFamily = FontFamily.Default, - fontWeight = FontWeight.Medium, - fontSize = 11.sp, - lineHeight = 16.sp, + fontWeight = FontWeight.Normal, + fontSize = 10.sp, + lineHeight = 12.sp, ), ) 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 e99154ab..4d130f96 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 @@ -7,5 +7,6 @@ class Constants { const val MINIMUM_PASSWORD_LENGTH = 8 const val MAXIMUM_NICKNAME_LENGTH = 15 const val CODE_LENGTH = 6 + const val MAXIMUM_LINES_FOR_SYMBOL_TEXT = 2 } } \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/SymbolSelectionViewModel.kt b/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/SymbolSelectionViewModel.kt new file mode 100644 index 00000000..7554032d --- /dev/null +++ b/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/SymbolSelectionViewModel.kt @@ -0,0 +1,96 @@ +package com.example.speechbuddy.viewmodel + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import com.example.speechbuddy.R +import com.example.speechbuddy.domain.models.Category +import com.example.speechbuddy.domain.models.Entry +import com.example.speechbuddy.domain.models.Symbol +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class SymbolSelectionViewModel @Inject internal constructor() : ViewModel() { + + var queryInput by mutableStateOf("") + private set + + var selectedSymbols by mutableStateOf(listOf()) + private set + + var entries by mutableStateOf(listOf()) + private set + + /* TODO: 나중에 삭제 */ + private val initialEntries = listOf( + Symbol( + id = 0, + text = "119에 전화해주세요", + imageResId = R.drawable.symbol_1, + categoryId = 0, + isFavorite = true, + isMine = false + ), + Symbol( + id = 0, + text = "119에 전화해주세요", + imageResId = R.drawable.symbol_1, + categoryId = 0, + isFavorite = true, + isMine = false + ) + ) + + private val secondaryEntries = listOf( + Symbol( + id = 0, + text = "119에 전화해주세요", + imageResId = R.drawable.symbol_1, + categoryId = 0, + isFavorite = true, + isMine = false + ), + Symbol( + id = 0, + text = "우와", + imageResId = R.drawable.symbol_1, + categoryId = 0, + isFavorite = true, + isMine = false + ) + ) + + init { + entries = initialEntries + } + + fun setQuery(input: String) { + queryInput = input + } + + fun clear(symbol: Symbol) { + val mutableSelectedSymbols = selectedSymbols.toMutableList() + mutableSelectedSymbols.remove(symbol) + selectedSymbols = mutableSelectedSymbols.toList() + } + + fun clearAll() { + selectedSymbols = listOf() + entries = initialEntries + } + + fun selectSymbol(symbol: Symbol) { + selectedSymbols = selectedSymbols.plus(symbol) + entries = secondaryEntries + } + + fun toggleFavorite(symbol: Symbol, value: Boolean) { + /* TODO */ + } + + fun selectCategory(category: Category) { + /* TODO */ + } +} \ No newline at end of file diff --git a/frontend/app/src/main/res/drawable/category_1.png b/frontend/app/src/main/res/drawable/category_1.png new file mode 100644 index 00000000..3c22c2b7 Binary files /dev/null and b/frontend/app/src/main/res/drawable/category_1.png differ diff --git a/frontend/app/src/main/res/drawable/symbol_1.png b/frontend/app/src/main/res/drawable/symbol_1.png new file mode 100644 index 00000000..f1531541 Binary files /dev/null and b/frontend/app/src/main/res/drawable/symbol_1.png differ diff --git a/frontend/app/src/main/res/values/strings.xml b/frontend/app/src/main/res/values/strings.xml index 60c83f74..a9262c16 100644 --- a/frontend/app/src/main/res/values/strings.xml +++ b/frontend/app/src/main/res/values/strings.xml @@ -43,6 +43,10 @@ 닉네임을 입력해주세요 로그인 오류 회원가입 오류 + 검색어를 입력하세요 + 즐겨찾기 + 상징 선택 취소 + 전체 삭제 소리로 말해보아요 키보드로 텍스트를 입력해주세요 재생하기