Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT] [#9] 키워드 입력 화면 UI 구현 #32

Merged
merged 2 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions core/designsystem/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,14 @@
<string name="camera_permission_denied">"카메라 권한을 거부하였습니다. 앱 설정으로 이동하여 권한을 부여할 수 있습니다."</string>
<string name="select_photo">사진을 선택해 주세요.</string>

<!-- input keyword-->
<string name="input_keyword_top_title">키워드 입력</string>
<string name="what_style_prefer">어떤 스타일을\n선호하시나요?</string>
<string name="creates_image_based_on_selected_style">선택한 스타일을 기반으로 이미지를 생성합니다.</string>
<string name="keyword_dreamy">#몽환적인</string>
<string name="keyword_lonely">#고독한</string>
<string name="keyword_natural">#자연적인</string>
<string name="keyword_sketch">#스케치</string>
<string name="create_ai_profile_image">AI 프로필 이미지 생성하기</string>

</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ package com.nexters.ilab.core.ui.component
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Person
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalInspectionMode
Expand Down Expand Up @@ -98,6 +101,36 @@ fun BackgroundImage(
}
}

@Composable
fun KeywordImage(
resId: Int,
contentDescription: String,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current

if (LocalInspectionMode.current) {
Icon(
imageVector = Icons.Outlined.Person,
contentDescription = "Keyword Image Icon",
modifier = Modifier
.clip(RoundedCornerShape(12.dp))
.fillMaxWidth()
.aspectRatio(1f),
)
} else {
AsyncImage(
model = ImageRequest.Builder(context)
.data(resId)
.crossfade(true)
.build(),
contentDescription = contentDescription,
contentScale = ContentScale.Fit,
modifier = modifier,
)
}
}

@ComponentPreview
@Composable
fun ExampleImagePreview() {
Expand All @@ -124,3 +157,12 @@ fun BackgroundImagePreview() {
contentDescription = "Background Image Icon",
)
}

