Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
20aff64
feat/#87: 이미지 업로드 기능(presigned-url + s3 업로드) 정리 및 DI 설정 추가
seungjae708 Feb 16, 2026
8865561
refactor/#87: KidCameraViewModel을 ImageUploadRepository로 마이그레이션
seungjae708 Feb 16, 2026
1f253c9
refactor/#87: 사용하지 않는 S3 Presigned URL 관련 코드 삭제
seungjae708 Feb 16, 2026
645ff39
refactor/#87: 파일 관련 로직을 ImageUriManager로 분리
seungjae708 Feb 16, 2026
c066f96
refactor/#87: SpeechField 위치 일정하게 수정
seungjae708 Feb 16, 2026
103f6a9
mod/#87: KidJourneyContentUiModel 내 공통 프로퍼티를 관리하는 `ScheduledContent` …
seungjae708 Feb 21, 2026
3ea4dbb
Merge branch 'develop' of https://github.com/Team-Kiero/Kiero-Android…
seungjae708 Feb 21, 2026
96dc14b
refactor/#87: KidJourneyViewModel 중복 코드 추출 및 정리
seungjae708 Feb 21, 2026
0cd0055
refactor/#87: S3 업로드 실패 시 에러 로깅 및 예외 처리 추가
seungjae708 Feb 21, 2026
e47346e
refactor/#87: KidCameraViewModel 내 isLoading 상태 업데이트 로직 개선
seungjae708 Feb 21, 2026
48212d9
Merge branch 'develop' of https://github.com/Team-Kiero/Kiero-Android…
seungjae708 Feb 25, 2026
d57466d
refactor/#87: `ImageUriManager` 내 코드 스타일 개선(apply) 및 임시 파일 생성 관련 안전성 강화
seungjae708 Feb 25, 2026
97b4430
refactor/#87: 이미지 업로드 아키텍처 개선 및 UseCase 도입
seungjae708 Feb 26, 2026
6e9cc3f
Merge branch 'develop' of https://github.com/Team-Kiero/Kiero-Android…
seungjae708 Mar 3, 2026
96c59fa
refactor/#87: `ImageUriManager.createTempImageUri`를 suspend 함수로 변경 및 …
seungjae708 Mar 5, 2026
a8c14ad
refactor/#87: 자녀 여정 내 컴포넌트 네이밍 변경
seungjae708 Mar 5, 2026
b96313d
refactor/#87: `KidCameraScreen` 내 `modifier` 파라미터 적용 오류 수정
seungjae708 Mar 5, 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
3 changes: 3 additions & 0 deletions .idea/deploymentTargetSelector.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 33 additions & 0 deletions app/src/main/java/com/kiero/core/common/util/ImageUriManager.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.kiero.core.common.util

import android.content.Context
import androidx.core.content.FileProvider
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.io.File
import java.io.IOException
import javax.inject.Inject

