From d024c787f1253b483870b6851476b78be7d00081 Mon Sep 17 00:00:00 2001 From: kimjw Date: Fri, 1 May 2026 00:21:58 +0900 Subject: [PATCH 1/4] feat: change profile image --- .../component/image/ProfileImage.kt | 6 +-- .../onboarding/OnboardingProfileScreen.kt | 46 +++++++++++++++++-- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/flint/core/designsystem/component/image/ProfileImage.kt b/app/src/main/java/com/flint/core/designsystem/component/image/ProfileImage.kt index 2bb6460f..6b3897fa 100644 --- a/app/src/main/java/com/flint/core/designsystem/component/image/ProfileImage.kt +++ b/app/src/main/java/com/flint/core/designsystem/component/image/ProfileImage.kt @@ -18,7 +18,7 @@ fun ProfileImage( ) { if (imageUrl.isNullOrBlank()) { Image( - painter = painterResource(R.drawable.ic_avatar_blue), + painter = painterResource(R.drawable.ic_avatar_gray), contentDescription = contentDescription, modifier = modifier.clip(CircleShape), ) @@ -27,8 +27,8 @@ fun ProfileImage( model = imageUrl, contentDescription = contentDescription, contentScale = ContentScale.Crop, - placeholder = painterResource(R.drawable.ic_avatar_blue), - error = painterResource(R.drawable.ic_avatar_blue), + placeholder = painterResource(R.drawable.ic_avatar_gray), + error = painterResource(R.drawable.ic_avatar_gray), modifier = modifier.clip(shape = CircleShape), ) } diff --git a/app/src/main/java/com/flint/presentation/onboarding/OnboardingProfileScreen.kt b/app/src/main/java/com/flint/presentation/onboarding/OnboardingProfileScreen.kt index c8639453..0a5bf0b5 100644 --- a/app/src/main/java/com/flint/presentation/onboarding/OnboardingProfileScreen.kt +++ b/app/src/main/java/com/flint/presentation/onboarding/OnboardingProfileScreen.kt @@ -1,5 +1,9 @@ package com.flint.presentation.onboarding +import android.net.Uri +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -13,7 +17,9 @@ 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.ExperimentalMaterial3Api import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -34,9 +40,11 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.chattymin.pebble.graphemeLength import com.flint.R +import com.flint.core.designsystem.component.bottomsheet.MenuBottomSheet +import com.flint.core.designsystem.component.bottomsheet.MenuBottomSheetData import com.flint.core.designsystem.component.button.FlintButtonState import com.flint.core.designsystem.component.button.FlintLargeButton -import com.flint.core.designsystem.component.image.ProfileImage +import com.flint.core.designsystem.component.image.EditProfileImage import com.flint.core.designsystem.component.textfield.FlintBasicTextField import com.flint.core.designsystem.component.toast.ShowToast import com.flint.core.designsystem.component.topappbar.FlintBackTopAppbar @@ -68,6 +76,7 @@ fun OnboardingProfileRoute( ) } +@OptIn(ExperimentalMaterial3Api::class) @Composable fun OnboardingProfileScreen( nickname: String, @@ -88,6 +97,14 @@ fun OnboardingProfileScreen( var showToast by remember { mutableStateOf(false) } var toastMessage by remember { mutableStateOf("") } var isToastSuccess by remember { mutableStateOf(false) } + var showProfileBottomSheet by remember { mutableStateOf(false) } + var selectedImageUri by remember { mutableStateOf(null) } + + val galleryLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.PickVisualMedia() + ) { uri -> + if (uri != null) selectedImageUri = uri + } LaunchedEffect(hasError, errorMessage) { if (hasError && errorMessage != null) { @@ -121,8 +138,10 @@ fun OnboardingProfileScreen( .padding(horizontal = 16.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { - ProfileImage( - imageUrl = "", + + EditProfileImage( + imageUrl = selectedImageUri?.toString() ?: "", + onEditClick = { showProfileBottomSheet = true } ) Spacer(modifier = Modifier.height(24.dp)) @@ -202,6 +221,27 @@ fun OnboardingProfileScreen( ) } + if (showProfileBottomSheet) { + MenuBottomSheet( + menuBottomSheetDataList = listOf( + MenuBottomSheetData( + label = "갤러리에서 선택", + clickAction = { + galleryLauncher.launch( + PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly) + ) + } + ), + MenuBottomSheetData( + label = "프로필 사진 삭제", + color = FlintTheme.colors.error500, + clickAction = { selectedImageUri = null } + ), + ), + onDismiss = { showProfileBottomSheet = false } + ) + } + if (showToast) { ShowToast( text = toastMessage, From ac5a9bdc51a22ca44fa30250f1542842afb89b9d Mon Sep 17 00:00:00 2001 From: kimjw Date: Mon, 11 May 2026 00:22:46 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20=EC=98=A8=EB=B3=B4=EB=94=A9=20?= =?UTF-8?q?=EC=95=BD=EA=B4=80=20=ED=99=94=EB=A9=B4=20=EB=84=A4=EB=B9=84?= =?UTF-8?q?=EA=B2=8C=EC=9D=B4=EC=85=98=20=EC=97=B0=EA=B2=B0=20=EB=B0=8F=20?= =?UTF-8?q?UI=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Profile → Terms → Content 순서로 온보딩 흐름 변경 - OnboardingTermsScreen 약관 항목 확장 영역 UI 개선 (구분선 full-width, 설명 영역 배경 분리) Co-Authored-By: Claude Sonnet 4.6 --- .../java/com/flint/core/navigation/Route.kt | 3 + .../onboarding/OnboardingTermsScreen.kt | 250 ++++++++++++++++++ .../navigation/OnboardingNavigation.kt | 18 +- 3 files changed, 270 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/flint/presentation/onboarding/OnboardingTermsScreen.kt diff --git a/app/src/main/java/com/flint/core/navigation/Route.kt b/app/src/main/java/com/flint/core/navigation/Route.kt index 722f7273..0eebba20 100644 --- a/app/src/main/java/com/flint/core/navigation/Route.kt +++ b/app/src/main/java/com/flint/core/navigation/Route.kt @@ -20,6 +20,9 @@ interface Route { val tempToken: String ) : Route + @Serializable + data object OnboardingTerms : Route + @Serializable data object OnboardingContent : Route diff --git a/app/src/main/java/com/flint/presentation/onboarding/OnboardingTermsScreen.kt b/app/src/main/java/com/flint/presentation/onboarding/OnboardingTermsScreen.kt new file mode 100644 index 00000000..2cbde9e8 --- /dev/null +++ b/app/src/main/java/com/flint/presentation/onboarding/OnboardingTermsScreen.kt @@ -0,0 +1,250 @@ +package com.flint.presentation.onboarding + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +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.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +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.rotate +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.flint.R +import com.flint.core.common.extension.noRippleClickable +import com.flint.core.designsystem.component.button.FlintButtonState +import com.flint.core.designsystem.component.button.FlintLargeButton +import com.flint.core.designsystem.component.topappbar.FlintBackTopAppbar +import com.flint.core.designsystem.theme.FlintTheme + +private data class TermItem( + val title: String, + val description: String, + val detailUrl: String, +) + +private val terms = listOf( + TermItem( + title = "(필수) 서비스 이용 약관 동의", + description = "본 약관은 서비스 이용과 관련한 기본적인 권리·의무 및 책임사항을 규정합니다.", + detailUrl = "", + ), + TermItem( + title = "(필수) 개인정보 처리 방침 동의", + description = "서비스 제공을 위해 개인정보를 수집·이용합니다.\n" + + "콘텐츠 추천, 컬렉션 생성 및 공유, 맞춤형 탐색 경험 제공을 위한 이용 기록 및 취향 정보 처리 내용이 포함됩니다.\n\n" + + "수집 항목: 계정 정보, 취향 정보, 컬렉션 및 콘텐츠 활동, 서비스 이용 기록 등\n\n" + + "수집 목적: 개인화 추천 제공, 컬렉션 생성 및 공유, 서비스 운영 및 이용자 보호", + detailUrl = "", + ), +) + +@Composable +fun OnboardingTermsRoute( + paddingValues: PaddingValues, + navigateUp: () -> Unit, + navigateToOnboardingContent: () -> Unit, +) { + OnboardingTermsScreen( + onBackClick = navigateUp, + onAgreeClick = navigateToOnboardingContent, + modifier = Modifier.padding(paddingValues), + ) +} + +@Composable +fun OnboardingTermsScreen( + onBackClick: () -> Unit, + onAgreeClick: () -> Unit, + modifier: Modifier = Modifier, +) { + var checkedStates by remember { mutableStateOf(List(terms.size) { false }) } + var expandedStates by remember { mutableStateOf(List(terms.size) { false }) } + val allChecked = checkedStates.all { it } + val uriHandler = LocalUriHandler.current + + Column( + modifier = modifier + .fillMaxSize() + .background(FlintTheme.colors.background), + ) { + FlintBackTopAppbar(onClick = onBackClick) + + Column(modifier = Modifier.weight(1f)) { + Column(modifier = Modifier.padding(horizontal = 16.dp)) { + Spacer(Modifier.height(8.dp)) + + Text( + text = "약관에 동의해주세요", + style = FlintTheme.typography.head2M20, + color = FlintTheme.colors.white, + ) + + Spacer(Modifier.height(24.dp)) + + Row( + modifier = Modifier + .fillMaxWidth() + .noRippleClickable { + val newState = !allChecked + checkedStates = List(terms.size) { newState } + } + .padding(vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = ImageVector.vectorResource( + if (allChecked) R.drawable.ic_check_fill else R.drawable.ic_check_empty + ), + contentDescription = null, + tint = Color.Unspecified, + ) + Spacer(Modifier.width(12.dp)) + Text( + text = "전체 동의", + style = FlintTheme.typography.body1M16, + color = FlintTheme.colors.white, + ) + } + + Spacer(Modifier.height(16.dp)) + } + + HorizontalDivider(thickness = 4.dp, color = FlintTheme.colors.gray600) + + Column(modifier = Modifier.padding(horizontal = 16.dp)) { + Spacer(Modifier.height(16.dp)) + + terms.forEachIndexed { index, term -> + TermRow( + term = term, + isChecked = checkedStates[index], + isExpanded = expandedStates[index], + onCheckClick = { + checkedStates = checkedStates.toMutableList().also { it[index] = !it[index] } + }, + onExpandClick = { + expandedStates = expandedStates.toMutableList().also { it[index] = !it[index] } + }, + onDetailClick = { + if (term.detailUrl.isNotEmpty()) uriHandler.openUri(term.detailUrl) + }, + ) + Spacer(Modifier.height(4.dp)) + } + } + } + + FlintLargeButton( + text = "동의하기", + state = if (allChecked) FlintButtonState.Able else FlintButtonState.Disable, + onClick = onAgreeClick, + enabled = allChecked, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 20.dp), + ) + } +} + +@Composable +private fun TermRow( + term: TermItem, + isChecked: Boolean, + isExpanded: Boolean, + onCheckClick: () -> Unit, + onExpandClick: () -> Unit, + onDetailClick: () -> Unit, +) { + Column(modifier = Modifier.fillMaxWidth()) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = ImageVector.vectorResource( + if (isChecked) R.drawable.ic_check_fill else R.drawable.ic_check_empty + ), + contentDescription = null, + tint = Color.Unspecified, + modifier = Modifier.noRippleClickable(onClick = onCheckClick), + ) + Spacer(Modifier.width(12.dp)) + Text( + text = term.title, + style = FlintTheme.typography.body2M14, + color = FlintTheme.colors.white, + modifier = Modifier.weight(1f), + ) + Icon( + imageVector = ImageVector.vectorResource(R.drawable.ic_down), + contentDescription = null, + tint = FlintTheme.colors.gray400, + modifier = Modifier + .rotate(if (isExpanded) 180f else 0f) + .noRippleClickable(onClick = onExpandClick), + ) + } + + AnimatedVisibility(visible = isExpanded) { + Column( + modifier = Modifier + .fillMaxWidth() + .background( + color = FlintTheme.colors.gray800, + shape = RoundedCornerShape(8.dp), + ) + .padding(start = 12.dp, top = 12.dp, end = 12.dp, bottom = 16.dp), + ) { + Text( + text = term.description, + style = FlintTheme.typography.body2R14, + color = FlintTheme.colors.gray300, + ) + Spacer(Modifier.height(8.dp)) + Text( + text = "자세히 보기", + style = FlintTheme.typography.body2R14, + color = FlintTheme.colors.primary200, + textDecoration = TextDecoration.Underline, + modifier = Modifier + .align(Alignment.End) + .noRippleClickable(onClick = onDetailClick), + ) + } + } + } +} + +@Preview +@Composable +private fun OnboardingTermsScreenPreview() { + FlintTheme { + OnboardingTermsScreen( + onBackClick = {}, + onAgreeClick = {}, + ) + } +} diff --git a/app/src/main/java/com/flint/presentation/onboarding/navigation/OnboardingNavigation.kt b/app/src/main/java/com/flint/presentation/onboarding/navigation/OnboardingNavigation.kt index 5c2b75cc..bcd741a6 100644 --- a/app/src/main/java/com/flint/presentation/onboarding/navigation/OnboardingNavigation.kt +++ b/app/src/main/java/com/flint/presentation/onboarding/navigation/OnboardingNavigation.kt @@ -13,6 +13,7 @@ import com.flint.presentation.onboarding.OnboardingContentRoute import com.flint.presentation.onboarding.OnboardingDoneRoute import com.flint.presentation.onboarding.OnboardingOttRoute import com.flint.presentation.onboarding.OnboardingProfileRoute +import com.flint.presentation.onboarding.OnboardingTermsRoute import com.flint.presentation.onboarding.OnboardingViewModel fun NavController.navigateToOnboarding( @@ -22,6 +23,11 @@ fun NavController.navigateToOnboarding( navigate(Route.OnboardingGraph(tempToken), navOptions) // OnboardingGraph로 네비게이트 } +fun NavController.navigateToOnboardingTerms() { + navigate(Route.OnboardingTerms) +} + + fun NavController.navigateToOnboardingContent() { navigate(Route.OnboardingContent) } @@ -42,13 +48,23 @@ fun NavGraphBuilder.onBoardingNavGraph( navigation( startDestination = Route.OnboardingProfile::class, ) { + composable { backStackEntry -> + val sharedViewModel = backStackEntry.sharedViewModel(navController) + + OnboardingTermsRoute( + paddingValues = paddingValues, + navigateUp = navController::navigateUp, + navigateToOnboardingContent = navController::navigateToOnboardingContent, + ) + } + composable { backStackEntry -> val sharedViewModel = backStackEntry.sharedViewModel(navController) OnboardingProfileRoute( paddingValues = paddingValues, navigateUp = navController::navigateUp, - navigateToOnboardingContent = navController::navigateToOnboardingContent, + navigateToOnboardingContent = navController::navigateToOnboardingTerms, viewModel = sharedViewModel, ) } From ee05ee5fcadce445516655355775c694a6dc6373 Mon Sep 17 00:00:00 2001 From: kimjw Date: Mon, 11 May 2026 00:27:35 +0900 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20=EC=98=A8=EB=B3=B4=EB=94=A9=20?= =?UTF-8?q?=EC=95=BD=EA=B4=80=20=ED=99=94=EB=A9=B4=20=ED=85=8D=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../flint/presentation/onboarding/OnboardingTermsScreen.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/flint/presentation/onboarding/OnboardingTermsScreen.kt b/app/src/main/java/com/flint/presentation/onboarding/OnboardingTermsScreen.kt index 2cbde9e8..41a7dcb7 100644 --- a/app/src/main/java/com/flint/presentation/onboarding/OnboardingTermsScreen.kt +++ b/app/src/main/java/com/flint/presentation/onboarding/OnboardingTermsScreen.kt @@ -96,7 +96,7 @@ fun OnboardingTermsScreen( Text( text = "약관에 동의해주세요", - style = FlintTheme.typography.head2M20, + style = FlintTheme.typography.display2M28, color = FlintTheme.colors.white, ) @@ -122,7 +122,7 @@ fun OnboardingTermsScreen( Spacer(Modifier.width(12.dp)) Text( text = "전체 동의", - style = FlintTheme.typography.body1M16, + style = FlintTheme.typography.body1R16, color = FlintTheme.colors.white, ) } @@ -194,7 +194,7 @@ private fun TermRow( Spacer(Modifier.width(12.dp)) Text( text = term.title, - style = FlintTheme.typography.body2M14, + style = FlintTheme.typography.body1R16, color = FlintTheme.colors.white, modifier = Modifier.weight(1f), ) From 9745aa09e3c07b2add01802091dea43e13d59f0b Mon Sep 17 00:00:00 2001 From: kimjw Date: Wed, 13 May 2026 00:25:32 +0900 Subject: [PATCH 4/4] =?UTF-8?q?feat:=20Storage=20Presigned=20URL=20API=20?= =?UTF-8?q?=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../java/com/flint/data/api/StorageApi.kt | 15 +++++++++++ .../java/com/flint/data/di/ServiceModule.kt | 5 ++++ .../response/PresignedUrlResponseDto.kt | 12 +++++++++ .../mapper/storage/PresignedUrlMapper.kt | 10 ++++++++ .../domain/model/storage/PresignedUrlModel.kt | 6 +++++ .../domain/repository/StorageRepository.kt | 25 +++++++++++++++++++ .../com/flint/domain/type/FileExtension.kt | 11 ++++++++ .../com/flint/domain/type/StoragePathType.kt | 8 ++++++ 8 files changed, 92 insertions(+) create mode 100644 app/src/main/java/com/flint/data/api/StorageApi.kt create mode 100644 app/src/main/java/com/flint/data/dto/storage/response/PresignedUrlResponseDto.kt create mode 100644 app/src/main/java/com/flint/domain/mapper/storage/PresignedUrlMapper.kt create mode 100644 app/src/main/java/com/flint/domain/model/storage/PresignedUrlModel.kt create mode 100644 app/src/main/java/com/flint/domain/repository/StorageRepository.kt create mode 100644 app/src/main/java/com/flint/domain/type/FileExtension.kt create mode 100644 app/src/main/java/com/flint/domain/type/StoragePathType.kt diff --git a/app/src/main/java/com/flint/data/api/StorageApi.kt b/app/src/main/java/com/flint/data/api/StorageApi.kt new file mode 100644 index 00000000..ba2f88ff --- /dev/null +++ b/app/src/main/java/com/flint/data/api/StorageApi.kt @@ -0,0 +1,15 @@ +package com.flint.data.api + +import com.flint.data.dto.base.BaseResponse +import com.flint.data.dto.storage.response.PresignedUrlResponseDto +import retrofit2.http.GET +import retrofit2.http.Query + +interface StorageApi { + // Presigned URL 발급 + @GET("/api/v1/storage/presigned-url") + suspend fun getPresignedUrl( + @Query("pathType") pathType: String, + @Query("extension") extension: String, + ): BaseResponse +} diff --git a/app/src/main/java/com/flint/data/di/ServiceModule.kt b/app/src/main/java/com/flint/data/di/ServiceModule.kt index 202d055e..71d9109b 100644 --- a/app/src/main/java/com/flint/data/di/ServiceModule.kt +++ b/app/src/main/java/com/flint/data/di/ServiceModule.kt @@ -6,6 +6,7 @@ import com.flint.data.api.CollectionApi import com.flint.data.api.ContentApi import com.flint.data.api.HomeApi import com.flint.data.api.SearchApi +import com.flint.data.api.StorageApi import com.flint.data.api.UserApi import dagger.Module import dagger.Provides @@ -44,4 +45,8 @@ object ServiceModule { @Provides @Singleton fun provideSearchApi(retrofit: Retrofit): SearchApi = retrofit.create(SearchApi::class.java) + + @Provides + @Singleton + fun provideStorageApi(retrofit: Retrofit): StorageApi = retrofit.create(StorageApi::class.java) } diff --git a/app/src/main/java/com/flint/data/dto/storage/response/PresignedUrlResponseDto.kt b/app/src/main/java/com/flint/data/dto/storage/response/PresignedUrlResponseDto.kt new file mode 100644 index 00000000..1bfc3e0b --- /dev/null +++ b/app/src/main/java/com/flint/data/dto/storage/response/PresignedUrlResponseDto.kt @@ -0,0 +1,12 @@ +package com.flint.data.dto.storage.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class PresignedUrlResponseDto( + @SerialName("uploadUrl") + val uploadUrl: String, + @SerialName("key") + val key: String, +) diff --git a/app/src/main/java/com/flint/domain/mapper/storage/PresignedUrlMapper.kt b/app/src/main/java/com/flint/domain/mapper/storage/PresignedUrlMapper.kt new file mode 100644 index 00000000..83174fd5 --- /dev/null +++ b/app/src/main/java/com/flint/domain/mapper/storage/PresignedUrlMapper.kt @@ -0,0 +1,10 @@ +package com.flint.domain.mapper.storage + +import com.flint.data.dto.storage.response.PresignedUrlResponseDto +import com.flint.domain.model.storage.PresignedUrlModel + +fun PresignedUrlResponseDto.toModel(): PresignedUrlModel = + PresignedUrlModel( + uploadUrl = uploadUrl, + key = key, + ) diff --git a/app/src/main/java/com/flint/domain/model/storage/PresignedUrlModel.kt b/app/src/main/java/com/flint/domain/model/storage/PresignedUrlModel.kt new file mode 100644 index 00000000..92f7a2f9 --- /dev/null +++ b/app/src/main/java/com/flint/domain/model/storage/PresignedUrlModel.kt @@ -0,0 +1,6 @@ +package com.flint.domain.model.storage + +data class PresignedUrlModel( + val uploadUrl: String, + val key: String, +) diff --git a/app/src/main/java/com/flint/domain/repository/StorageRepository.kt b/app/src/main/java/com/flint/domain/repository/StorageRepository.kt new file mode 100644 index 00000000..206636ce --- /dev/null +++ b/app/src/main/java/com/flint/domain/repository/StorageRepository.kt @@ -0,0 +1,25 @@ +package com.flint.domain.repository + +import com.flint.core.common.util.suspendRunCatching +import com.flint.data.api.StorageApi +import com.flint.domain.mapper.storage.toModel +import com.flint.domain.model.storage.PresignedUrlModel +import com.flint.domain.type.FileExtension +import com.flint.domain.type.StoragePathType +import javax.inject.Inject + +class StorageRepository @Inject constructor( + private val api: StorageApi, +) { + // Presigned URL 발급 + suspend fun getPresignedUrl( + pathType: StoragePathType, + extension: FileExtension, + ): Result = + suspendRunCatching { + api.getPresignedUrl( + pathType = pathType.name, + extension = extension.name, + ).data.toModel() + } +} diff --git a/app/src/main/java/com/flint/domain/type/FileExtension.kt b/app/src/main/java/com/flint/domain/type/FileExtension.kt new file mode 100644 index 00000000..7b2af322 --- /dev/null +++ b/app/src/main/java/com/flint/domain/type/FileExtension.kt @@ -0,0 +1,11 @@ +package com.flint.domain.type + +enum class FileExtension { + JPG, + JPEG, + PNG, + GIF, + WEBP, + SVG, + PDF, +} diff --git a/app/src/main/java/com/flint/domain/type/StoragePathType.kt b/app/src/main/java/com/flint/domain/type/StoragePathType.kt new file mode 100644 index 00000000..8d5e7219 --- /dev/null +++ b/app/src/main/java/com/flint/domain/type/StoragePathType.kt @@ -0,0 +1,8 @@ +package com.flint.domain.type + +enum class StoragePathType { + USER_PROFILE, + LOGO_IMAGE, + COLLECTION_THUMBNAIL, + COLLECTION_CONTENT, +}