Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
140 commits
Select commit Hold shift + click to select a range
53a5f05
refactor: Jetpack Compose 의존성 및 빌드 설정 추가 #489
edv-Shin Apr 22, 2026
e5b8df5
refactor: Compose Theme (Color, Typography, Shape) 추가 #489
edv-Shin Apr 22, 2026
b991797
refactor: Compose 공통 UI 컴포넌트 추가 (Button, TextField, AlertDialog, TopB…
edv-Shin Apr 22, 2026
dd253d9
Merge branch 'dev' into refactor/compose-489
edv-Shin Apr 23, 2026
a64c1aa
refactor: Compose 공통 컴포넌트 API 정리 및 XML 비주얼 매칭 #489
edv-Shin Apr 24, 2026
7533b6b
docs: Compose ↔ XML 병행 운영 가이드 추가 #489
edv-Shin Apr 24, 2026
05faf5f
fix: ktlint 문법 수정 #489
edv-Shin Apr 24, 2026
6bc4442
chore: ktlint 에서 @Composable 함수 네이밍 규칙 예외 처리 #489
edv-Shin Apr 24, 2026
d17a690
Merge pull request #495 from projects200/refactor-compose-489
edv-Shin Apr 24, 2026
f3793ef
refactor: 설정 화면 Compose 로 전환 #491
edv-Shin Apr 24, 2026
38057c3
Merge pull request #496 from projects200/refactor/compose-491
edv-Shin Apr 24, 2026
48ca537
refactor: 알림 설정 화면 Compose 전환 #497
edv-Shin Apr 27, 2026
d875431
docs: Compose ↔ XML 병행 운영 가이드 제거
edv-Shin Apr 28, 2026
7d93151
refactor: 공통 BottomSheet 컴포넌트 추가 + SelectionBottomSheetDialog 4곳 교체 #499
edv-Shin May 3, 2026
c0ec06d
Merge pull request #498 from projects200/refactor/compose-497
edv-Shin May 3, 2026
b998658
Merge pull request #500 from projects200/refactor/compose-499
edv-Shin May 3, 2026
e4ba547
refactor: MenuBottomSheetDialog 3곳 교체 + UpdateDialogFragment Compose …
edv-Shin May 4, 2026
7ac7082
chore: dialog_base_alert.xml 의 무의미한 layout_constraint attr 제거 #501
edv-Shin May 4, 2026
5a43049
Merge pull request #502 from projects200/refactor/compose-501
edv-Shin May 4, 2026
87e9dfb
refactor: TermsDialogFragment Compose 전환 + BaseAlertDialog 3곳 → Undab…
edv-Shin May 4, 2026
24a31a0
Merge pull request #504 from projects200/refactor/compose-503
edv-Shin May 4, 2026
1fd3422
refactor: DatePickerDialogFragment Compose 전환 (M3 DatePicker) #505
edv-Shin May 4, 2026
67aa3f5
Merge pull request #506 from projects200/refactor/compose-505
edv-Shin Jun 2, 2026
94d80d5
chore: Compose 공통 컴포넌트 KDoc 정리
edv-Shin Jun 2, 2026
d288333
chore: Compose convention plugin 추가 #507
edv-Shin Jun 2, 2026
d2624e1
refactor: TermsFragment Compose 전환 #507
edv-Shin Jun 2, 2026
568fc48
refactor: PermissionFragment Compose 전환 #509
edv-Shin Jun 2, 2026
e68891b
refactor: ProfileImageDetailFragment Compose 전환 #511
edv-Shin Jun 3, 2026
ab13b2b
fix: TermsViewModelTest StateFlow 전환에 맞춰 갱신 #507
edv-Shin Jun 3, 2026
4b9ff4e
fix: TermsViewModelTest StateFlow 전환에 맞춰 갱신 #507
edv-Shin Jun 3, 2026
1f476a6
fix: TermsViewModelTest StateFlow 전환에 맞춰 갱신 #507
edv-Shin Jun 3, 2026
3a83d4d
refactor: ProfileEditFragment Compose 전환 #513
edv-Shin Jun 4, 2026
c328528
refactor: PreferredExercise 화면 묶음 Compose 전환 #515
edv-Shin Jun 4, 2026
a8fc294
refactor: BlockMembersFragment Compose 전환 #517
edv-Shin Jun 5, 2026
2d352fe
fix: UndabangAvatar의 placeholder를 ic_profile_default로 변경 #517
edv-Shin Jun 5, 2026
ed388ae
refactor: 타이머 목록 화면 Compose 전환 #519
edv-Shin Jun 5, 2026
20698b5
refactor: 매칭 안내 화면 Compose 전환 #521
edv-Shin Jun 6, 2026
ca19d3b
refactor: 운동 장소 목록 화면 Compose 전환 #523
edv-Shin Jun 7, 2026
d35d4bd
fix: ExercisePlaceViewModelTest SharedFlow 전환에 맞춰 갱신 #523
edv-Shin Jun 9, 2026
e04e9b2
refactor: 운동 장소 등록 화면 Compose 전환 #525
edv-Shin Jun 7, 2026
95e6195
refactor: 회원가입 화면 Compose 전환 #527
edv-Shin Jun 9, 2026
62b1b69
Merge pull request #508 from projects200/refactor/compose-507
edv-Shin Jun 12, 2026
4f41c58
Merge pull request #529 from projects200/refactor/compose-509
edv-Shin Jun 12, 2026
001bb12
Merge pull request #512 from projects200/refactor/compose-profile-ima…
edv-Shin Jun 12, 2026
a6f59c2
Merge pull request #514 from projects200/refactor/compose-profile-edi…
edv-Shin Jun 12, 2026
766bf06
Merge pull request #516 from projects200/refactor/compose-preferred-e…
edv-Shin Jun 12, 2026
d986265
Merge pull request #518 from projects200/refactor/compose-block-membe…
edv-Shin Jun 12, 2026
1d3fa3d
Merge pull request #520 from projects200/refactor/compose-timer-list-519
edv-Shin Jun 12, 2026
ea60a7e
Merge pull request #522 from projects200/refactor/compose-matching-gu…
edv-Shin Jun 12, 2026
292d4f8
Merge pull request #524 from projects200/refactor/compose-exercise-pl…
edv-Shin Jun 12, 2026
26a54c4
Merge pull request #526 from projects200/refactor/compose-exercise-pl…
edv-Shin Jun 12, 2026
05f0f6c
Merge pull request #528 from projects200/refactor/compose-register-527
edv-Shin Jun 12, 2026
5f5f5b4
refactor: UndabangScaffold 공통 wrapper + verticalScroll 누락 6개 화면 적용 #530
edv-Shin Jun 13, 2026
4c9ebd3
refactor: 커스텀 타이머 폼 화면 Compose 전환 #532
edv-Shin Jun 15, 2026
76cf49c
fix: LazyColumn 하단까지 채우도록 weight(1f) 적용 #532
edv-Shin Jun 15, 2026
53577be
refactor: 피드 작성 화면 Compose 전환 #534
edv-Shin Jun 15, 2026
4f876f7
refactor: 피드 목록 화면 Compose 전환 #536
edv-Shin Jun 15, 2026
b052641
refactor: 피드 상세 화면 Compose 전환 #538
edv-Shin Jun 15, 2026
01a5cce
refactor: 운동 기록 작성 화면 Compose 전환 #540
edv-Shin Jun 15, 2026
b0c0ab5
refactor: 운동 기록 상세 화면 Compose 전환 #542
edv-Shin Jun 15, 2026
13b4d01
refactor: SettingScreen 메뉴 항목을 LazyColumn으로 정리 #530
edv-Shin Jun 20, 2026
0a09f56
Merge pull request #531 from projects200/refactor/scaffold-and-scroll
edv-Shin Jun 20, 2026
c461d9b
Merge pull request #533 from projects200/refactor/compose-custom-time…
edv-Shin Jun 20, 2026
4280aee
Merge pull request #535 from projects200/refactor/compose-feed-form-534
edv-Shin Jun 20, 2026
3d88aa8
Merge pull request #537 from projects200/refactor/compose-feed-list-536
edv-Shin Jun 20, 2026
61a8b84
Merge pull request #539 from projects200/refactor/compose-feed-detail…
edv-Shin Jun 20, 2026
dcf55f0
Merge pull request #541 from projects200/refactor/compose-exercise-fo…
edv-Shin Jun 20, 2026
cf09661
Merge pull request #543 from projects200/refactor/compose-exercise-de…
edv-Shin Jun 20, 2026
601b687
refactor: 심플 타이머 화면 Compose 전환 #544
edv-Shin Jun 21, 2026
77830c0
refactor: 커스텀 타이머 실행 화면 Compose 전환 #545
edv-Shin Jun 22, 2026
ab2411b
fix: 원형 progress animateFloatAsState 제거 #545
edv-Shin Jun 26, 2026
cb207a3
fix: 원형 progress animateFloatAsState 제거 #544
edv-Shin Jun 27, 2026
b453c2e
fix: SimpleTimer 원형 progress drawBehind + lambda 패턴 적용 #544
edv-Shin Jun 27, 2026
ce72242
fix: CustomTimer 원형 progress drawBehind + lambda 패턴 적용 #545
edv-Shin Jun 27, 2026
d625fde
refactor: KakaoMapView Compose 재사용 컴포넌트 추가 (#546)
edv-Shin Jun 29, 2026
e566ed4
refactor: MatchingMapScreen 추가 - combinedMapData 구독해 마커/클러스터 그리기 (#546)
edv-Shin Jun 29, 2026
8b8b3e2
refactor: MatchingMapFragment 지도 본체 ComposeView로 교체 (#546)
edv-Shin Jun 30, 2026
029ad59
refactor: MatchingMapViewModel initialMapPosition을 StateFlow로 전환 (#546)
edv-Shin Jun 30, 2026
3d21778
refactor: MatchingMapController로 카메라 제어 + 현재위치/초기복원 복원 (#546)
edv-Shin Jun 30, 2026
667cdbc
refactor: MatchingMapController에서 미사용 isReady/currentBounds 제거 (#546)
edv-Shin Jul 2, 2026
76e2280
refactor: 현재위치 버튼을 Compose 오버레이로 이동 (#550)
edv-Shin Jul 1, 2026
d280156
refactor: 현재위치 버튼 XML/리스너 제거 (#550)
edv-Shin Jul 1, 2026
77d671f
refactor: 필터 칩 줄 Compose 컴포넌트 MatchingFilterRow 추가 (#550)
edv-Shin Jul 2, 2026
bbf9a43
refactor: 상단 툴바·필터 로딩을 MatchingMapScreen에 통합 (#550)
edv-Shin Jul 2, 2026
0c578b7
refactor: 지도 화면을 단일 ComposeView로 통합 + 필터 RV 잔재 제거 (#550)
edv-Shin Jul 2, 2026
4b86b77
Merge pull request #547 from projects200/refactor/compose-simple-time…
edv-Shin Jul 2, 2026
b063f38
Merge pull request #552 from projects200/refactor/compose-custom-time…
edv-Shin Jul 2, 2026
6722a44
refactor: KakaoMapView Preview 시 modifier 적용 placeholder 표시
edv-Shin Jul 7, 2026
17a5225
refactor: KakaoMapView MapView remember 키에 context 추가
edv-Shin Jul 7, 2026
58e0626
refactor: KakaoMapView 콜백을 rememberUpdatedState로 최신 참조 보장
edv-Shin Jul 7, 2026
48e6775
refactor: moveToCurrentLocation에 getLastLocation Failure 경로 처리 추가
edv-Shin Jul 7, 2026
d83b8ed
refactor: 필터 칩(ClearChip/FilterChip)에 modifier 파라미터 추가 (#550)
edv-Shin Jul 13, 2026
aa70cb4
refactor: 현재위치 버튼 배경색을 theme ColorWhite300으로 통일 (#550)
edv-Shin Jul 13, 2026
bb64dce
refactor: MatchingMapScreen을 stateless MatchingMapContent로 분리 + Previ…
edv-Shin Aug 9, 2026
2f66871
[Refactor] KakaoMap 매칭 지도 화면 Compose 전환 #546
edv-Shin Aug 9, 2026
736d11f
[Refactor] 매칭 지도 오버레이 Compose 전환, 단일 ComposeView 전환 #550
edv-Shin Aug 9, 2026
fc1432a
refactor: 공통 KakaoMapView 컴포저블을 presentation으로 이동 (#551)
edv-Shin Jul 2, 2026
5deb84b
refactor: ExercisePlaceSearchFragment 지도 본체 ComposeView 전환 (#551)
edv-Shin Jul 2, 2026
cc1b100
refactor: PlaceSearchFragment 지도 본체 ComposeView 전환 (#551)
edv-Shin Jul 2, 2026
8b9cfa9
[Refactor] 공통 KakaoMapView 추출 + 장소 검색 Compose 전환 #551
edv-Shin Aug 9, 2026
a22d735
fix: 오프라인 실행 시 업데이트 확인 실패로 스플래시에서 멈추는 버그 수정 #555
edv-Shin Aug 9, 2026
7b718ed
fix: 온라인 전환 시 업데이트 재확인 및 강제 업데이트 처리 #559
edv-Shin Aug 10, 2026
1ade2f6
fix: ktlint 문법 수정 #559
edv-Shin Aug 10, 2026
da90c08
refactor: checkIsRegistered 반환을 Registered/Unregistered/Indeterminate…
edv-Shin Aug 11, 2026
871e79a
refactor: MainViewModel 진입 게이트를 로컬 토큰 기반으로 분리, loginInBackground 도입 #556
edv-Shin Aug 11, 2026
535bb7e
refactor: 오프라인 진입 허용 및 LoginActivity 네트워크 에러 라우팅 수정 #556
edv-Shin Aug 11, 2026
17dc696
fix: wasOffline 초기화 수정 #559
edv-Shin Aug 12, 2026
bae0c25
Merge branch 'fix/online-reconnect-update-check-559' into refactor/of…
edv-Shin Aug 12, 2026
011552f
fix: 테스트코드 수정 #556
edv-Shin Aug 12, 2026
ca2dc75
Merge pull request #558 from projects200/fix/offline-splash-freeze-555
edv-Shin Aug 12, 2026
8a4ecda
Merge pull request #560 from projects200/fix/online-reconnect-update-…
edv-Shin Aug 12, 2026
45106ba
Merge pull request #561 from projects200/refactor/offline-entry-sessi…
edv-Shin Aug 12, 2026
09f1527
refactor: EntryState로 라우팅 상태 관리하도록 수정 #562
edv-Shin Aug 12, 2026
156f479
refactor: 재연결 로직 수정 #562
edv-Shin Aug 12, 2026
7dee4be
test: 테스트코드 수정 #562
edv-Shin Aug 12, 2026
0269962
feat: 세션 정리 기반 #562
edv-Shin Aug 13, 2026
38c8a03
fix: 회원 여부 확인 수정 #562
edv-Shin Aug 13, 2026
93e792c
fix: 회원 여부 확인 수정 #562
edv-Shin Aug 13, 2026
c34ca27
fix: 로그인 화면으로 이동시 토큰, id 삭제 #562
edv-Shin Aug 13, 2026
64be10d
fix: ktlint 문법 수정 #562
edv-Shin Aug 13, 2026
566aaf9
fix: 불필요한 호출 제거 #562
edv-Shin Aug 13, 2026
d0bce7f
fix: 로그아웃 후 화면이동 오류 수정 #562
edv-Shin Aug 13, 2026
3ad1e5e
test: entry-state 리팩터에 맞춰 유닛 테스트 정합화
edv-Shin Aug 13, 2026
e6eaee5
fix: ktlint 문법 수정 #562
edv-Shin Aug 13, 2026
b80f036
fix: ktlint 문법 수정 #562
edv-Shin Aug 13, 2026
ce23a5b
fix: 로그아웃 에러 콜백의 중복 로그인 화면 이동 제거
edv-Shin Aug 13, 2026
681f7de
Merge pull request #563 from projects200/refactor/entry-state-562
edv-Shin Aug 14, 2026
3883633
refactor: 오프라인 진입 시 시작 화면을 운동 기록 탭으로 변경
edv-Shin Aug 14, 2026
ac768c0
Merge pull request #564 from projects200/fix/issue-557
edv-Shin Aug 14, 2026
b2d69a1
chore: WorkManager 의존성과 Hilt Worker 초기화 추가 #565
edv-Shin Aug 15, 2026
3afe776
fix: FCM 토큰을 매번 저장소에서 읽도록 메모리 캐시 제거 #565
edv-Shin Aug 15, 2026
272f0ec
feat: FCM 토큰 갱신 시 WorkManager로 서버 반영 #565
edv-Shin Aug 15, 2026
95ef800
refactor: POST login 호출을 FcmRepository.registerToken으로 이동 #566
edv-Shin Aug 15, 2026
30a7f98
fix: FCM 토큰 동기화 워커에 만료 토큰 선갱신 추가 #565
edv-Shin Aug 16, 2026
99f1e98
Merge pull request #567 from projects200/fix/fcm-token-sync-565
edv-Shin Aug 16, 2026
296a7f3
Merge pull request #568 from projects200/refactor/fcm-repository-nami…
edv-Shin Aug 16, 2026
8dd9ee5
refactor: FCM 토큰 등록 경로를 워커로 통합 #569
edv-Shin Aug 16, 2026
39f34d4
Merge pull request #575 from projects200/refactor/fcm-registration-wo…
edv-Shin Aug 16, 2026
e93a557
chore: 버전 증가 (versionCode 30 → 31, versionName 0.8.3 → 0.8.4)
edv-Shin Aug 16, 2026
56d890e
Merge pull request #576 from projects200/chore/version-0.8.4
edv-Shin Aug 16, 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
1 change: 1 addition & 0 deletions .editorconfig
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ root = true
[*.{kt,kts}]
ktlint_standard_filename = enabled
ktlint_standard_no-consecutive-comments = disabled
ktlint_function_naming_ignore_when_annotated_with = Composable



Expand Down
5 changes: 5 additions & 0 deletions app/build.gradle.kts
Original file line numberDiff line numberDiff line change
Expand Up@@ -109,6 +109,11 @@ dependencies {
implementation(libs.androidx.navigation.fragment.ktx)
implementation(libs.androidx.navigation.ui.ktx)

// WorkManager (FCM 토큰 전송 예약)
implementation(libs.androidx.work.runtime.ktx)
implementation(libs.androidx.hilt.work)
"ksp"(libs.androidx.hilt.compiler)

implementation(libs.appauth)

// Kakao Map
Expand Down
15 changes: 14 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<!-- 인터넷 권한 -->
<uses-permission android:name="android.permission.INTERNET" />
Expand DownExpand Up@@ -44,6 +45,18 @@
android:name="com.kakao.sdk.AppKey"
android:value="${KAKAO_NATIVE_APP_KEY}" />

<!-- WorkManager 기본 초기화를 끄고 Configuration.Provider로 직접 초기화합니다 -->
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="androidx.work.WorkManagerInitializer"
android:value="androidx.startup"
tools:node="remove" />
</provider>

<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
Expand Down
15 changes: 14 additions & 1 deletion app/src/main/java/com/project200/undabang/ApplicationClass.kt
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,13 +5,26 @@ import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import androidx.appcompat.app.AppCompatDelegate
import androidx.hilt.work.HiltWorkerFactory
import androidx.work.Configuration
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.kakao.vectormap.KakaoMapSdk
import dagger.hilt.android.HiltAndroidApp
import timber.log.Timber
import javax.inject.Inject

@HiltAndroidApp
class ApplicationClass : Application() {
class ApplicationClass : Application(), Configuration.Provider {
@Inject
lateinit var workerFactory: HiltWorkerFactory

/** WorkManager가 Hilt로 Worker를 생성하도록 팩토리를 넘깁니다 */
override val workManagerConfiguration: Configuration
get() =
Configuration.Builder()
.setWorkerFactory(workerFactory)
.build()

override fun onCreate() {
super.onCreate()

Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
package com.project200.undabang.di

import com.project200.common.utils.ChatRoomStateRepository
import com.project200.domain.manager.FcmTokenSyncScheduler
import com.project200.undabang.fcm.ChatRoomStateRepositoryImpl
import com.project200.undabang.fcm.FcmTokenSyncSchedulerImpl
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
Expand All@@ -14,4 +16,8 @@ abstract class RepositoryModule {
@Binds
@Singleton
abstract fun bindChatRoomStateRepository(impl: ChatRoomStateRepositoryImpl): ChatRoomStateRepository

@Binds
@Singleton
abstract fun bindFcmTokenSyncScheduler(impl: FcmTokenSyncSchedulerImpl): FcmTokenSyncScheduler
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,13 +30,15 @@ class FcmService : FirebaseMessagingService() {

/**
* 새로운 FCM 토큰이 발급되거나 갱신될 때 호출됩니다.
* 이 토큰은 백엔드 서버로 전송되어 특정 기기에 알림을 보내는 데 사용됩니다.
* 기기에 저장한 뒤 서버 반영은 워커에 맡깁니다.
* 이 콜백은 백그라운드에서도 불려 네트워크와 로그인 상태를 확신할 수 없습니다.
*/
override fun onNewToken(token: String) {
super.onNewToken(token)
Timber.tag(TAG).d("Refreshed FCM token: $token")

saveFcmToken(token)
FcmTokenSyncWorker.enqueue(applicationContext)
}

/**
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
package com.project200.undabang.fcm

/** FcmTokenSyncWorker의 재시도 판단. 안드로이드 의존성이 없어 단위 테스트로 검증합니다 */
object FcmTokenSyncPolicy {
/** 전송 실패 시 총 시도 횟수 상한 */
const val MAX_ATTEMPT_COUNT = 10

/** runAttemptCount는 WorkManager가 주는 값으로 첫 실행이 0입니다 */
fun shouldRetry(runAttemptCount: Int): Boolean = runAttemptCount + 1 < MAX_ATTEMPT_COUNT

/**
* 토큰 갱신 실패 시 재시도 여부입니다.
* invalid_grant는 리프레시 토큰이 무효한 상태라 재시도해도 결과가 같습니다
*/
fun shouldRetryAfterRefreshFailure(
invalidGrant: Boolean,
runAttemptCount: Int,
): Boolean = !invalidGrant && shouldRetry(runAttemptCount)
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
package com.project200.undabang.fcm

import android.content.Context
import com.project200.domain.manager.FcmTokenSyncScheduler
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class FcmTokenSyncSchedulerImpl
@Inject
constructor(
@ApplicationContext private val context: Context,
) : FcmTokenSyncScheduler {
override fun schedule() = FcmTokenSyncWorker.enqueue(context)

override fun cancel() = FcmTokenSyncWorker.cancel(context)
}
109 changes: 109 additions & 0 deletions app/src/main/java/com/project200/undabang/fcm/FcmTokenSyncWorker.kt
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
package com.project200.undabang.fcm

import android.content.Context
import androidx.hilt.work.HiltWorker
import androidx.work.BackoffPolicy
import androidx.work.Constraints
import androidx.work.CoroutineWorker
import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import com.project200.domain.model.FcmTokenSyncResult
import com.project200.domain.usecase.SyncFcmTokenUseCase
import com.project200.undabang.oauth.AuthManager
import com.project200.undabang.oauth.AuthStateManager
import com.project200.undabang.oauth.TokenRefreshResult
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import net.openid.appauth.AuthorizationException
import timber.log.Timber
import java.util.concurrent.TimeUnit

/**
* 갱신된 FCM 토큰을 서버에 반영하는 워커입니다
* onNewToken()은 백그라운드나 프로세스 기동 직후에도 불려 네트워크와 로그인 상태를 확신할 수 없습니다
* 전송을 워커로 미뤄 연결이 잡힌 뒤에 보내고 실패하면 다시 시도합니다
*/
@HiltWorker
class FcmTokenSyncWorker
@AssistedInject
constructor(
@Assisted appContext: Context,
@Assisted params: WorkerParameters,
private val syncFcmTokenUseCase: SyncFcmTokenUseCase,
private val authStateManager: AuthStateManager,
private val authManager: AuthManager,
) : CoroutineWorker(appContext, params) {
override suspend fun doWork(): Result {
// POST /login은 @AccessTokenWithFcmApi라 401 자동 갱신이 없음 - 만료면 먼저 갱신
if (authStateManager.getCurrent().needsTokenRefresh) {
when (val refresh = authManager.refreshAccessToken()) {
is TokenRefreshResult.Success -> Unit
is TokenRefreshResult.Error -> {
val ex = refresh.exception
val invalidGrant =
ex?.type == AuthorizationException.TYPE_OAUTH_TOKEN_ERROR &&
ex.error == "invalid_grant"
// 세션 정리는 refreshAccessToken() 내부가 함
return if (FcmTokenSyncPolicy.shouldRetryAfterRefreshFailure(invalidGrant, runAttemptCount)) {
Result.retry()
} else {
Result.failure()
}
}
is TokenRefreshResult.NoRefreshToken,
is TokenRefreshResult.ConfigError,
-> return Result.failure()
}
}

val syncResult = syncFcmTokenUseCase()
Timber.tag(TAG).d("FCM 토큰 전송 결과: $syncResult (시도 ${runAttemptCount + 1}회)")

return when (syncResult) {
FcmTokenSyncResult.SUCCESS -> Result.success()
// 로그인 전이거나 보낼 토큰이 없는 상태 - 재시도해도 같으므로 성공 처리
FcmTokenSyncResult.SKIPPED -> Result.success()
FcmTokenSyncResult.FAILURE ->
if (FcmTokenSyncPolicy.shouldRetry(runAttemptCount)) Result.retry() else Result.failure()
}
}

companion object {
private const val TAG = "FcmTokenSyncWorker"
const val WORK_NAME = "fcm_token_sync"
private const val BACKOFF_DELAY_SECONDS = 30L

/**
* 토큰 등록을 예약합니다
* 대기 중에 토큰이 다시 갱신되면 새 요청으로 바꿔 백오프를 처음부터 다시 셉니다
* 워커는 실행 시점에 저장소에서 토큰을 읽으므로 마지막 값이 올라갑니다
*/
fun enqueue(context: Context) {
val constraints =
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()

val request =
OneTimeWorkRequestBuilder<FcmTokenSyncWorker>()
.setConstraints(constraints)
.setBackoffCriteria(
BackoffPolicy.EXPONENTIAL,
BACKOFF_DELAY_SECONDS,
TimeUnit.SECONDS,
)
.build()

WorkManager.getInstance(context)
.enqueueUniqueWork(WORK_NAME, ExistingWorkPolicy.REPLACE, request)
}

/** 예약된 등록을 취소합니다. 세션이 정리되면 보낼 회원ID가 없어 실행할 이유가 없습니다 */
fun cancel(context: Context) {
WorkManager.getInstance(context).cancelUniqueWork(WORK_NAME)
}
}
}
11 changes: 11 additions & 0 deletions app/src/main/java/com/project200/undabang/main/EntryState.kt
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
package com.project200.undabang.main

sealed class EntryState {
object Loading : EntryState()

object Content : EntryState()

object Login : EntryState()

data class ForceUpdate(val fromReconnect: Boolean) : EntryState()
}
Loading
Loading