From 8074bf3cceb1bd3b995f79b85e8fa4e35e4ccc9f Mon Sep 17 00:00:00 2001 From: Sehwan Yun <39579912+l5x5l@users.noreply.github.com> Date: Sun, 14 Jul 2024 14:56:12 +0900 Subject: [PATCH] =?UTF-8?q?[Feature]=20#13=20=ED=8F=AC=ED=82=B7=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=ED=99=94=EB=A9=B4=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?(#15)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [BASE] #13 feature:addpokit 모듈 생성 * [BASE] #13 이미지 선택부분을 제외한 포킷 추가 화면 구현 * [FEATURE] #13 포킷 프로필 선택부분 구현 * [CHORE] #13 ktlint 적용 * [CHORE] #13 포킷 이름 최대 글자수 상수로 분리 * [CHORE] #13 core 모듈 resource를 사용하는 부분을 alias import로 변경 --- feature/addpokit/.gitignore | 1 + feature/addpokit/build.gradle.kts | 64 +++++ feature/addpokit/consumer-rules.pro | 0 feature/addpokit/proguard-rules.pro | 21 ++ .../addpokit/ExampleInstrumentedTest.kt | 22 ++ feature/addpokit/src/main/AndroidManifest.xml | 4 + .../strayalpaca/addpokit/AddPokitScreen.kt | 259 ++++++++++++++++++ .../strayalpaca/addpokit/AddPokitViewModel.kt | 116 ++++++++ .../java/com/strayalpaca/addpokit/Preview.kt | 24 ++ .../components/atom/PokitProfileImage.kt | 49 ++++ .../addpokit/components/block/Toolbar.kt | 54 ++++ .../com/strayalpaca/addpokit/const/Consts.kt | 3 + .../addpokit/model/AddPokitScreenState.kt | 29 ++ .../com/strayalpaca/addpokit/model/Pokit.kt | 15 + .../addpokit/model/PokitProfile.kt | 13 + .../addpokit/utils/BackPressHandler.kt | 35 +++ .../addpokit/src/main/res/values/string.xml | 11 + .../strayalpaca/addpokit/ExampleUnitTest.kt | 16 ++ 18 files changed, 736 insertions(+) create mode 100644 feature/addpokit/.gitignore create mode 100644 feature/addpokit/build.gradle.kts create mode 100644 feature/addpokit/consumer-rules.pro create mode 100644 feature/addpokit/proguard-rules.pro create mode 100644 feature/addpokit/src/androidTest/java/com/strayalpaca/addpokit/ExampleInstrumentedTest.kt create mode 100644 feature/addpokit/src/main/AndroidManifest.xml create mode 100644 feature/addpokit/src/main/java/com/strayalpaca/addpokit/AddPokitScreen.kt create mode 100644 feature/addpokit/src/main/java/com/strayalpaca/addpokit/AddPokitViewModel.kt create mode 100644 feature/addpokit/src/main/java/com/strayalpaca/addpokit/Preview.kt create mode 100644 feature/addpokit/src/main/java/com/strayalpaca/addpokit/components/atom/PokitProfileImage.kt create mode 100644 feature/addpokit/src/main/java/com/strayalpaca/addpokit/components/block/Toolbar.kt create mode 100644 feature/addpokit/src/main/java/com/strayalpaca/addpokit/const/Consts.kt create mode 100644 feature/addpokit/src/main/java/com/strayalpaca/addpokit/model/AddPokitScreenState.kt create mode 100644 feature/addpokit/src/main/java/com/strayalpaca/addpokit/model/Pokit.kt create mode 100644 feature/addpokit/src/main/java/com/strayalpaca/addpokit/model/PokitProfile.kt create mode 100644 feature/addpokit/src/main/java/com/strayalpaca/addpokit/utils/BackPressHandler.kt create mode 100644 feature/addpokit/src/main/res/values/string.xml create mode 100644 feature/addpokit/src/test/java/com/strayalpaca/addpokit/ExampleUnitTest.kt diff --git a/feature/addpokit/.gitignore b/feature/addpokit/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/feature/addpokit/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/addpokit/build.gradle.kts b/feature/addpokit/build.gradle.kts new file mode 100644 index 00000000..0c700c02 --- /dev/null +++ b/feature/addpokit/build.gradle.kts @@ -0,0 +1,64 @@ +plugins { + alias(libs.plugins.com.android.library) + alias(libs.plugins.org.jetbrains.kotlin.android) +} + +android { + namespace = "com.strayalpaca.addpokit" + compileSdk = 34 + + defaultConfig { + minSdk = 24 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = "1.5.1" + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.activity.compose) + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.ui) + implementation(libs.androidx.ui.graphics) + implementation(libs.androidx.ui.tooling.preview) + implementation(libs.androidx.material3) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(platform(libs.androidx.compose.bom)) + androidTestImplementation(libs.androidx.ui.test.junit4) + debugImplementation(libs.androidx.ui.tooling) + debugImplementation(libs.androidx.ui.test.manifest) + + implementation(libs.orbit.compose) + implementation(libs.orbit.core) + implementation(libs.orbit.viewmodel) + + implementation(project(":core:ui")) +} diff --git a/feature/addpokit/consumer-rules.pro b/feature/addpokit/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/feature/addpokit/proguard-rules.pro b/feature/addpokit/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/feature/addpokit/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/feature/addpokit/src/androidTest/java/com/strayalpaca/addpokit/ExampleInstrumentedTest.kt b/feature/addpokit/src/androidTest/java/com/strayalpaca/addpokit/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..943ab772 --- /dev/null +++ b/feature/addpokit/src/androidTest/java/com/strayalpaca/addpokit/ExampleInstrumentedTest.kt @@ -0,0 +1,22 @@ +package com.strayalpaca.addpokit + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.strayalpaca.addpokit.test", appContext.packageName) + } +} diff --git a/feature/addpokit/src/main/AndroidManifest.xml b/feature/addpokit/src/main/AndroidManifest.xml new file mode 100644 index 00000000..a5918e68 --- /dev/null +++ b/feature/addpokit/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/feature/addpokit/src/main/java/com/strayalpaca/addpokit/AddPokitScreen.kt b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/AddPokitScreen.kt new file mode 100644 index 00000000..120d66be --- /dev/null +++ b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/AddPokitScreen.kt @@ -0,0 +1,259 @@ +package com.strayalpaca.addpokit + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +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.lazy.items +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.strayalpaca.addpokit.components.atom.PokitProfileImage +import com.strayalpaca.addpokit.components.block.Toolbar +import com.strayalpaca.addpokit.model.AddPokitScreenState +import com.strayalpaca.addpokit.model.AddPokitScreenStep +import com.strayalpaca.addpokit.model.AddPokitSideEffect +import com.strayalpaca.addpokit.model.Pokit +import com.strayalpaca.addpokit.model.PokitProfile +import com.strayalpaca.addpokit.model.samplePokitProfileList +import com.strayalpaca.addpokit.utils.BackPressHandler +import org.orbitmvi.orbit.compose.collectSideEffect +import pokitmons.pokit.core.ui.components.atom.button.PokitButton +import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonSize +import pokitmons.pokit.core.ui.components.block.labeledinput.LabeledInput +import pokitmons.pokit.core.ui.components.block.pokitlist.PokitList +import pokitmons.pokit.core.ui.components.block.pokitlist.attributes.PokitListState +import pokitmons.pokit.core.ui.components.template.bottomsheet.PokitBottomSheet +import pokitmons.pokit.core.ui.theme.PokitTheme +import pokitmons.pokit.core.ui.R.drawable as coreDrawable +import pokitmons.pokit.core.ui.R.string as coreString + +@Composable +fun AddPokitScreenContainer( + viewModel: AddPokitViewModel, + onBackPressed: () -> Unit, +) { + val state by viewModel.container.stateFlow.collectAsState() + val pokitName by viewModel.pokitName.collectAsState() + + val saveButtonEnable = remember { + derivedStateOf { + state.step != AddPokitScreenStep.POKIT_SAVE_LOADING && + state.step != AddPokitScreenStep.POKIT_LIST_LOADING && + state.pokitInputErrorMessage == null && + state.pokitProfile != null + } + } + + viewModel.collectSideEffect { sideEffect -> + when (sideEffect) { + AddPokitSideEffect.AddPokitSuccess -> { + onBackPressed() + } + + AddPokitSideEffect.OnNavigationBack -> { + onBackPressed() + } + } + } + + BackPressHandler(onBackPressed = viewModel::onBackPressed) + + AddPokitScreen( + pokitName = pokitName, + state = state, + saveButtonEnable = saveButtonEnable.value, + onclickAddPokit = viewModel::savePokit, + inputPokitName = viewModel::inputPokitName, + onBackPressed = viewModel::onBackPressed, + hideProfileSelectBottomSheet = viewModel::hidePokitProfileSelectBottomSheet, + showSelectProfileBottomSheet = viewModel::showPokitProfileSelectBottomSheet, + selectPokitProfileImage = viewModel::selectPoktiProfile + ) +} + +@Composable +fun AddPokitScreen( + pokitName: String = "", + state: AddPokitScreenState = AddPokitScreenState(), + saveButtonEnable: Boolean = true, + onclickAddPokit: () -> Unit = {}, + inputPokitName: (String) -> Unit = {}, + onBackPressed: () -> Unit = {}, + hideProfileSelectBottomSheet: () -> Unit = {}, + showSelectProfileBottomSheet: () -> Unit = {}, + selectPokitProfileImage: (PokitProfile) -> Unit = {}, +) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(vertical = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Toolbar( + onClickBack = onBackPressed, + title = stringResource(id = R.string.title_add_pokit) + ) + + Box(modifier = Modifier.size(80.dp)) { + Image( + painter = painterResource(id = coreDrawable.icon_24_google), + contentDescription = null, + modifier = Modifier + .size(80.dp) + .clip(shape = RoundedCornerShape(12.dp)) + ) + + Box( + modifier = Modifier + .align(Alignment.BottomEnd) + .offset(x = 7.dp, y = (-7).dp) + .size(24.dp) + .background( + color = PokitTheme.colors.inverseWh, + shape = CircleShape + ) + .border( + width = 1.dp, + color = PokitTheme.colors.borderSecondary, + shape = CircleShape + ) + .clip( + shape = CircleShape + ) + .clickable( + onClick = showSelectProfileBottomSheet + ) + .padding(3.dp) + ) { + Image( + painter = painterResource(id = coreDrawable.icon_24_edit), + contentDescription = "null", + modifier = Modifier + .size(18.dp), + colorFilter = ColorFilter.tint( + color = PokitTheme.colors.iconTertiary + ) + ) + } + } + + Spacer(modifier = Modifier.height(12.dp)) + + LabeledInput( + modifier = Modifier.padding(horizontal = 20.dp), + label = stringResource(id = R.string.pokit_name), + inputText = pokitName, + hintText = stringResource(id = R.string.placeholder_pokit_name), + onChangeText = inputPokitName, + isError = state.pokitInputErrorMessage != null, + sub = state.pokitInputErrorMessage?.let { stringResource(id = it.resourceId) } ?: "", + enable = (state.step != AddPokitScreenStep.POKIT_SAVE_LOADING), + maxLength = 10 + ) + + Spacer(modifier = Modifier.height(28.dp)) + + Text( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp), + text = stringResource(id = R.string.my_pokit), + style = PokitTheme.typography.body2Medium.copy(color = PokitTheme.colors.textSecondary) + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Box( + modifier = Modifier + .fillMaxSize() + .weight(1f), + contentAlignment = Alignment.Center + ) { + LazyColumn( + modifier = Modifier.fillMaxSize() + ) { + items(state.pokitList) { item: Pokit -> + PokitList( + item = item, + title = item.title, + sub = stringResource(id = coreString.pokit_count_format, item.count), + onClickKebab = {}, + onClickItem = {}, + state = PokitListState.DEFAULT + ) + } + } + + if (state.step == AddPokitScreenStep.POKIT_LIST_LOADING) { + CircularProgressIndicator( + modifier = Modifier.width(64.dp), + color = PokitTheme.colors.brand, + trackColor = PokitTheme.colors.backgroundSecondary + ) + } + } + + Box( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp) + ) { + PokitButton( + text = stringResource(id = R.string.save), + icon = null, + onClick = onclickAddPokit, + modifier = Modifier.fillMaxWidth(), + size = PokitButtonSize.LARGE, + enable = saveButtonEnable + ) + } + + if (state.step == AddPokitScreenStep.SELECT_PROFILE) { + PokitBottomSheet(onHideBottomSheet = hideProfileSelectBottomSheet) { + LazyVerticalGrid( + modifier = Modifier.padding(vertical = 12.dp, horizontal = 40.dp), + columns = GridCells.Adaptive(66.dp), + horizontalArrangement = Arrangement.spacedBy(20.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + items(samplePokitProfileList) { profileImage -> + PokitProfileImage( + pokitProfile = profileImage, + onClick = selectPokitProfileImage, + focused = (state.pokitProfile?.id == profileImage.id) + ) + } + } + } + } + } +} diff --git a/feature/addpokit/src/main/java/com/strayalpaca/addpokit/AddPokitViewModel.kt b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/AddPokitViewModel.kt new file mode 100644 index 00000000..590775db --- /dev/null +++ b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/AddPokitViewModel.kt @@ -0,0 +1,116 @@ +package com.strayalpaca.addpokit + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.strayalpaca.addpokit.const.POKIT_NAME_MAX_LENGTH +import com.strayalpaca.addpokit.model.AddPokitScreenState +import com.strayalpaca.addpokit.model.AddPokitScreenStep +import com.strayalpaca.addpokit.model.AddPokitSideEffect +import com.strayalpaca.addpokit.model.PokitInputErrorMessage +import com.strayalpaca.addpokit.model.PokitProfile +import com.strayalpaca.addpokit.model.samplePokitList +import kotlinx.coroutines.delay +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 org.orbitmvi.orbit.Container +import org.orbitmvi.orbit.ContainerHost +import org.orbitmvi.orbit.syntax.simple.intent +import org.orbitmvi.orbit.syntax.simple.postSideEffect +import org.orbitmvi.orbit.syntax.simple.reduce +import org.orbitmvi.orbit.viewmodel.container + +class AddPokitViewModel : ContainerHost, ViewModel() { + override val container: Container = container(AddPokitScreenState()) + + private val _pokitName = MutableStateFlow("") + val pokitName: StateFlow = _pokitName.asStateFlow() + + init { + loadPokitList() + } + + private fun loadPokitList() = intent { + viewModelScope.launch { + reduce { + state.copy( + step = AddPokitScreenStep.POKIT_LIST_LOADING, + pokitInputErrorMessage = null, + pokitList = emptyList() + ) + } + // todo 포킷 리스트 로드 api 연동 + delay(1000L) + + reduce { + state.copy( + step = AddPokitScreenStep.IDLE, + pokitList = samplePokitList + ) + } + } + } + + fun inputPokitName(pokitName: String) { + _pokitName.update { pokitName } + + intent { + val isInAvailableLength = pokitName.length > POKIT_NAME_MAX_LENGTH + val isDuplicatePokitName = state.pokitList.find { it.title == pokitName } != null + + val errorMessage = if (isInAvailableLength) { + PokitInputErrorMessage.TEXT_LENGTH_LIMIT + } else if (isDuplicatePokitName) { + PokitInputErrorMessage.ALREADY_USED_POKIT_NAME + } else { + null + } + reduce { state.copy(pokitInputErrorMessage = errorMessage) } + } + } + + fun savePokit() = intent { + reduce { + state.copy(step = AddPokitScreenStep.POKIT_SAVE_LOADING) + } + // todo 포킷 저장 api 연동 + delay(1000L) + reduce { + state.copy(step = AddPokitScreenStep.IDLE) + } + postSideEffect(AddPokitSideEffect.AddPokitSuccess) + } + + fun onBackPressed() = intent { + val currentStep = state.step + when (currentStep) { + AddPokitScreenStep.POKIT_SAVE_LOADING -> {} // discard + AddPokitScreenStep.SELECT_PROFILE -> { + reduce { state.copy(step = AddPokitScreenStep.IDLE) } + } + else -> { + postSideEffect(AddPokitSideEffect.OnNavigationBack) + } + } + } + + fun showPokitProfileSelectBottomSheet() = intent { + reduce { + state.copy(step = AddPokitScreenStep.SELECT_PROFILE) + } + } + + fun hidePokitProfileSelectBottomSheet() = intent { + reduce { + state.copy(step = AddPokitScreenStep.IDLE) + } + } + + fun selectPoktiProfile(pokitProfile: PokitProfile) = intent { + reduce { + state.copy(pokitProfile = pokitProfile) + } + } +} diff --git a/feature/addpokit/src/main/java/com/strayalpaca/addpokit/Preview.kt b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/Preview.kt new file mode 100644 index 00000000..dbb00c58 --- /dev/null +++ b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/Preview.kt @@ -0,0 +1,24 @@ +package com.strayalpaca.addpokit + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import com.strayalpaca.addpokit.model.AddPokitScreenState +import com.strayalpaca.addpokit.model.samplePokitList +import pokitmons.pokit.core.ui.theme.PokitTheme + +@Preview(showBackground = true) +@Composable +fun Preview() { + PokitTheme { + Column( + modifier = Modifier.fillMaxSize() + ) { + AddPokitScreen( + state = AddPokitScreenState().copy(pokitList = samplePokitList) + ) + } + } +} diff --git a/feature/addpokit/src/main/java/com/strayalpaca/addpokit/components/atom/PokitProfileImage.kt b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/components/atom/PokitProfileImage.kt new file mode 100644 index 00000000..91579c71 --- /dev/null +++ b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/components/atom/PokitProfileImage.kt @@ -0,0 +1,49 @@ +package com.strayalpaca.addpokit.components.atom + +import androidx.compose.foundation.Image +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import com.strayalpaca.addpokit.model.PokitProfile +import pokitmons.pokit.core.ui.theme.PokitTheme +import pokitmons.pokit.core.ui.R.drawable as coreDrawable + +@Composable +fun PokitProfileImage( + pokitProfile: PokitProfile, + onClick: (PokitProfile) -> Unit, + focused: Boolean = false, +) { + val activeStrokeColor = PokitTheme.colors.brand + val strokeColor = remember(focused) { + if (focused) { + activeStrokeColor + } else { + Color.Unspecified + } + } + + Image( + painter = painterResource(id = coreDrawable.icon_24_plus_r), + contentDescription = "pokit profile image", + modifier = Modifier + .size(66.dp) + .clip(shape = RoundedCornerShape(12.dp)) + .clickable { + onClick(pokitProfile) + } + .border( + color = strokeColor, + width = 1.dp, + shape = RoundedCornerShape(12.dp) + ) + ) +} diff --git a/feature/addpokit/src/main/java/com/strayalpaca/addpokit/components/block/Toolbar.kt b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/components/block/Toolbar.kt new file mode 100644 index 00000000..8154ecce --- /dev/null +++ b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/components/block/Toolbar.kt @@ -0,0 +1,54 @@ +package com.strayalpaca.addpokit.components.block + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +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.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import pokitmons.pokit.core.ui.theme.PokitTheme +import pokitmons.pokit.core.ui.R.drawable as coreDrawable + +@Composable +internal fun Toolbar( + onClickBack: () -> Unit, + title: String, + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier + .height(56.dp) + .padding(horizontal = 12.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + IconButton( + modifier = Modifier.size(48.dp), + onClick = onClickBack + ) { + Icon( + painter = painterResource(id = coreDrawable.icon_24_arrow_left), + contentDescription = "back button" + ) + } + + Text( + modifier = Modifier.weight(1f), + text = title, + style = PokitTheme.typography.title3.copy(color = PokitTheme.colors.textPrimary), + textAlign = TextAlign.Center + ) + + Spacer(modifier = Modifier.width(48.dp)) + } +} diff --git a/feature/addpokit/src/main/java/com/strayalpaca/addpokit/const/Consts.kt b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/const/Consts.kt new file mode 100644 index 00000000..9ec703a4 --- /dev/null +++ b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/const/Consts.kt @@ -0,0 +1,3 @@ +package com.strayalpaca.addpokit.const + +internal const val POKIT_NAME_MAX_LENGTH = 10 diff --git a/feature/addpokit/src/main/java/com/strayalpaca/addpokit/model/AddPokitScreenState.kt b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/model/AddPokitScreenState.kt new file mode 100644 index 00000000..511ee69c --- /dev/null +++ b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/model/AddPokitScreenState.kt @@ -0,0 +1,29 @@ +package com.strayalpaca.addpokit.model + +import androidx.compose.runtime.Immutable +import com.strayalpaca.addpokit.R + +@Immutable +data class AddPokitScreenState( + val pokitInputErrorMessage: PokitInputErrorMessage? = null, + val pokitList: List = emptyList(), + val step: AddPokitScreenStep = AddPokitScreenStep.POKIT_LIST_LOADING, + val pokitProfile: PokitProfile? = null, +) + +sealed class AddPokitScreenStep { + data object IDLE : AddPokitScreenStep() + data object POKIT_LIST_LOADING : AddPokitScreenStep() + data object POKIT_SAVE_LOADING : AddPokitScreenStep() + data object SELECT_PROFILE : AddPokitScreenStep() +} + +sealed class AddPokitSideEffect { + data object AddPokitSuccess : AddPokitSideEffect() + data object OnNavigationBack : AddPokitSideEffect() +} + +enum class PokitInputErrorMessage(val resourceId: Int) { + TEXT_LENGTH_LIMIT(R.string.text_length_limit_format), + ALREADY_USED_POKIT_NAME(R.string.already_used_pokit_name), +} diff --git a/feature/addpokit/src/main/java/com/strayalpaca/addpokit/model/Pokit.kt b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/model/Pokit.kt new file mode 100644 index 00000000..1b004274 --- /dev/null +++ b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/model/Pokit.kt @@ -0,0 +1,15 @@ +package com.strayalpaca.addpokit.model + +data class Pokit( + val title: String, + val id: String, + val count: Int, +) + +internal val samplePokitList = listOf( + Pokit(title = "안드로이드", id = "1", count = 2), + Pokit(title = "IOS", id = "2", count = 2), + Pokit(title = "디자인", id = "3", count = 2), + Pokit(title = "PM", id = "4", count = 1), + Pokit(title = "서버", id = "5", count = 2) +) diff --git a/feature/addpokit/src/main/java/com/strayalpaca/addpokit/model/PokitProfile.kt b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/model/PokitProfile.kt new file mode 100644 index 00000000..41274e56 --- /dev/null +++ b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/model/PokitProfile.kt @@ -0,0 +1,13 @@ +package com.strayalpaca.addpokit.model + +data class PokitProfile( + val id: String, +) + +internal val samplePokitProfileList = + listOf( + PokitProfile("1"), PokitProfile("2"), PokitProfile("3"), PokitProfile("4"), + PokitProfile("5"), PokitProfile("6"), PokitProfile("7"), PokitProfile("8"), + PokitProfile("9"), PokitProfile("10"), PokitProfile("11"), PokitProfile("12"), + PokitProfile("13"), PokitProfile("14"), PokitProfile("15") + ) diff --git a/feature/addpokit/src/main/java/com/strayalpaca/addpokit/utils/BackPressHandler.kt b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/utils/BackPressHandler.kt new file mode 100644 index 00000000..2459aad0 --- /dev/null +++ b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/utils/BackPressHandler.kt @@ -0,0 +1,35 @@ +package com.strayalpaca.addpokit.utils + +import androidx.activity.OnBackPressedCallback +import androidx.activity.OnBackPressedDispatcher +import androidx.activity.compose.LocalOnBackPressedDispatcherOwner +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState + +@Composable +internal fun BackPressHandler( + backPressedDispatcher: OnBackPressedDispatcher? = + LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher, + onBackPressed: () -> Unit, +) { + val currentOnBackPressed by rememberUpdatedState(newValue = onBackPressed) + + val backCallback = remember { + object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + currentOnBackPressed() + } + } + } + + DisposableEffect(backPressedDispatcher) { + backPressedDispatcher?.addCallback(backCallback) + + onDispose { + backCallback.remove() + } + } +} diff --git a/feature/addpokit/src/main/res/values/string.xml b/feature/addpokit/src/main/res/values/string.xml new file mode 100644 index 00000000..09e348fc --- /dev/null +++ b/feature/addpokit/src/main/res/values/string.xml @@ -0,0 +1,11 @@ + + + 포킷 추가 + 포킷 이름 + 카테고리 이름을 입력해주세요. + 저장하기 + 내 포킷 + + 최대 10자까지 입력 가능합니다. + 사용 중인 포킷명입니다. + \ No newline at end of file diff --git a/feature/addpokit/src/test/java/com/strayalpaca/addpokit/ExampleUnitTest.kt b/feature/addpokit/src/test/java/com/strayalpaca/addpokit/ExampleUnitTest.kt new file mode 100644 index 00000000..03eade60 --- /dev/null +++ b/feature/addpokit/src/test/java/com/strayalpaca/addpokit/ExampleUnitTest.kt @@ -0,0 +1,16 @@ +package com.strayalpaca.addpokit + +import org.junit.Assert.assertEquals +import org.junit.Test + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +}