Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4d7b4f7
[refactor] #78: 아카이브 앨범 LazyColumn의 verticalArrangement 제거
ikseong00 Feb 6, 2026
c3120b5
[chore] #78: 즐겨찾는 사진 앨범명 즐겨찾기로 수정
ikseong00 Feb 6, 2026
938970b
[feat] #78: 로딩 애니메이션 Lottie 종속성 및 리소스 추가
ikseong00 Feb 6, 2026
2248019
[feat] #78: 로딩 인디케이터 Lottie 애니메이션으로 변경
ikseong00 Feb 6, 2026
28b14ca
[feat] #78: 네키 타이포 로고 아이콘 추가
ikseong00 Feb 6, 2026
1ec67f4
[refactor] #78: 앨범 커버 디자인 개선 및 블러 효과(Haze) 적용
ikseong00 Feb 6, 2026
b55efad
[feat] #78: 공통 `ToolTipPopup` 컴포저블 추가 및 적용
ikseong00 Feb 6, 2026
cb88209
[feat] #78: 포토 그리드 아이템 오버레이 컴포저블 추가 및 적용
ikseong00 Feb 6, 2026
0210ea2
[feat] #78: Empty 뷰 수정사항 적용
ikseong00 Feb 6, 2026
019bfef
[refactor] #78: 포토 그리드 배경에 alpha 0.04f의 검은색 배경 레이어 추가
ikseong00 Feb 6, 2026
17a0e2e
[fix] #78: 이미지 업로드 시 EXIF 회전 방향 보정 적용
ikseong00 Feb 6, 2026
94e557e
[refactor] #78: backgroundHazeBlur KDoc 주석 업데이트 및 미사용 파라미터 제거
ikseong00 Feb 6, 2026
f18f6d7
[feat] #78: ToolTipPopup onDismissRequest 콜백 추가 및 팝업 dismiss 분리
ikseong00 Feb 6, 2026
1eaf057
[fix] #78: PhotoComponent cornerRadius 12dp에서 8dp로 수정
ikseong00 Feb 6, 2026
ce04f30
[refactor] #78: SelectablePhotoItem 선택 시 검은 배경 오버레이 제거 및 미사용 import 정리
ikseong00 Feb 6, 2026
aa10101
[refactor] #78: AlbumDetailContent 접근제어자 internal로 변경
ikseong00 Feb 6, 2026
d71f0d1
[refactor] #78: 아카이브 전체보기 버튼 터치 영역 수정
ikseong00 Feb 6, 2026
b63b96b
[refactor] #78: BuildConfig 필드명 오타 수정
ikseong00 Feb 6, 2026
d1833d2
[refactor] #78: `AlbumDetailTopBar`의 클릭 이벤트 핸들러에 기본값 추가
ikseong00 Feb 7, 2026
1fcc6c4
[fix] #78: `SelectablePhotoItem` 컴포저블 파일명 오타 수정 및 radius 값 변경
ikseong00 Feb 7, 2026
9565c8e
[refactor] #78: 앨범 목록 리스트 Arrangement.spacedBy(20.dp) 속성 제거
ikseong00 Feb 7, 2026
b233af2
[refactor] #78: ItemOverlay에서 photoGridBackground Modifier 제거
ikseong00 Feb 7, 2026
7545aa4
[chore] #78: detekt 린트 수정
ikseong00 Feb 7, 2026
e84480e
Merge branch 'develop' into fix/#78-archive-upload-qa
ikseong00 Feb 7, 2026
f9f49af
[fix] #78: 랜덤 포즈 이미지 흐림 효과 및 그라데이션 배경 추가
ikseong00 Feb 7, 2026
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
2 changes: 2 additions & 0 deletions core/common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ dependencies {
api(libs.kakao.user)
implementation(libs.androidx.security.crypto)
implementation(libs.androidx.core.ktx)

implementation(libs.androidx.exifinterface)
}
Comment thread
Ojongseok marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package com.neki.android.core.common.util
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
import android.net.Uri
import androidx.exifinterface.media.ExifInterface
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.ByteArrayOutputStream
Expand All @@ -16,8 +18,22 @@ fun Uri.toByteArray(
quality: Int = DEFAULT_QUALITY,
format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG,
): ByteArray? {
val orientation = context.contentResolver.openInputStream(this)?.use { input ->
ExifInterface(input).getAttributeInt(
ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_UNDEFINED,
)
} ?: ExifInterface.ORIENTATION_UNDEFINED
val bytes = context.contentResolver.openInputStream(this)?.use { it.readBytes() } ?: return null
return bytes.compress(quality, format)
val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) ?: return null
val rotatedBitmap = bitmap.applyOrientation(orientation)

return ByteArrayOutputStream().use { outputStream ->
rotatedBitmap.compress(format, quality, outputStream)
if (rotatedBitmap !== bitmap) bitmap.recycle()
rotatedBitmap.recycle()
outputStream.toByteArray()
}
}

suspend fun String.urlToByteArray(
Expand All @@ -28,6 +44,29 @@ suspend fun String.urlToByteArray(
bytes.compress(quality, format)
}

private fun Bitmap.applyOrientation(orientation: Int): Bitmap {
val matrix = Matrix()
when (orientation) {
ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90f)
ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180f)
ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270f)
ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> matrix.postScale(-1f, 1f)
ExifInterface.ORIENTATION_FLIP_VERTICAL -> matrix.postScale(1f, -1f)
ExifInterface.ORIENTATION_TRANSPOSE -> {
matrix.postRotate(90f)
matrix.postScale(-1f, 1f)
}

ExifInterface.ORIENTATION_TRANSVERSE -> {
matrix.postRotate(270f)
matrix.postScale(-1f, 1f)
}

else -> return this
}
return Bitmap.createBitmap(this, 0, 0, width, height, matrix, true)
}

private fun ByteArray.compress(
quality: Int,
format: Bitmap.CompressFormat,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class PhotoService @Inject constructor(
}.body()
}

// 즐겨찾기 사진 조회
// 즐겨찾는 앨범 조회
suspend fun getFavoritePhotos(
page: Int = 0,
size: Int = 20,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.neki.android.core.designsystem.logo

import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import com.neki.android.core.designsystem.ComponentPreview
import com.neki.android.core.designsystem.R
import com.neki.android.core.designsystem.ui.theme.NekiTheme

@Composable
private fun NekiTypoLogo(
color: Color,
modifier: Modifier = Modifier,
) {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.icon_neki_logo_typo),
contentDescription = null,
tint = color,
modifier = modifier,
)
}

@Composable
fun PrimaryNekiTypoLogo(
modifier: Modifier = Modifier,
) {
NekiTypoLogo(
color = NekiTheme.colorScheme.primary400,
modifier = modifier,
)
}

@Composable
fun GrayNekiTypoLogo(
modifier: Modifier = Modifier,
) {
NekiTypoLogo(
color = NekiTheme.colorScheme.gray900,
modifier = modifier,
)
}

@Composable
fun WhiteNekiTypoLogo(
modifier: Modifier = Modifier,
) {
NekiTypoLogo(
color = NekiTheme.colorScheme.white,
modifier = modifier,
)
}

@ComponentPreview
@Composable
private fun PrimaryNekiTypoLogoPreview() {
NekiTheme {
PrimaryNekiTypoLogo()
}
}

@ComponentPreview
@Composable
private fun GrayNekiTypoLogoPreview() {
NekiTheme {
GrayNekiTypoLogo()
}
}

@Preview
@Composable
private fun WhiteNekiTypoLogoPreview() {
NekiTheme {
WhiteNekiTypoLogo()
}
}
Comment thread
ikseong00 marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,11 @@ package com.neki.android.core.designsystem.modifier

import androidx.compose.foundation.background
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import dev.chrisbanes.haze.HazeState
Expand Down Expand Up @@ -56,28 +53,29 @@ fun Modifier.poseBackground(
* 블러 효과가 적용된 배경을 설정하는 Modifier 확장 함수
*
* @param hazeState Haze 블러 효과를 관리하는 상태 객체
* @param enabled 블러 효과 활성화 여부 (false일 경우 단색 배경 적용)
* @param alpha 블러 틴트에 적용될 색상의 알파 값
* @param color 블러 효과에 적용될 배경 색상
* @param defaultBackgroundColor 블러 비활성화 시 적용될 기본 배경 색상
* @param blurRadius 블러 효과의 반경
* @param enabled 블러 효과 활성화 여부 (false일 경우 단색 배경 적용)
* @param defaultBackgroundColor 블러 비활성화 시 적용될 기본 배경 색상
*/
@Composable
fun Modifier.backgroundHazeBlur(
hazeState: HazeState,
alpha: Float,
color: Color,
blurRadius: Dp,
enabled: Boolean = true,
color: Color = Color(0xFF202227).copy(alpha = 0.9f),
defaultBackgroundColor: Color = color,
blurRadius: Dp = 12.dp,
): Modifier =
if (enabled) {
this.hazeEffect(
state = hazeState,
style = HazeStyle(
backgroundColor = color,
tint = HazeTint(
color.copy(alpha = if (color.luminance() >= 0.5) 0.6f else 0.65f),
),
tint = HazeTint(color.copy(alpha = alpha)),
blurRadius = blurRadius,
),
)
} else this.background(color = defaultBackgroundColor)
} else this.background(
color = defaultBackgroundColor,
)
Comment thread
ikseong00 marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package com.neki.android.core.designsystem.popup

import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
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.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Popup
import com.neki.android.core.designsystem.ComponentPreview
import com.neki.android.core.designsystem.ui.theme.NekiTheme

@Composable
fun ToolTipPopup(
tooltipText: String,
color: Color,
offset: IntOffset,
alignment: Alignment,
onDismissRequest: () -> Unit,
) {
Popup(
alignment = alignment,
offset = offset,
onDismissRequest = onDismissRequest,
) {
ToolTipContent(
tooltipText = tooltipText,
color = color,
)
}
}
Comment thread
ikseong00 marked this conversation as resolved.

@Composable
private fun ToolTipContent(
tooltipText: String,
color: Color,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier.width(IntrinsicSize.Max),
) {
// 꼬리 (오른쪽 정렬, 오른쪽에서 16dp)
Box(
modifier = Modifier
.fillMaxWidth()
.padding(end = 16.dp),
contentAlignment = Alignment.CenterEnd,
) {
Canvas(
modifier = Modifier.size(width = 10.dp, height = 8.dp),
) {
val cornerRadius = 1.dp.toPx()
val path = Path().apply {
// 왼쪽 하단에서 시작
moveTo(0f, size.height)
// 왼쪽 하단 -> 꼭대기 (둥근 모서리)
lineTo(
size.width / 2 - cornerRadius,
cornerRadius,
)
quadraticTo(
size.width / 2,
0f,
size.width / 2 + cornerRadius,
cornerRadius,
)
// 꼭대기 -> 오른쪽 하단
lineTo(size.width, size.height)
close()
}
drawPath(path, color)
}
}

// 몸통
Box(
modifier = Modifier
.background(
color = color,
shape = RoundedCornerShape(8.dp),
)
.padding(horizontal = 12.dp, vertical = 8.dp),
) {
Text(
text = tooltipText,
style = NekiTheme.typography.body14Medium,
color = NekiTheme.colorScheme.white,
)
}
}
}

@ComponentPreview
@Composable
private fun ToolTipPopupPreview() {
NekiTheme {
Box(modifier = Modifier.padding(16.dp)) {
ToolTipContent(
tooltipText = "툴팁 메시지입니다",
color = NekiTheme.colorScheme.gray800,
)
}
}
}
Loading