diff --git a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/space/generate/AddSpaceActivity.kt b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/space/generate/AddSpaceActivity.kt index 84193a1b..56f2a3b1 100644 --- a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/space/generate/AddSpaceActivity.kt +++ b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/space/generate/AddSpaceActivity.kt @@ -1,70 +1,17 @@ package boostcamp.and07.mindsync.ui.space.generate -import android.content.pm.PackageManager import android.net.Uri import androidx.activity.viewModels -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import boostcamp.and07.mindsync.R -import boostcamp.and07.mindsync.databinding.ActivityAddSpaceBinding -import boostcamp.and07.mindsync.ui.base.BaseActivity -import boostcamp.and07.mindsync.ui.base.BaseActivityViewModel -import boostcamp.and07.mindsync.ui.space.SpaceUiEvent -import boostcamp.and07.mindsync.ui.util.ImagePickerHandler -import boostcamp.and07.mindsync.ui.util.setClickEvent +import androidx.compose.runtime.Composable +import boostcamp.and07.mindsync.ui.base.BaseComposeActivity +import boostcamp.and07.mindsync.ui.theme.MindSyncTheme import boostcamp.and07.mindsync.ui.util.toAbsolutePath import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.launch import java.io.File @AndroidEntryPoint -class AddSpaceActivity : BaseActivity(R.layout.activity_add_space) { +class AddSpaceActivity : BaseComposeActivity() { private val addSpaceViewModel: AddSpaceViewModel by viewModels() - private val imagePickerHandler = - ImagePickerHandler(this) { uri -> - createImage(uri) - } - - override fun init() { - setBinding() - setBackBtn() - collectSpaceEvent() - setClickEventThrottle() - } - - override fun getViewModel(): BaseActivityViewModel { - return addSpaceViewModel - } - - private fun setBinding() { - binding.vm = addSpaceViewModel - } - - private fun setClickEventThrottle() { - binding.imgbtnUpdateSpaceThumbnail.setClickEvent(lifecycleScope) { - imagePickerHandler.checkPermissionsAndLaunchImagePicker() - } - } - - private fun collectSpaceEvent() { - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - addSpaceViewModel.event.collectLatest { spaceEvent -> - when (spaceEvent) { - is SpaceUiEvent.SuccessAdd -> { - finish() - } - is SpaceUiEvent.ShowMessage -> { // 에러일 경우가 잇나? - showMessage(spaceEvent.message) - } - else -> {} - } - } - } - } - } private fun createImage(uri: Uri?) { uri?.let { uri -> @@ -74,22 +21,16 @@ class AddSpaceActivity : BaseActivity(R.layout.activity } } - private fun setBackBtn() { - binding.imgbtnAddSpaceBack.setOnClickListener { - finish() - } - } - - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray, - ) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - if (requestCode == ImagePickerHandler.REQUEST_CODE_PERMISSIONS && - grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED - ) { - imagePickerHandler.launchImagePicker() + @Composable + override fun Content() { + MindSyncTheme { + AddSpaceScreen( + onBackClicked = { this.finish() }, + addSpaceViewModel = addSpaceViewModel, + createSpace = { addSpaceViewModel.addSpace(it) }, + updateSpaceName = { addSpaceViewModel.onSpaceNameChanged(it, 0, 0, 0) }, + createImage = { createImage(it) }, + ) } } } diff --git a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/space/generate/AddSpaceScreen.kt b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/space/generate/AddSpaceScreen.kt new file mode 100644 index 00000000..30de5b3b --- /dev/null +++ b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/space/generate/AddSpaceScreen.kt @@ -0,0 +1,299 @@ +package boostcamp.and07.mindsync.ui.space.generate + +import android.net.Uri +import androidx.activity.compose.ManagedActivityResultLauncher +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import boostcamp.and07.mindsync.R +import boostcamp.and07.mindsync.ui.space.SpaceUiEvent +import boostcamp.and07.mindsync.ui.space.SpaceUiState +import boostcamp.and07.mindsync.ui.theme.Blue1 +import boostcamp.and07.mindsync.ui.theme.MindSyncTheme +import boostcamp.and07.mindsync.ui.theme.Yellow4 +import coil.compose.AsyncImage +import kotlinx.coroutines.flow.collectLatest + +@Composable +fun AddSpaceScreen( + addSpaceViewModel: AddSpaceViewModel, + onBackClicked: () -> Unit, + createSpace: (String) -> Unit, + updateSpaceName: (String) -> Unit, + createImage: (Uri) -> Unit, +) { + val uiState by addSpaceViewModel.uiState.collectAsStateWithLifecycle() + val snackBarHostState = remember { SnackbarHostState() } + HandleAddSpaceEvents( + addSpaceViewModel = addSpaceViewModel, + onBack = onBackClicked, + snackBarHostState = snackBarHostState, + ) + Scaffold( + containerColor = Yellow4, + ) { innerPadding -> + BoxWithConstraints( + modifier = + Modifier + .padding(innerPadding) + .fillMaxWidth(), + ) { + AddSpaceContent( + uiState = uiState, + onBackClicked = onBackClicked, + createSpace = createSpace, + updateSpaceName = updateSpaceName, + createImage = createImage, + ) + } + } +} + +@Composable +fun AddSpaceContent( + uiState: SpaceUiState = SpaceUiState(), + onBackClicked: () -> Unit = {}, + createSpace: (String) -> Unit = {}, + updateSpaceName: (String) -> Unit = {}, + createImage: (Uri) -> Unit = {}, +) { + val imageLauncher = + rememberLauncherForActivityResult( + contract = ActivityResultContracts.PickVisualMedia(), + onResult = { uri -> + uri?.let { imageThumbnail -> + createImage(imageThumbnail) + } + }, + ) + Row(verticalAlignment = Alignment.CenterVertically) { + AddSpaceTopBar(onBackClicked) + } + Row( + modifier = + Modifier + .padding(top = 50.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.Absolute.Center, + ) { + AddSpaceInfo() + } + Row( + modifier = + Modifier + .fillMaxWidth() + .padding(top = 150.dp), + horizontalArrangement = Arrangement.Absolute.Center, + ) { + AddSpaceThumbnail(onImageClicked = imageLauncher, imageUrl = uiState.spaceThumbnail) + } + Row( + modifier = + Modifier + .fillMaxWidth() + .padding(top = 300.dp, start = 20.dp, end = 20.dp), + horizontalArrangement = Arrangement.Center, + ) { + InputSpaceNameField(uiState = uiState, updateSpaceName = updateSpaceName) + } + Row( + modifier = + Modifier + .padding(top = 450.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.Center, + ) { + SpaceNameInputButton(createSpace = createSpace, uiState.spaceName) + } +} + +@Composable +private fun HandleAddSpaceEvents( + addSpaceViewModel: AddSpaceViewModel, + onBack: () -> Unit, + snackBarHostState: SnackbarHostState, +) { + LaunchedEffect(addSpaceViewModel.event) { + addSpaceViewModel.event.collectLatest { event -> + when (event) { + is SpaceUiEvent.SuccessAdd -> onBack() + + is SpaceUiEvent.ShowMessage -> snackBarHostState.showSnackbar(event.message) + else -> {} + } + } + } +} + +@Composable +fun AddSpaceTopBar(onBackClicked: () -> Unit) { + Row { + IconButton( + modifier = + Modifier + .size(25.dp) + .padding(1.dp), + onClick = onBackClicked, + ) { + Image( + painter = painterResource(id = R.drawable.ic_back), + contentDescription = null, + ) + } + Text( + text = stringResource(id = R.string.generate_space_menu_message), + style = MaterialTheme.typography.displaySmall, + modifier = Modifier.padding(start = 14.dp, top = 1.dp), + ) + } +} + +@Composable +fun AddSpaceInfo() { + Text( + text = stringResource(id = R.string.generate_space_title), + style = MaterialTheme.typography.displayMedium, + textAlign = TextAlign.Center, + ) +} + +@Composable +fun AddSpaceThumbnail( + onImageClicked: ManagedActivityResultLauncher, + imageUrl: String, +) { + Box( + modifier = + Modifier + .size(120.dp), + ) { + AsyncImage( + model = imageUrl, + contentDescription = null, + placeholder = painterResource(id = R.drawable.ic_placeholder), + error = painterResource(id = R.drawable.ic_placeholder), + modifier = + Modifier + .clickable { + onImageClicked.launch( + PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly), + ) + } + .clip(CircleShape), + contentScale = ContentScale.Crop, + ) + Box( + Modifier + .size(25.dp) + .clip(shape = RoundedCornerShape(5.dp)) + .background(color = Blue1) + .align(Alignment.TopEnd), + ) { + IconButton(onClick = { /*TODO*/ }) { + Image( + painterResource(id = R.drawable.ic_add_board), + contentDescription = null, + ) + } + } + } +} + +@Composable +fun InputSpaceNameField( + uiState: SpaceUiState, + updateSpaceName: (String) -> Unit, +) { + val spaceHint = stringResource(id = R.string.space_name_hint) + var spaceName by remember { + mutableStateOf( + TextFieldValue( + text = uiState.spaceName, + ), + ) + } + TextField( + modifier = Modifier.fillMaxWidth().padding(20.dp), + value = spaceName, + onValueChange = { + spaceName = it + updateSpaceName(it.text) + }, + supportingText = { + Text( + text = "${uiState.spaceName.length}", + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.End, + ) + }, + placeholder = { Text(spaceHint) }, + ) +} + +@Composable +fun SpaceNameInputButton( + createSpace: (String) -> Unit, + spaceName: String, +) { + val icon = stringResource(id = R.string.space_image_name) + Button( + onClick = { + createSpace(icon) + }, + enabled = spaceName.length in 1..20, + modifier = Modifier.width(264.dp), + colors = + ButtonDefaults.buttonColors( + disabledContainerColor = Color.LightGray, + ), + ) { + Text(text = stringResource(id = R.string.check_message)) + } +} + +@Preview(showSystemUi = true, showBackground = true) +@Composable +private fun AddSpacePreview() { + MindSyncTheme { + AddSpaceContent() + } +} diff --git a/AOS/app/src/main/res/drawable/ic_placeholder.xml b/AOS/app/src/main/res/drawable/ic_placeholder.xml new file mode 100644 index 00000000..f7d841b8 --- /dev/null +++ b/AOS/app/src/main/res/drawable/ic_placeholder.xml @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AOS/app/src/main/res/layout/activity_add_space.xml b/AOS/app/src/main/res/layout/activity_add_space.xml deleted file mode 100644 index d22dfe3a..00000000 --- a/AOS/app/src/main/res/layout/activity_add_space.xml +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file