class ImageUriManager @Inject constructor(
@param:ApplicationContext private val context: Context
) {
suspend fun createTempImageUri(): String? = withContext(Dispatchers.IO) {
try {
val directory = File(context.cacheDir, "images").apply {
if (!exists()) mkdirs()
}
val file = File.createTempFile("IMG_", ".jpg", directory)

FileProvider.getUriForFile(
context,
"${context.packageName}.fileprovider",
file
).toString()
} catch (e: IOException) {
Timber.e(e, "파일 생성 실패")
null
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.kiero.data.kid.schedule.di

import com.kiero.data.kid.schedule.remote.datasource.ImageUploadDataSource
import com.kiero.data.kid.schedule.remote.datasource.ScheduleDataSource
import com.kiero.data.kid.schedule.remote.datasourceimpl.ImageUploadDataSourceImpl
import com.kiero.data.kid.schedule.remote.datasourceimpl.ScheduleDataSourceImpl
import dagger.Binds
import dagger.Module
Expand All @@ -16,4 +18,11 @@ abstract class ScheduleDataSourceModule {
abstract fun bindScheduleDataSource(
scheduleDataSourceImpl: ScheduleDataSourceImpl
): ScheduleDataSource

@Binds
@Singleton
abstract fun bindImageUploadDataSource(
imageUploadDataSourceImpl: ImageUploadDataSourceImpl
): ImageUploadDataSource

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.kiero.data.kid.schedule.di

import com.kiero.data.kid.schedule.repository.ImageUploadRepository
import com.kiero.data.kid.schedule.repository.ScheduleRepository
import com.kiero.data.kid.schedule.repositoryimpl.ImageUploadRepositoryImpl
import com.kiero.data.kid.schedule.repositoryimpl.ScheduleRepositoryImpl
import dagger.Binds
import dagger.Module
Expand All @@ -16,4 +18,10 @@ interface ScheduleRepositoryModule {
fun bindsScheduleRepository(
scheduleRepositoryImpl: ScheduleRepositoryImpl
) : ScheduleRepository

@Binds
@Singleton
fun bindImageUploadRepository(
imageUploadRepositoryImpl: ImageUploadRepositoryImpl
) : ImageUploadRepository
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.kiero.data.kid.schedule.local.datasource

import android.content.Context
import android.graphics.Bitmap
import android.graphics.ImageDecoder
import android.net.Uri
import android.os.Build
import androidx.core.net.toUri
import dagger.hilt.android.qualifiers.ApplicationContext
import timber.log.Timber
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileOutputStream
import java.util.UUID
import javax.inject.Inject
import kotlin.math.max

class ImageLocalDataSource @Inject constructor(
@param: ApplicationContext private val context: Context
) {
fun getOptimizedFile(uriString: String): File {
val uri = uriString.toUri()
val dir = getDirectory()
return compressToWebP(uri, dir)
}

fun clearCache() {
getDirectory().listFiles()?.forEach { it.delete() }
}

fun deleteOriginalUri(uriString: String) {
try {
context.contentResolver.delete(uriString.toUri(), null, null)
} catch (e: Exception) {
Timber.e(e, "원본 파일 삭제 실패")
}
}

private fun getDirectory(): File {
return File(context.cacheDir, DIRECTORY).apply {
if (!exists()) mkdirs()
}
}

private fun compressToWebP(uri: Uri, dir: File): File {
val source = ImageDecoder.createSource(context.contentResolver, uri)
val bitmap = ImageDecoder.decodeBitmap(source) { decoder, info, _ ->
decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
decoder.isMutableRequired = true

val size = info.size
val targetSize = calculateTargetSize(size.width, size.height)
decoder.setTargetSize(targetSize.first, targetSize.second)
}

val format = if (Build.VERSION.SDK_INT >= 30) {
Bitmap.CompressFormat.WEBP_LOSSY
} else {
Bitmap.CompressFormat.WEBP
}

val byteArray = ByteArrayOutputStream().use { stream ->
bitmap.compress(format, WEBP_QUALITY, stream)
bitmap.recycle()
stream.toByteArray()
}

val tempFile = File(dir, "${UUID.randomUUID()}.webp")
FileOutputStream(tempFile).use { it.write(byteArray) }

return tempFile
}

private fun calculateTargetSize(width: Int, height: Int): Pair<Int, Int> {
if (width <= MAX_SIZE && height <= MAX_SIZE) return width to height

val ratio = max(width.toFloat() / MAX_SIZE, height.toFloat() / MAX_SIZE)
return (width / ratio).toInt() to (height / ratio).toInt()
}

companion object {
private const val DIRECTORY = "image_cache"
private const val MAX_SIZE = 1024
private const val WEBP_QUALITY = 80
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.kiero.data.kid.schedule.remote.datasource

import com.kiero.core.network.model.BaseResponse
import com.kiero.data.kid.schedule.remote.dto.response.ScheduleImageUploadResponseDto
import okhttp3.RequestBody
import retrofit2.Response

interface ImageUploadDataSource {
suspend fun postPresignedUrl(
fileName: String,
contentType: String
): BaseResponse<ScheduleImageUploadResponseDto>

suspend fun uploadImageToS3(
presignedUrl: String,
image: RequestBody
): Response<Unit>
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,12 @@ package com.kiero.data.kid.schedule.remote.datasource

import com.kiero.core.network.model.BaseResponse
import com.kiero.data.kid.schedule.remote.dto.response.ScheduleFireResponseDto
import com.kiero.data.kid.schedule.remote.dto.response.ScheduleImageUploadResponseDto
import com.kiero.data.kid.schedule.remote.dto.response.ScheduleSkipResponseDto
import com.kiero.data.kid.schedule.remote.dto.response.ScheduleTodayResponseDto

interface ScheduleDataSource {
suspend fun patchScheduleToday(): BaseResponse<ScheduleTodayResponseDto>

suspend fun postPresignedUrl(
fileName: String,
contentType: String
): BaseResponse<ScheduleImageUploadResponseDto>

suspend fun patchScheduleComplete(
scheduleDetailId: Long,
imageUrl: String
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.kiero.data.kid.schedule.remote.datasourceimpl

import com.kiero.core.network.model.BaseResponse
import com.kiero.data.kid.schedule.remote.api.S3Service
import com.kiero.data.kid.schedule.remote.api.ScheduleService
import com.kiero.data.kid.schedule.remote.datasource.ImageUploadDataSource
import com.kiero.data.kid.schedule.remote.dto.request.ScheduleImageUploadRequestDto
import com.kiero.data.kid.schedule.remote.dto.response.ScheduleImageUploadResponseDto
import okhttp3.RequestBody
import retrofit2.Response
import javax.inject.Inject

class ImageUploadDataSourceImpl @Inject constructor(
private val scheduleService: ScheduleService,
private val s3Service: S3Service
) : ImageUploadDataSource {
override suspend fun postPresignedUrl(
fileName: String,
contentType: String
): BaseResponse<ScheduleImageUploadResponseDto> =
scheduleService.postPresignedUrl(
request = ScheduleImageUploadRequestDto(
fileName = fileName,
contentType = contentType
)
)

override suspend fun uploadImageToS3(
presignedUrl: String,
image: RequestBody
): Response<Unit> =
s3Service.uploadImageToS3(
url = presignedUrl,
image = image
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import com.kiero.core.network.model.BaseResponse
import com.kiero.data.kid.schedule.remote.api.ScheduleService
import com.kiero.data.kid.schedule.remote.datasource.ScheduleDataSource
import com.kiero.data.kid.schedule.remote.dto.request.ScheduleCompleteRequestDto
import com.kiero.data.kid.schedule.remote.dto.request.ScheduleImageUploadRequestDto
import com.kiero.data.kid.schedule.remote.dto.response.ScheduleFireResponseDto
import com.kiero.data.kid.schedule.remote.dto.response.ScheduleImageUploadResponseDto
import com.kiero.data.kid.schedule.remote.dto.response.ScheduleSkipResponseDto
import com.kiero.data.kid.schedule.remote.dto.response.ScheduleTodayResponseDto
import javax.inject.Inject
Expand All @@ -17,17 +15,6 @@ class ScheduleDataSourceImpl @Inject constructor(
override suspend fun patchScheduleToday(): BaseResponse<ScheduleTodayResponseDto> =
service.patchScheduleToday()

override suspend fun postPresignedUrl(
fileName: String,
contentType: String
): BaseResponse<ScheduleImageUploadResponseDto> =
service.postPresignedUrl(
request = ScheduleImageUploadRequestDto(
fileName = fileName,
contentType = contentType
)
)

override suspend fun patchScheduleComplete(
scheduleDetailId: Long,
imageUrl: String
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.kiero.data.kid.schedule.repository

interface ImageUploadRepository {
suspend fun uploadImage(
uriString: String,
fileName: String,
contentType: String = "image/jpeg"
): Result<String>
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
package com.kiero.data.kid.schedule.repository

import com.kiero.data.kid.schedule.model.ScheduleFireModel
import com.kiero.data.kid.schedule.model.ScheduleImageUploadModel
import com.kiero.data.kid.schedule.model.ScheduleTodayModel

interface ScheduleRepository {
suspend fun patchScheduleToday(): Result<ScheduleTodayModel>

suspend fun postPresignedUrl(
fileName: String,
contentType: String
): Result<ScheduleImageUploadModel>

suspend fun patchScheduleComplete(
scheduleDetailId: Long,
imageUrl: String
Expand Down
Loading