Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
7b51c01
[feat/#109] 마이페이지 바텀 네비바 숨김 처리 및 상단 바 제작
Hiimynameiss Sep 23, 2026
98e6774
[feat/#109] 마이페이지 뒤로 가기 버튼 연결
Hiimynameiss Sep 23, 2026
f8db424
[feat/#109] 마이페이지 - 설정 화면 연결
Hiimynameiss Sep 23, 2026
d8c7e01
[feat/#109] 설정 화면 구현
Hiimynameiss Sep 23, 2026
08cd12b
[mod/#109] 설정 화면 컴포넌트화
Hiimynameiss Sep 23, 2026
29363c8
[feat/#109] 아이콘 없 ver 다이얼로그 추가
Hiimynameiss Sep 25, 2026
a6bdbd5
[feat/#109] 로그아웃 다이얼로그 연결
Hiimynameiss Sep 25, 2026
7f340b7
[mod/#109] 공고 등록 화면의 다이얼로그 아이콘 있게 수정, 다이얼로그 너비 수정
Hiimynameiss Sep 25, 2026
0b59317
[feat/#109] 탈퇴 화면 구현
Hiimynameiss Sep 30, 2026
5a3dae5
[feat/#109] 사유 입력 창 카운터 추가
Hiimynameiss Oct 2, 2026
2ad6a97
[rename/#109] LeaveCheckList -> LeaveCheckItem
Hiimynameiss Oct 2, 2026
8a5d9bb
[feat/#109] 버튼 비활성화 구현
Hiimynameiss Oct 2, 2026
83e1781
[mod/#109] 사유 입력 텍스트 필드 기획 반영
Hiimynameiss Oct 2, 2026
54a47bb
[feat/#109] 화면 연결
Hiimynameiss Oct 2, 2026
293f8d2
[mod/#109] 자잘하게 디자인 반영
Hiimynameiss Oct 2, 2026
1a595b9
[del/#109] 주석 삭제
Hiimynameiss Oct 2, 2026
c236a1d
[feat/#109] 키보드가 사유 입력 칸 안 가리게
Hiimynameiss Oct 2, 2026
5fb4bde
[merge/#109] pulled develop
Hiimynameiss Oct 2, 2026
55ab088
[feat/#109] 회원 탈퇴 완료 화면 구현
Hiimynameiss Oct 2, 2026
ac54111
[mod/#109] 탈퇴 유의 사항 영역 색칠
Hiimynameiss Oct 2, 2026
7b1db5d
[feat/#109] 로그아웃 API 연동
Hiimynameiss Oct 2, 2026
9e05cbb
[feat/#109] FCM 토큰 삭제 한수 추가
Hiimynameiss Oct 2, 2026
22e9c9e
[feat/#109] 로그아웃 API Repository 구현
Hiimynameiss Oct 4, 2026
fbb28ff
[feat/#109] 로그아웃 네비게이션
Hiimynameiss Oct 4, 2026
6e1efd9
[fix/#109] Authorization 헤더 직접 전달
Hiimynameiss Oct 4, 2026
fac76f4
[mod/#109] 로그아웃 성공했을 때만 로컬 토큰 정리
Hiimynameiss Oct 4, 2026
9379108
[mod/#109] 중복 방지 및 로그아웃 실패 시 상태 유지
Hiimynameiss Oct 4, 2026
d9ef455
[mod/#109] 중복 방지 코드 수정
Hiimynameiss Oct 4, 2026
50d5a06
[feat/#109] 회원탈퇴 API 연동
Hiimynameiss Oct 4, 2026
0ed888c
[mod/#109] 탈퇴 사유 enum에 서버 값 연결
Hiimynameiss Oct 4, 2026
1b06b3b
[feat/#109] 회원 탈퇴 Contract에 로딩 상태 추가
Hiimynameiss Oct 4, 2026
31dbff7
[feat/#109] 회원 탈퇴 ViewModel 구현
Hiimynameiss Oct 4, 2026
a689868
[refactor/#109] 로그아웃 로직 Auth -> mypage
Hiimynameiss Oct 5, 2026
c6938f2
[mod/#109] 마이페이지를 네비게이션 바 예외에서 제외
Hiimynameiss Oct 5, 2026
14d88dd
[mod/#109] 이모지 포함 글자수 세기
Hiimynameiss Oct 5, 2026
3c8d4d6
[mod/#109] 다이얼로그 기본 여백 제거
Hiimynameiss Oct 5, 2026
b5a86ed
[del/#109] 불필요한 괄호 제거
Hiimynameiss Oct 5, 2026
eff33bb
[mod/#109] @Immutable 붙임
Hiimynameiss Oct 5, 2026
873ca9b
[merge/#109] pulled develop
Hiimynameiss Oct 5, 2026
4e7f215
[mod/#109] 불필요한 BoxWithConstraints 대체
Hiimynameiss Oct 5, 2026
dcc2e89
[mod/#109] 탈퇴 완료 화면의 navGraph 삭제
Hiimynameiss Oct 5, 2026
7b77682
[mod/#109] p3 리뷰 반영
Hiimynameiss Oct 5, 2026
88be358
[mod/#109] 탈퇴 화면 버튼 bottom 패딩 수정
Hiimynameiss Oct 5, 2026
5f0716b
[mod/#109] FCM 실패와 인증 토큰 삭제를 별도 작업으로 취급
Hiimynameiss Oct 5, 2026
36ab9d8
[mod/#109] deviceId가 null이면 서버 요청을 건너뜀
Hiimynameiss Oct 5, 2026
e84dd04
[merge/#109] pulled develop
Hiimynameiss Oct 6, 2026
571b586
[del/#109] 잘못된 import문 제거
Hiimynameiss Oct 6, 2026
15494a3
[mod/#109] 키보드만큼의 여백 주기 방법 간소화
Hiimynameiss Oct 6, 2026
6c3ba7c
[feat/#109] isNewUser 필드 추가, 기존 유저와 새로운 유저 구분 -> 회원가입 완료 화면 분기
Hiimynameiss Oct 6, 2026
80ada5e
[mod/#109] 중괄호 없앰
Hiimynameiss Oct 6, 2026
3cf0c41
[mod/#109] 인증 토큰만 관리
Hiimynameiss Oct 6, 2026
75d61f2
[del/#109] 불필요한 패딩 처리 삭제
Hiimynameiss Oct 6, 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
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import com.haphap.app.R
import com.haphap.app.core.designsystem.component.button.HapHapBasicButton
import com.haphap.app.core.designsystem.theme.HapHapTheme
Expand All @@ -31,6 +32,7 @@ import com.haphap.app.core.designsystem.type.ButtonType
* @param content 다이얼로그 본문에 표시할 안내 문구
* @param onDismiss 다이얼로그 바깥 영역 또는 "취소" 버튼 클릭 시 호출되는 콜백
* @param onConfirmClick "확인" 버튼 클릭 시 호출되는 콜백
* @param showIcon 다이얼로그에 아이콘이 있는 지 설정하는 변수
*/

@Composable
Expand All @@ -39,27 +41,37 @@ fun HapHapDialog(
onDismiss: () -> Unit,
onConfirmClick: () -> Unit,
modifier: Modifier = Modifier,
showIcon: Boolean = false,
) {
Dialog (
onDismissRequest = onDismiss,
properties = DialogProperties(usePlatformDefaultWidth = false),
) {
Column(
modifier = modifier
.padding(horizontal = 31.dp)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

p1: 다이얼로그는 기본 여백이 있어서 요거 있으면 오히려 다이얼로그 사이즈가 더 줄어들텐뎅?? 여백 제거하고 한번 확인해보셔용

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Dialog에 usePlatformDefaultWidth = false를 주면 기본 여백을 없앨 수 있다고 해서 기본 여백을 없애고 horizontal 패딩을 유지하는 방향으로 수정했습니당!!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

오호 혹시 왜 기본 여백 없앴는지 알 수 있을까용..?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

기본 여백만 주었더니 좌우 패딩이 디자인과 다르게 주어져서 기본 여백 없애고 디자인 상의 패딩을 주는 걸로 결정했습니다...!!

기본 여백만 준 상태 기본 여백x 좌우 31.dp
Screenshot_20261005_194359_HapHap Screenshot_20261005_194331_HapHap

.fillMaxWidth()
.background(
color = HapHapTheme.colors.white,
shape = RoundedCornerShape(16.dp),
)
.padding(start = 20.dp, top = 16.dp, end = 20.dp, bottom = 21.dp),
.padding(
start = 19.dp,
top = 16.dp,
end = 19.dp,
bottom = if (showIcon) 21.dp else 16.dp,
),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_alert_53),
contentDescription = null,
tint = HapHapTheme.colors.gray200,
)
if (showIcon) {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_alert_53),
contentDescription = null,
tint = HapHapTheme.colors.gray200,
)

Spacer(modifier = Modifier.height(8.dp))
Spacer(modifier = Modifier.height(8.dp))
}

Text(
text = content,
Expand All @@ -72,7 +84,7 @@ fun HapHapDialog(

Row(
modifier = modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(11.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
HapHapBasicButton(
text = "취소",
Expand All @@ -98,9 +110,10 @@ fun HapHapDialog(
private fun HapHapDialogPreview() {
HapHapTheme {
HapHapDialog(
content = "이전에 등록한 결과가 있습니다.\n결과를 변경할까요?",
content = "로그아웃 하시겠습니까?",
onDismiss = {},
onConfirmClick = {},
showIcon = false,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ val Sub300 = Color(0xFFC3D9FF)
val Sub400 = Color(0xFF041B50)
val Yellow = Color(0xFFFEE500)

val SubRed = Color(0xFFFF7D7D)

val Gray50 = Color(0xFFFAFBFB)
val Gray100 = Color(0xFFF3F4F7)
val Gray200 = Color(0xFFD2D6DB)
Expand All @@ -46,6 +48,7 @@ data class HapHapColors(
val sub300: Color,
val sub400: Color,
val yellow: Color,
val subred: Color,
val gray50: Color,
val gray100: Color,
val gray200: Color,
Expand All @@ -69,6 +72,7 @@ val defaultHapHapColors = HapHapColors(
sub300 = Sub300,
sub400 = Sub400,
yellow = Yellow,
subred = SubRed,
gray50 = Gray50,
gray100 = Gray100,
gray200 = Gray200,
Expand Down Expand Up @@ -98,6 +102,7 @@ private fun ColorPreview() {
HapHapTheme.colors.sub300,
HapHapTheme.colors.sub400,
HapHapTheme.colors.yellow,
HapHapTheme.colors.subred,

HapHapTheme.colors.gray50,
HapHapTheme.colors.gray100,
Expand Down
21 changes: 21 additions & 0 deletions app/src/main/java/com/haphap/app/core/extensions/StringExt.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.haphap.app.core.extensions

import java.text.BreakIterator
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
Expand All @@ -17,4 +18,24 @@ fun String.toTimeFormat(pattern: String = "HH:mm"): String {
return runCatching {
LocalDateTime.parse(this).format(DateTimeFormatter.ofPattern(pattern))
}.getOrDefault("")
}

/**
* 사람이 보는 글자(grapheme cluster) 단위로 길이를 센다.
*
* [CharSequence.length]는 UTF-16 Char 개수를 세기 때문에 이모지 하나가 2개 이상으로 세어지는 반면,
* 이 함수는 "😀", "👍🏻", "👨‍👩‍👧" 같은 이모지도 1글자로 센다.
*
* @receiver 길이를 셀 문자열
* @return 글자 수
*/
fun CharSequence.graphemeLength(): Int {
val iterator = BreakIterator.getCharacterInstance().apply { setText(this@graphemeLength.toString()) }

var count = 0
while (iterator.next() != BreakIterator.DONE) {
count++
}

return count
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ package com.haphap.app.data.local.datasource.api
interface LocalFcmDataSource {
suspend fun getFcmToken(): String?
suspend fun setFcmToken(fcmToken: String)
suspend fun clearFcmToken()
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ class LocalFcmDataSourceImpl @Inject constructor(
}
}

override suspend fun clearFcmToken() {
dataStore.edit { prefs ->
prefs.remove(FCM_TOKEN)
}
}

companion object {
private val FCM_TOKEN = stringPreferencesKey("FCM_TOKEN")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ fun KakaoLoginResponseDto.toModel(): KakaoLoginModel = KakaoLoginModel(
refreshToken = refreshToken,
name = name,
anonymousName = anonymousName,
isNewUser = isNewUser,
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ data class KakaoLoginModel(
val refreshToken: String,
val name: String,
val anonymousName: String,
val isNewUser: Boolean,
)
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package com.haphap.app.data.remote.datasource.api.mypage

import com.haphap.app.data.remote.dto.BaseResponse
import com.haphap.app.data.remote.dto.mypage.LeaveRequestDto
import com.haphap.app.data.remote.dto.mypage.LogoutRequestDto
import com.haphap.app.data.remote.dto.mypage.MyPageResponseDto
import retrofit2.Response

interface MyPageDataSource{
suspend fun getMyPage(): BaseResponse<MyPageResponseDto>
suspend fun deleteMember(requestDto: LeaveRequestDto): Response<Unit>
suspend fun postLogout(requestDto: LogoutRequestDto): Response<Unit>
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,23 @@ package com.haphap.app.data.remote.datasource.impl.mypage

import com.haphap.app.data.remote.datasource.api.mypage.MyPageDataSource
import com.haphap.app.data.remote.dto.BaseResponse
import com.haphap.app.data.remote.dto.mypage.LeaveRequestDto
import com.haphap.app.data.remote.dto.mypage.LogoutRequestDto
import com.haphap.app.data.remote.dto.mypage.MyPageResponseDto
import com.haphap.app.data.remote.service.MyPageService
import jakarta.inject.Inject
import retrofit2.Response

class MyPageDataSourceImpl @Inject constructor(
private val myPageService: MyPageService,
) : MyPageDataSource {
override suspend fun getMyPage(): BaseResponse<MyPageResponseDto> {
return myPageService.getMyPage()
}

override suspend fun deleteMember(requestDto: LeaveRequestDto): Response<Unit> =
myPageService.deleteMember(requestDto)

override suspend fun postLogout(requestDto: LogoutRequestDto): Response<Unit> =
myPageService.logout(requestDto)
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ data class KakaoLoginResponseDto(
val name: String,
@SerialName("anonymousName")
val anonymousName: String,
@SerialName("isNewUser")
val isNewUser: Boolean = false,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.haphap.app.data.remote.dto.mypage

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class LeaveRequestDto(
@SerialName("reason")
val reason: String,
@SerialName("etcReason")
val etcReason: String? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.haphap.app.data.remote.dto.mypage

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class LogoutRequestDto(
@SerialName("deviceId")
val deviceId: String,
)
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
package com.haphap.app.data.remote.service

import com.haphap.app.data.remote.dto.BaseResponse
import com.haphap.app.data.remote.dto.mypage.LeaveRequestDto
import com.haphap.app.data.remote.dto.mypage.LogoutRequestDto
import com.haphap.app.data.remote.dto.mypage.MyPageResponseDto
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.HTTP
import retrofit2.http.POST

interface MyPageService {
@GET("api/v1/members/me")
suspend fun getMyPage(): BaseResponse<MyPageResponseDto>

@HTTP(method = "DELETE", path = "api/v1/members/me", hasBody = true)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

p3: 오오 바디가 있어서 어노테이션을 HTTP로 쓰는구나.. 배워갑니닷!!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

POST, PUT, PATCH처럼 body가 있어도 @http 말고 어노테이션 그대로 사용할 수 있는 메서드들도 있답니당~😆

suspend fun deleteMember(@Body request: LeaveRequestDto): Response<Unit>

@POST("api/v1/auth/logout")
suspend fun logout(@Body request: LogoutRequestDto): Response<Unit>
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.haphap.app.data.repository.api.auth

import com.haphap.app.data.model.auth.KakaoLoginModel
import com.haphap.app.data.remote.dto.auth.KakaoLoginRequestDto

interface AuthRepository {
suspend fun postKakaoLogin(accessToken: String): Result<KakaoLoginModel>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ import com.haphap.app.data.model.mypage.MyPageModel

interface MyPageRepository {
suspend fun getMyPage(): Result<MyPageModel>
suspend fun deleteMember(reason: String, etcReason: String?): Result<Unit>
suspend fun postLogout(): Result<Unit>
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,58 @@
package com.haphap.app.data.repository.impl.mypage

import com.haphap.app.core.fcm.FirebaseMessagingManager
import com.haphap.app.core.util.suspendRunCatching
import com.haphap.app.data.local.datasource.api.LocalFcmDataSource
import com.haphap.app.data.local.datasource.api.LocalTokenDataSource
import com.haphap.app.data.mapper.mypage.toModel
import com.haphap.app.data.model.mypage.MyPageModel
import com.haphap.app.data.remote.datasource.api.mypage.MyPageDataSource
import com.haphap.app.data.remote.dto.checkData
import com.haphap.app.data.remote.dto.mypage.LeaveRequestDto
import com.haphap.app.data.remote.dto.mypage.LogoutRequestDto
import com.haphap.app.data.repository.api.mypage.MyPageRepository
import jakarta.inject.Inject
import retrofit2.HttpException
import timber.log.Timber

class MyPageRepositoryImpl @Inject constructor(
private val myPageDataSource: MyPageDataSource,
private val localTokenDataSource: LocalTokenDataSource,
private val localFcmDataSource: LocalFcmDataSource,
private val firebaseMessagingManager: FirebaseMessagingManager,
): MyPageRepository {
override suspend fun getMyPage(): Result<MyPageModel> =
suspendRunCatching {
val response = myPageDataSource.getMyPage().checkData()
response.toModel()
}

override suspend fun deleteMember(reason: String, etcReason: String?): Result<Unit> =
suspendRunCatching {
val response = myPageDataSource.deleteMember(
LeaveRequestDto(reason = reason, etcReason = etcReason),
)

if (!response.isSuccessful && response.code() != 404) throw HttpException(response)

localTokenDataSource.clearTokens()
}

override suspend fun postLogout(): Result<Unit> =
suspendRunCatching {
val accessToken = localTokenDataSource.getAccessToken()

if (!accessToken.isNullOrBlank()) {
val deviceId = firebaseMessagingManager.getInstallationId()

if (deviceId != null) {
val response = myPageDataSource.postLogout(LogoutRequestDto(deviceId))
if (!response.isSuccessful && response.code() != 401) throw HttpException(response)
} else {
Timber.w("Installation id is null. Skip remote logout")
}
}

localTokenDataSource.clearTokens()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ sealed interface LoginContract {

sealed class SideEffect {
data class NavigateToSignUpComplete(val userName: String) : SideEffect()
data object NavigateToHome : SideEffect()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,13 @@ import com.haphap.app.R
import com.haphap.app.core.designsystem.component.circular_progress_indicator.HapHapCircularProgressIndicator
import com.haphap.app.core.designsystem.theme.HapHapTheme
import com.haphap.app.core.extensions.noRippleClickable
import com.haphap.app.presentation.auth.login.LoginContract.SideEffect.NavigateToHome
import com.haphap.app.presentation.auth.login.LoginContract.SideEffect.NavigateToSignUpComplete

@Composable
fun LoginRoute(
navigateToSignUpComplete: (userName: String) -> Unit,
navigateToHome: () -> Unit,
modifier: Modifier = Modifier,
viewModel: LoginViewModel = hiltViewModel(),
) {
Expand All @@ -56,6 +58,8 @@ fun LoginRoute(
is NavigateToSignUpComplete -> {
navigateToSignUpComplete(sideEffect.userName)
}

is NavigateToHome -> navigateToHome()
}
}
}
Expand Down
Loading
Loading