@ComponentPreview
@Composable
fun KeywordImagePreview() {
KeywordImage(
resId = 0,
contentDescription = "Keyword Image Icon",
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.nexters.ilab.android.feature.home.navigation.HOME_ROUTE
import com.nexters.ilab.android.feature.home.navigation.navigateToHome
import com.nexters.ilab.android.feature.mypage.navigation.navigateToMyPage
import com.nexters.ilab.android.feature.setting.navigation.navigateToSetting
import com.nexters.ilab.android.feature.uploadphoto.navigation.navigateToInputKeyword

internal class MainNavController(
val navController: NavHostController,
Expand Down Expand Up @@ -49,6 +50,10 @@ internal class MainNavController(
navController.navigateToUploadCheck()
}

fun navigateToInputKeyword() {
navController.navigateToInputKeyword()
}

fun navigateToSetting() {
navController.navigateToSetting()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ internal fun MainScreen(
navController = navigator.navController,
onBackClick = navigator::popBackStackIfNotHome,
onNavigateToUploadCheck = navigator::navigateToUploadCheck,
onNavigateToInputKeyword = navigator::navigateToInputKeyword,
)

myPageNavGraph(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package com.nexters.ilab.android.feature.uploadphoto

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.nexters.ilab.android.core.designsystem.R
import com.nexters.ilab.android.core.designsystem.theme.Blue600
import com.nexters.ilab.android.core.designsystem.theme.Contents1
import com.nexters.ilab.android.core.designsystem.theme.Gray500
import com.nexters.ilab.android.core.designsystem.theme.Subtitle1
import com.nexters.ilab.android.core.designsystem.theme.Title1
import com.nexters.ilab.core.ui.DevicePreview
import com.nexters.ilab.core.ui.component.ILabButton
import com.nexters.ilab.core.ui.component.ILabTopAppBar
import com.nexters.ilab.core.ui.component.KeywordImage
import com.nexters.ilab.core.ui.component.TopAppBarNavigationType
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf

@Composable
internal fun InputKeywordRoute(
onBackClick: () -> Unit,
viewModel: UploadPhotoViewModel,
) {
val uiState by viewModel.container.stateFlow.collectAsStateWithLifecycle()

InputKeywordScreen(
uiState = uiState,
onBackClick = onBackClick,
onKeywordSelect = viewModel::setSelectedKeyword,
createProfileImage = {},
)
}

@Composable
internal fun InputKeywordScreen(
uiState: UploadPhotoState,
onBackClick: () -> Unit,
onKeywordSelect: (String) -> Unit,
createProfileImage: () -> Unit,
) {
Column {
InputKeywordTopAppBar(onBackClick = onBackClick)
InputKeywordContent(
isKeywordSelected = uiState.selectedKeyword.isNotEmpty(),
onKeywordSelect = onKeywordSelect,
createProfileImage = createProfileImage,
)
}
}

@Composable
internal fun InputKeywordTopAppBar(
onBackClick: () -> Unit,
) {
ILabTopAppBar(
titleRes = R.string.input_keyword_top_title,
navigationType = TopAppBarNavigationType.Back,
navigationIconContentDescription = "navigation Icon",
modifier = Modifier
.statusBarsPadding()
.height(56.dp),
onNavigationClick = onBackClick,
)
}

@Composable
internal fun InputKeywordContent(
isKeywordSelected: Boolean,
createProfileImage: () -> Unit,
onKeywordSelect: (String) -> Unit,
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 20.dp),
) {
Spacer(modifier = Modifier.height(32.dp))
Text(
text = stringResource(id = R.string.what_style_prefer),
style = Title1,
color = Color.Black,
)
Spacer(modifier = Modifier.height(20.dp))
Text(
text = stringResource(id = R.string.creates_image_based_on_selected_style),
style = Contents1,
color = Gray500,
)
Spacer(modifier = Modifier.height(60.dp))
CheckableKeywordImage(
images = keywordImages,
onKeywordSelect = onKeywordSelect,
)
Spacer(modifier = Modifier.weight(1f))
ILabButton(
onClick = createProfileImage,
modifier = Modifier
.fillMaxWidth()
.navigationBarsPadding()
.padding(bottom = 18.dp)
.height(60.dp),
enabled = isKeywordSelected,
containerColor = Blue600,
contentColor = Color.White,
text = {
Text(
text = stringResource(id = R.string.create_ai_profile_image),
style = Subtitle1,
)
},
)
}
}

val keywordImages = persistentListOf(
Pair(R.drawable.img_keyword_dreamy, "dreamy image"),
Pair(R.drawable.img_keyword_lonely, "lonely image"),
Pair(R.drawable.img_keyword_natural, "natural image"),
Pair(R.drawable.img_keyword_sketch, "sketch image"),
)

@Composable
fun CheckableKeywordImage(
images: ImmutableList<Pair<Int, String>>,
onKeywordSelect: (String) -> Unit,
) {
LazyVerticalGrid(
columns = GridCells.Fixed(count = 2),
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
items(count = images.size) { index ->
KeywordImage(
resId = images[index].first,
contentDescription = images[index].second,
modifier = Modifier
.clip(RoundedCornerShape(12.dp))
.fillMaxWidth()
.aspectRatio(1f)
.clickable {
onKeywordSelect(images[index].second)
},
)
}
}
}

@DevicePreview
@Composable
fun InputKeywordScreenPreview() {
InputKeywordScreen(
uiState = UploadPhotoState(),
onBackClick = {},
onKeywordSelect = {},
createProfileImage = {},
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ import com.nexters.ilab.core.ui.component.ILabTopAppBar
import com.nexters.ilab.core.ui.component.NetworkImage
import com.nexters.ilab.core.ui.component.TopAppBarNavigationType

@Suppress("unused")
@Composable
internal fun UploadCheckRoute(
onBackClick: () -> Unit,
onNavigateToInputKeyword: () -> Unit,
viewModel: UploadPhotoViewModel = hiltViewModel(),
) {
val uiState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
Expand All @@ -59,7 +59,7 @@ internal fun UploadCheckRoute(
val singlePhotoPickerLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.PickVisualMedia(),
onResult = { uri ->
uri?.let { viewModel.setSelectImageUri(it.toString()) }
uri?.let { viewModel.setSelectedImageUri(it.toString()) }
},
)

Expand All @@ -73,7 +73,7 @@ internal fun UploadCheckRoute(
val cameraLauncher = rememberLauncherForActivityResult(ActivityResultContracts.TakePicturePreview()) { bitmap ->
bitmap?.let {
val photoUri = it.toUri(context)
viewModel.setSelectImageUri(photoUri.toString())
viewModel.setSelectedImageUri(photoUri.toString())
}
}

Expand Down Expand Up @@ -105,6 +105,7 @@ internal fun UploadCheckRoute(
toggleUploadPhotoDialog = viewModel::toggleUploadPhotoDialog,
openPhotoPicker = viewModel::openPhotoPicker,
requestCameraPermission = viewModel::requestCameraPermission,
onNavigateToInputKeyword = onNavigateToInputKeyword,
)
}

Expand All @@ -115,6 +116,7 @@ private fun UploadCheckScreen(
toggleUploadPhotoDialog: (Boolean) -> Unit,
openPhotoPicker: () -> Unit,
requestCameraPermission: () -> Unit,
onNavigateToInputKeyword: () -> Unit,
) {
if (uiState.isUploadPhotoDialogVisible) {
UploadPhotoDialog(
Expand All @@ -129,6 +131,7 @@ private fun UploadCheckScreen(
UploadCheckContent(
selectedPhotoUri = uiState.selectedPhotoUri,
toggleUploadPhotoDialog = toggleUploadPhotoDialog,
onNavigateToInputKeyword = onNavigateToInputKeyword,
)
}
}
Expand All @@ -152,11 +155,12 @@ private fun UploadCheckTopAppBar(
private fun UploadCheckContent(
selectedPhotoUri: String,
toggleUploadPhotoDialog: (Boolean) -> Unit,
onNavigateToInputKeyword: () -> Unit,
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 16.dp),
.padding(horizontal = 20.dp),
) {
Spacer(modifier = Modifier.height(32.dp))
Text(
Expand Down Expand Up @@ -209,7 +213,7 @@ private fun UploadCheckContent(
modifier = Modifier
.fillMaxWidth()
.navigationBarsPadding()
.padding(start = 4.dp, end = 4.dp, bottom = 18.dp),
.padding(bottom = 18.dp),
) {
ILabButton(
onClick = {
Expand All @@ -229,7 +233,7 @@ private fun UploadCheckContent(
},
)
ILabButton(
onClick = {},
onClick = onNavigateToInputKeyword,
modifier = Modifier
.weight(1f)
.height(60.dp)
Expand Down Expand Up @@ -280,5 +284,6 @@ fun UploadCheckScreenPreview() {
toggleUploadPhotoDialog = {},
openPhotoPicker = {},
requestCameraPermission = {},
onNavigateToInputKeyword = {},
)
}
Loading
Loading