Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -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),
)
Expand All @@ -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),
)
}
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/java/com/flint/core/navigation/Route.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ interface Route {
val tempToken: String
) : Route

@Serializable
data object OnboardingTerms : Route

@Serializable
data object OnboardingContent : Route

Expand Down
15 changes: 15 additions & 0 deletions app/src/main/java/com/flint/data/api/StorageApi.kt
Original file line number Diff line number Diff line change
@@ -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<PresignedUrlResponseDto>
}
5 changes: 5 additions & 0 deletions app/src/main/java/com/flint/data/di/ServiceModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Original file line number Diff line number Diff line change
@@ -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,
)
Original file line number Diff line number Diff line change
@@ -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,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.flint.domain.model.storage

data class PresignedUrlModel(
val uploadUrl: String,
val key: String,
)
Original file line number Diff line number Diff line change
@@ -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<PresignedUrlModel> =
suspendRunCatching {
api.getPresignedUrl(
pathType = pathType.name,
extension = extension.name,
).data.toModel()
}
}
11 changes: 11 additions & 0 deletions app/src/main/java/com/flint/domain/type/FileExtension.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.flint.domain.type

enum class FileExtension {
JPG,
JPEG,
PNG,
GIF,
WEBP,
SVG,
PDF,
}
8 changes: 8 additions & 0 deletions app/src/main/java/com/flint/domain/type/StoragePathType.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.flint.domain.type

enum class StoragePathType {
USER_PROFILE,
LOGO_IMAGE,
COLLECTION_THUMBNAIL,
COLLECTION_CONTENT,
}
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -68,6 +76,7 @@ fun OnboardingProfileRoute(
)
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun OnboardingProfileScreen(
nickname: String,
Expand All @@ -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<Uri?>(null) }

val galleryLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.PickVisualMedia()
) { uri ->
if (uri != null) selectedImageUri = uri
}

LaunchedEffect(hasError, errorMessage) {
if (hasError && errorMessage != null) {
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -202,6 +221,27 @@ fun OnboardingProfileScreen(
)
}

if (showProfileBottomSheet) {
MenuBottomSheet(
menuBottomSheetDataList = listOf(
MenuBottomSheetData(
label = "갤러리에서 선택",
clickAction = {
galleryLauncher.launch(
PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
)
}
Comment on lines +229 to +233
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

메뉴 액션 이후 바텀시트를 명시적으로 닫아주세요.

Line 229, Line 238의 clickAction에서 showProfileBottomSheet = false 처리가 없어 바텀시트가 남을 수 있습니다. 액션 직후 닫기를 명시하는 편이 안전합니다.

수정 예시
                     MenuBottomSheetData(
                         label = "갤러리에서 선택",
                         clickAction = {
+                            showProfileBottomSheet = false
                             galleryLauncher.launch(
                                 PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
                             )
                         }
                     ),
                     MenuBottomSheetData(
                         label = "프로필 사진 삭제",
                         color = FlintTheme.colors.error500,
-                        clickAction = { selectedImageUri = null }
+                        clickAction = {
+                            selectedImageUri = null
+                            showProfileBottomSheet = false
+                        }
                     ),

Also applies to: 236-239

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/src/main/java/com/flint/presentation/onboarding/OnboardingProfileScreen.kt`
around lines 229 - 233, 현재 galleryLauncher.launch(...)를 호출하는 clickAction 핸들러에서
바텀시트를 닫는 처리가 빠져 있습니다; clickAction(예: 해당 블록에서 사용 중인 galleryLauncher,
PickVisualMediaRequest 및 ActivityResultContracts.PickVisualMedia.ImageOnly 호출)
실행 직후 showProfileBottomSheet = false를 설정하여 바텀시트를 명시적으로 닫아 주세요. 동일한 패턴이 존재하는 다른
clickAction(예: 라인 236-239 범위)에도 동일한 showProfileBottomSheet = false 추가를 적용해 주세요.

),
MenuBottomSheetData(
label = "프로필 사진 삭제",
color = FlintTheme.colors.error500,
clickAction = { selectedImageUri = null }
),
),
onDismiss = { showProfileBottomSheet = false }
)
}

if (showToast) {
ShowToast(
text = toastMessage,
Expand Down
Loading
Loading