From d0db6405519ac35a338285c0f6d78a058412ca3b Mon Sep 17 00:00:00 2001 From: stopstone Date: Thu, 20 Nov 2025 22:45:02 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20=EC=97=B0=EB=9D=BD=EC=B2=98=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EB=B0=8F=20=ED=91=9C=EC=8B=9C=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 연락처의 프로필 이미지를 가져와 Presigned URL을 통해 업로드하는 로직을 구현했습니다. - `ContactImageReader`를 추가하여 연락처 이미지 URI로부터 이미지 데이터를 읽어옵니다. - `ImageUploader`를 추가하여 OkHttp를 사용해 이미지를 업로드합니다. - 친구 목록 및 홈 화면에서 `ImageLoader`를 사용하여 프로필 이미지를 표시하도록 수정했습니다. --- .../near/data/mapper/FriendInitMapper.kt | 17 +++++- .../repository/DefaultFriendRepository.kt | 56 +++++++++++++++---- .../near/local/contact/ContactImageReader.kt | 49 ++++++++++++++++ .../near/network/uploader/ImageUploader.kt | 36 ++++++++++++ .../components/ContactLoadContent.kt | 13 ++++- .../feature/home/component/ContactItem.kt | 14 ++++- 6 files changed, 167 insertions(+), 18 deletions(-) create mode 100644 Near/app/src/main/java/com/alarmy/near/local/contact/ContactImageReader.kt create mode 100644 Near/app/src/main/java/com/alarmy/near/network/uploader/ImageUploader.kt diff --git a/Near/app/src/main/java/com/alarmy/near/data/mapper/FriendInitMapper.kt b/Near/app/src/main/java/com/alarmy/near/data/mapper/FriendInitMapper.kt index 0efe1617..ae570310 100644 --- a/Near/app/src/main/java/com/alarmy/near/data/mapper/FriendInitMapper.kt +++ b/Near/app/src/main/java/com/alarmy/near/data/mapper/FriendInitMapper.kt @@ -1,5 +1,6 @@ package com.alarmy.near.data.mapper +import com.alarmy.near.local.contact.ContactImageData import com.alarmy.near.model.Anniversary import com.alarmy.near.model.ContactFrequency import com.alarmy.near.model.DayOfWeek @@ -7,6 +8,7 @@ import com.alarmy.near.model.Friend import com.alarmy.near.model.Relation import com.alarmy.near.model.ReminderInterval import com.alarmy.near.network.request.ContactFrequencyInitRequest +import com.alarmy.near.network.request.ImageUploadRequest import com.alarmy.near.network.request.FriendInitItemRequest import com.alarmy.near.network.response.AnniversaryInitEntity import com.alarmy.near.network.response.ContactFrequencyInitEntity @@ -27,7 +29,10 @@ private fun String.formatPhoneNumber(): String = PhoneNumberFormatter.formatPhon /** * UI 모델을 서버 요청 모델로 변환 */ -fun FriendContactUIModel.toFriendInitItemRequest(providerType: String): FriendInitItemRequest = +fun FriendContactUIModel.toFriendInitItemRequest( + providerType: String, + imageUploadRequest: ImageUploadRequest?, +): FriendInitItemRequest = FriendInitItemRequest( name = name, phone = phones.firstOrNull()?.formatPhoneNumber() ?: "", @@ -35,7 +40,7 @@ fun FriendContactUIModel.toFriendInitItemRequest(providerType: String): FriendIn birthDay = birthDay, source = providerType, contactFrequency = createContactFrequencyRequest(reminderInterval!!), - imageUploadRequest = null, + imageUploadRequest = imageUploadRequest, anniversary = null, relation = "FRIEND", // 기본값으로 FRIEND 설정 ) @@ -102,3 +107,11 @@ private fun String.toRelation(): Relation = .onFailure { exception -> NearLog.w("잘못된 관계 값: '$this', 기본값(FRIEND) 사용") }.getOrDefault(Relation.FRIEND) + +fun ContactImageData.toImageUploadRequest(category: String): ImageUploadRequest = + ImageUploadRequest( + fileName = fileName, + contentType = contentType, + fileSize = fileSize, + category = category, + ) diff --git a/Near/app/src/main/java/com/alarmy/near/data/repository/DefaultFriendRepository.kt b/Near/app/src/main/java/com/alarmy/near/data/repository/DefaultFriendRepository.kt index 31045cfa..b172dbcb 100644 --- a/Near/app/src/main/java/com/alarmy/near/data/repository/DefaultFriendRepository.kt +++ b/Near/app/src/main/java/com/alarmy/near/data/repository/DefaultFriendRepository.kt @@ -1,15 +1,20 @@ package com.alarmy.near.data.repository import com.alarmy.near.data.mapper.toFriendInitItemRequest +import com.alarmy.near.data.mapper.toImageUploadRequest import com.alarmy.near.data.mapper.toModel import com.alarmy.near.data.mapper.toRequest +import com.alarmy.near.local.contact.ContactImageData +import com.alarmy.near.local.contact.ContactImageReader import com.alarmy.near.model.Friend import com.alarmy.near.model.FriendRecord import com.alarmy.near.model.friendsummary.FriendSummary import com.alarmy.near.model.monthly.MonthlyFriend +import com.alarmy.near.network.request.FriendInitItemRequest import com.alarmy.near.network.request.FriendInitRequest import com.alarmy.near.network.response.FriendInitItemEntity import com.alarmy.near.network.service.FriendService +import com.alarmy.near.network.uploader.ImageUploader import com.alarmy.near.presentation.feature.friendcontactcycle.model.FriendContactUIModel import com.alarmy.near.utils.extensions.apiCallFlow import kotlinx.coroutines.flow.Flow @@ -20,6 +25,8 @@ class DefaultFriendRepository @Inject constructor( private val friendService: FriendService, + private val contactImageReader: ContactImageReader, + private val imageUploader: ImageUploader, ) : FriendRepository { override fun fetchFriends(): Flow> = flow { @@ -79,16 +86,43 @@ class DefaultFriendRepository providerType: String, ): Flow> = apiCallFlow { - // UI 모델을 Data 모델로 변환 - val friendInitRequest = - FriendInitRequest( - friendList = - contacts - .filter { it.reminderInterval != null } - .map { it.toFriendInitItemRequest(providerType) }, - ) - - // 서버 요청 및 응답 반환 - friendService.initFriends(friendInitRequest).friendList + val payloads = + contacts + .filter { it.reminderInterval != null } + .map { contact -> + val imageData = contact.photoUri?.let { uri -> contactImageReader.read(uri) } + val request = + contact.toFriendInitItemRequest( + providerType = providerType, + imageUploadRequest = imageData?.toImageUploadRequest(PROFILE_IMAGE_CATEGORY), + ) + FriendInitRequestPayload( + request = request, + imageData = imageData, + ) + } + val friendInitRequest = FriendInitRequest(friendList = payloads.map { it.request }) + val response = friendService.initFriends(friendInitRequest) + response.friendList.forEachIndexed { index, entity -> + val uploadUrl = entity.preSignedImageUrl + val imageData = payloads.getOrNull(index)?.imageData + if (uploadUrl != null && imageData != null) { + imageUploader.upload( + url = uploadUrl, + contentType = imageData.contentType, + data = imageData.data, + ) + } + } + response.friendList } + + private data class FriendInitRequestPayload( + val request: FriendInitItemRequest, + val imageData: ContactImageData?, + ) + + companion object { + private const val PROFILE_IMAGE_CATEGORY = "PROFILE" + } } diff --git a/Near/app/src/main/java/com/alarmy/near/local/contact/ContactImageReader.kt b/Near/app/src/main/java/com/alarmy/near/local/contact/ContactImageReader.kt new file mode 100644 index 00000000..6d0286f1 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/local/contact/ContactImageReader.kt @@ -0,0 +1,49 @@ +package com.alarmy.near.local.contact + +import android.content.ContentResolver +import android.net.Uri +import android.webkit.MimeTypeMap +import javax.inject.Inject + +data class ContactImageData( + val fileName: String, + val contentType: String, + val fileSize: Int, + val data: ByteArray, +) + +class ContactImageReader + @Inject + constructor( + private val contentResolver: ContentResolver, + ) { + fun read(uriString: String): ContactImageData? { + val uri = runCatching { Uri.parse(uriString) }.getOrNull() ?: return null + val bytes = contentResolver.openInputStream(uri)?.use { inputStream -> inputStream.readBytes() } ?: return null + val resolvedMimeType = contentResolver.getType(uri) ?: guessMimeType(uriString) ?: DEFAULT_MIME_TYPE + val extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(resolvedMimeType) ?: DEFAULT_EXTENSION + val fileName = "contact_${System.currentTimeMillis()}.$extension" + return ContactImageData( + fileName = fileName, + contentType = resolvedMimeType, + fileSize = bytes.size, + data = bytes, + ) + } + + private fun guessMimeType(uriString: String): String? { + val lowerCase = uriString.lowercase() + return when { + lowerCase.endsWith(".png") -> "image/png" + lowerCase.endsWith(".webp") -> "image/webp" + lowerCase.endsWith(".jpg") || lowerCase.endsWith(".jpeg") -> "image/jpeg" + else -> null + } + } + + companion object { + private const val DEFAULT_MIME_TYPE = "image/jpeg" + private const val DEFAULT_EXTENSION = "jpg" + } + } + diff --git a/Near/app/src/main/java/com/alarmy/near/network/uploader/ImageUploader.kt b/Near/app/src/main/java/com/alarmy/near/network/uploader/ImageUploader.kt new file mode 100644 index 00000000..a84acf7a --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/network/uploader/ImageUploader.kt @@ -0,0 +1,36 @@ +package com.alarmy.near.network.uploader + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ImageUploader + @Inject + constructor() { + private val okHttpClient = OkHttpClient() + + suspend fun upload( + url: String, + contentType: String, + data: ByteArray, + ) = withContext(Dispatchers.IO) { + val requestBody = data.toRequestBody(contentType.toMediaTypeOrNull()) + val request = + Request + .Builder() + .url(url) + .put(requestBody) + .build() + okHttpClient.newCall(request).execute().use { response -> + if (!response.isSuccessful) { + throw IllegalStateException("이미지 업로드에 실패했습니다.") + } + } + } + } diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendcontactcycle/components/ContactLoadContent.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendcontactcycle/components/ContactLoadContent.kt index 0d57214e..1b593a6d 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendcontactcycle/components/ContactLoadContent.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendcontactcycle/components/ContactLoadContent.kt @@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -28,6 +29,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.alarmy.near.R import com.alarmy.near.presentation.feature.friendcontactcycle.model.FriendContactUIModel +import com.alarmy.near.presentation.ui.extension.ImageLoader import com.alarmy.near.presentation.ui.extension.onNoRippleClick import com.alarmy.near.presentation.ui.theme.NearTheme @@ -116,10 +118,15 @@ fun FriendListItem( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.weight(1f), ) { - Image( - painter = painterResource(R.drawable.img_64_user_gray), + ImageLoader( + uri = contact.photoUri, + modifier = + Modifier + .size(24.dp) + .clip(CircleShape), + placeholder = R.drawable.img_64_user_gray, + error = R.drawable.img_64_user_gray, contentDescription = null, - modifier = Modifier.size(24.dp), ) Spacer(modifier = Modifier.size(12.dp)) diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/component/ContactItem.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/component/ContactItem.kt index 892176ff..109fe2da 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/component/ContactItem.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/component/ContactItem.kt @@ -1,6 +1,7 @@ package com.alarmy.near.presentation.feature.home.component import androidx.compose.foundation.Image +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -8,11 +9,13 @@ import androidx.compose.foundation.layout.Spacer 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.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow @@ -21,6 +24,7 @@ import androidx.compose.ui.unit.dp import com.alarmy.near.R import com.alarmy.near.model.friendsummary.ContactFrequencyLevel import com.alarmy.near.model.friendsummary.FriendSummary +import com.alarmy.near.presentation.ui.extension.ImageLoader import com.alarmy.near.presentation.ui.extension.onNoRippleClick import com.alarmy.near.presentation.ui.theme.NearTheme @@ -40,8 +44,14 @@ fun ContactItem( horizontalAlignment = Alignment.CenterHorizontally, ) { Box { - Image( - painter = painterResource(R.drawable.img_64_user1), + ImageLoader( + uri = friendSummary.profileImageUrl, + modifier = + Modifier + .size(64.dp) + .clip(CircleShape), + placeholder = R.drawable.img_64_user1, + error = R.drawable.img_64_user1, contentDescription = "", ) Image( From d8328411979461009039cca2df6e91b29615b7f2 Mon Sep 17 00:00:00 2001 From: stopstone Date: Thu, 20 Nov 2025 22:49:33 +0900 Subject: [PATCH 2/6] =?UTF-8?q?refactor:=20=EC=B9=9C=EA=B5=AC=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EB=A1=9C?= =?UTF-8?q?=EB=94=A9=20=EB=B0=A9=EC=8B=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `ImageLoader`를 사용하여 친구의 프로필 이미지를 동적으로 로드하도록 수정했습니다. - 이미지 로딩 실패 시 표시할 플레이스홀더와 에러 이미지를 설정했습니다. - 프로필 이미지를 원형으로 표시하기 위해 `clip(CircleShape)`를 적용했습니다. --- .../friendprofile/FriendProfileScreen.kt | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofile/FriendProfileScreen.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofile/FriendProfileScreen.kt index 9ca3720d..50a2eee5 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofile/FriendProfileScreen.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/friendprofile/FriendProfileScreen.kt @@ -17,10 +17,12 @@ 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.layout.widthIn import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.HorizontalDivider @@ -39,6 +41,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.composed +import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color @@ -70,6 +73,7 @@ import com.alarmy.near.presentation.ui.component.appbar.NearTopAppbar import com.alarmy.near.presentation.ui.component.button.NearSolidTypeButton import com.alarmy.near.presentation.ui.component.dropdown.NearDropdownMenu import com.alarmy.near.presentation.ui.component.dropdown.NearDropdownMenuItem +import com.alarmy.near.presentation.ui.extension.ImageLoader import com.alarmy.near.presentation.ui.extension.onNoRippleClick import com.alarmy.near.presentation.ui.theme.NearTheme import kotlinx.coroutines.delay @@ -248,11 +252,20 @@ fun FriendProfileScreen( .padding(horizontal = 32.dp), verticalAlignment = Alignment.CenterVertically, ) { - Box { - // 이미지 + 이모지 - Image( - modifier = Modifier.align(Alignment.Center), - painter = painterResource(R.drawable.img_80_user1), + Box( + modifier = + Modifier + .size(80.dp), + contentAlignment = Alignment.Center, + ) { + ImageLoader( + uri = friend.imageUrl, + modifier = + Modifier + .matchParentSize() + .clip(CircleShape), + placeholder = R.drawable.img_80_user1, + error = R.drawable.img_80_user1, contentDescription = null, ) Image( @@ -345,7 +358,7 @@ fun FriendProfileScreen( NearTheme.colors.GRAY01_888888.copy( alpha = 0.3f, ), - selected = currentTabPosition.intValue == 0, + selected = currentTabPosition.intValue == 0, onClick = { currentTabPosition.intValue = 0 }, ) { Text( @@ -375,9 +388,10 @@ fun FriendProfileScreen( .height(50.dp), selected = currentTabPosition.intValue == 1, onClick = { currentTabPosition.intValue = 1 }, - selectedContentColor = NearTheme.colors.GRAY01_888888.copy( - alpha = 0.3f - ), + selectedContentColor = + NearTheme.colors.GRAY01_888888.copy( + alpha = 0.3f, + ), ) { Text( text = stringResource(R.string.friend_profile_tab_text_record), From 76960375b1eb78ccfa1d7eaad7d5a36634dc8306 Mon Sep 17 00:00:00 2001 From: stopstone Date: Thu, 20 Nov 2025 23:00:01 +0900 Subject: [PATCH 3/6] =?UTF-8?q?refactor:=20ImageUploader=EC=97=90=20OkHttp?= =?UTF-8?q?Client=20=EC=A3=BC=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/alarmy/near/network/uploader/ImageUploader.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Near/app/src/main/java/com/alarmy/near/network/uploader/ImageUploader.kt b/Near/app/src/main/java/com/alarmy/near/network/uploader/ImageUploader.kt index a84acf7a..e562307f 100644 --- a/Near/app/src/main/java/com/alarmy/near/network/uploader/ImageUploader.kt +++ b/Near/app/src/main/java/com/alarmy/near/network/uploader/ImageUploader.kt @@ -12,8 +12,9 @@ import javax.inject.Singleton @Singleton class ImageUploader @Inject - constructor() { - private val okHttpClient = OkHttpClient() + constructor( + private val okHttpClient: OkHttpClient, + ) { suspend fun upload( url: String, From c7e4a2089c696f478bb3be77acc324ae1a4fca5e Mon Sep 17 00:00:00 2001 From: stopstone Date: Thu, 20 Nov 2025 23:01:40 +0900 Subject: [PATCH 4/6] =?UTF-8?q?refactor:=20=EC=B9=9C=EA=B5=AC=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=20=EB=B3=91=EB=A0=AC=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `initFriends` 함수 내에서 친구 프로필 이미지를 업로드할 때, `coroutineScope`와 `launch`를 사용하여 각 이미지를 병렬로 업로드하도록 수정했습니다. - 이를 통해 여러 친구를 한 번에 추가할 때 이미지 업로드 시간을 단축하고 성능을 개선했습니다. --- .../repository/DefaultFriendRepository.kt | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/Near/app/src/main/java/com/alarmy/near/data/repository/DefaultFriendRepository.kt b/Near/app/src/main/java/com/alarmy/near/data/repository/DefaultFriendRepository.kt index b172dbcb..998f8e8e 100644 --- a/Near/app/src/main/java/com/alarmy/near/data/repository/DefaultFriendRepository.kt +++ b/Near/app/src/main/java/com/alarmy/near/data/repository/DefaultFriendRepository.kt @@ -17,8 +17,10 @@ import com.alarmy.near.network.service.FriendService import com.alarmy.near.network.uploader.ImageUploader import com.alarmy.near.presentation.feature.friendcontactcycle.model.FriendContactUIModel import com.alarmy.near.utils.extensions.apiCallFlow +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.launch import javax.inject.Inject class DefaultFriendRepository @@ -103,15 +105,19 @@ class DefaultFriendRepository } val friendInitRequest = FriendInitRequest(friendList = payloads.map { it.request }) val response = friendService.initFriends(friendInitRequest) - response.friendList.forEachIndexed { index, entity -> - val uploadUrl = entity.preSignedImageUrl - val imageData = payloads.getOrNull(index)?.imageData - if (uploadUrl != null && imageData != null) { - imageUploader.upload( - url = uploadUrl, - contentType = imageData.contentType, - data = imageData.data, - ) + coroutineScope { + response.friendList.forEachIndexed { index, entity -> + val uploadUrl = entity.preSignedImageUrl + val imageData = payloads.getOrNull(index)?.imageData + if (uploadUrl != null && imageData != null) { + launch { + imageUploader.upload( + url = uploadUrl, + contentType = imageData.contentType, + data = imageData.data, + ) + } + } } } response.friendList From a2b8d8a075f84f7c046a8dc445863a13692fefc9 Mon Sep 17 00:00:00 2001 From: stopstone Date: Tue, 2 Dec 2025 15:14:32 +0900 Subject: [PATCH 5/6] =?UTF-8?q?feat:=20ContactImageData=EC=97=90=20equals,?= =?UTF-8?q?=20hashCode=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `ContactImageData` 데이터 클래스에 `equals`와 `hashCode` 메서드를 오버라이드하여 객체 비교가 올바르게 동작하도록 수정했습니다. --- .../near/local/contact/ContactImageReader.kt | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/Near/app/src/main/java/com/alarmy/near/local/contact/ContactImageReader.kt b/Near/app/src/main/java/com/alarmy/near/local/contact/ContactImageReader.kt index 6d0286f1..4558c20f 100644 --- a/Near/app/src/main/java/com/alarmy/near/local/contact/ContactImageReader.kt +++ b/Near/app/src/main/java/com/alarmy/near/local/contact/ContactImageReader.kt @@ -1,8 +1,8 @@ package com.alarmy.near.local.contact import android.content.ContentResolver -import android.net.Uri import android.webkit.MimeTypeMap +import androidx.core.net.toUri import javax.inject.Inject data class ContactImageData( @@ -10,7 +10,26 @@ data class ContactImageData( val contentType: String, val fileSize: Int, val data: ByteArray, -) +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + other as ContactImageData + if (fileName != other.fileName) return false + if (contentType != other.contentType) return false + if (fileSize != other.fileSize) return false + if (!data.contentEquals(other.data)) return false + return true + } + + override fun hashCode(): Int { + var result = fileName.hashCode() + result = 31 * result + contentType.hashCode() + result = 31 * result + fileSize + result = 31 * result + data.contentHashCode() + return result + } +} class ContactImageReader @Inject @@ -18,7 +37,7 @@ class ContactImageReader private val contentResolver: ContentResolver, ) { fun read(uriString: String): ContactImageData? { - val uri = runCatching { Uri.parse(uriString) }.getOrNull() ?: return null + val uri = runCatching { uriString.toUri() }.getOrNull() ?: return null val bytes = contentResolver.openInputStream(uri)?.use { inputStream -> inputStream.readBytes() } ?: return null val resolvedMimeType = contentResolver.getType(uri) ?: guessMimeType(uriString) ?: DEFAULT_MIME_TYPE val extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(resolvedMimeType) ?: DEFAULT_EXTENSION @@ -46,4 +65,3 @@ class ContactImageReader private const val DEFAULT_EXTENSION = "jpg" } } - From c4d82c5c1c9291df04e1b65e305bf52eb6b813e9 Mon Sep 17 00:00:00 2001 From: StopStone Date: Tue, 2 Dec 2025 22:52:51 +0900 Subject: [PATCH 6/6] =?UTF-8?q?refactor:=20=EB=8B=A4=EC=9D=8C=20=EC=97=B0?= =?UTF-8?q?=EB=9D=BD=20=EC=A3=BC=EA=B8=B0=20=ED=91=9C=EC=8B=9C=20=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EB=A7=81=ED=85=9C=ED=94=8C=EB=A6=BF=20=ED=98=95?= =?UTF-8?q?=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/bottomsheet/CycleSettingBottomSheet.kt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/bottomsheet/CycleSettingBottomSheet.kt b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/bottomsheet/CycleSettingBottomSheet.kt index 394b7cb9..36823d3a 100644 --- a/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/bottomsheet/CycleSettingBottomSheet.kt +++ b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/bottomsheet/CycleSettingBottomSheet.kt @@ -126,11 +126,10 @@ fun CycleSettingBottomSheet( Text( text = - stringResource(R.string.friend_contact_cycle_next_cycle_prefix) + - ( - selectedInterval?.let { DateExtension.getNextCycleDate(it) } - ?: DateExtension.getNextWeekSameDay() - ), + "${stringResource(R.string.friend_contact_cycle_next_cycle_prefix)} ${ + selectedInterval?.let { DateExtension.getNextCycleDate(it) } + ?: DateExtension.getNextWeekSameDay() + }", style = NearTheme.typography.B2_14_MEDIUM, color = NearTheme.colors.GRAY01_888888, ) @@ -199,4 +198,3 @@ fun CycleSettingBottomSheetPreview() { ) } } -