Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
7a352a3
chore: 버전네임 0.0.1
DongChyeon Feb 23, 2026
5374b72
feat: 본인 글 투표 방지 기능 추가
DongChyeon Feb 23, 2026
2302443
feat: 업로드 화면에서 뒤로가기 시 입력 내용이 없으면 바로 종료되도록 수정
DongChyeon Feb 23, 2026
0e1ab0e
feat: Proguard 설정 및 릴리스 빌드 최적화
DongChyeon Feb 23, 2026
0b480b4
feat: Firebase Crashlytics 의존성 및 관련 설정 추가
DongChyeon Feb 23, 2026
bc0e855
chore: 버전 코드 1 -> 2
DongChyeon Feb 23, 2026
6995ad7
feat/#60: 피드 천 단위 콤마 포맷팅
DongChyeon Feb 24, 2026
48f0e97
feat/#60: 피드 상세 화면 신고/삭제 기능 구현
DongChyeon Feb 24, 2026
b5ac89e
refactor/#60: 피드 가격 천 단위 콤마 포맷팅을 도메인 변환 과정에서 수행
DongChyeon Feb 24, 2026
2307e88
feat/#60: 로그인 시 사용자 프로필 정보 로컬 저장 및 투표자 프로필 이미지 표시
DongChyeon Feb 24, 2026
f902913
fix/#60: 앱 최초 실행 시에만 알림 권한을 요청
DongChyeon Feb 24, 2026
15018c2
fix/#60: 유저 정보 저장 suspend 함수로 수정
DongChyeon Feb 25, 2026
89a9f6b
fix/#60: 마이페이지 프로필 로드 실패 시 예외 로깅
DongChyeon Feb 25, 2026
29d3b29
fix/#60: 프로필 조회 실패 시 피드 상세 조회로 에러 전파 방지
DongChyeon Feb 25, 2026
4e7cab9
refactor/#60: NotificationDetailViewModel의 로그 태그를 상수로 변경
DongChyeon Feb 25, 2026
2ac8fb4
chore/#60: Proguard 규칙 수정
DongChyeon Feb 25, 2026
dc2ecde
refactor/#60: updateFcmToken를 정지 함수로 만들어 실행 보장
DongChyeon Feb 26, 2026
5ff9472
refactor/#60: 첫 실행 여부를 AppPreferences에서 관리
DongChyeon Feb 26, 2026
c229473
fix/#60: isFirstRun 기본값 false로 변경
DongChyeon Feb 26, 2026
ed85932
fix/#60: 로그아웃, 회원탈퇴 시 프로필 이미지 정보 삭제
DongChyeon Feb 26, 2026
9820d39
fix/#60: 로그아웃, 회원탈퇴 시 프로필 이름 삭제
DongChyeon Feb 26, 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
9 changes: 6 additions & 3 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ plugins {
alias(libs.plugins.hilt)
alias(libs.plugins.ksp)
alias(libs.plugins.google.services)
alias(libs.plugins.firebase.crashlytics)
}

val localProperties =
Expand All @@ -21,8 +22,8 @@ android {

defaultConfig {
applicationId = "com.sseotdabwa.buyornot"
versionCode = 1
versionName = "1.0.0"
versionCode = 2
versionName = "0.0.1"

buildConfigField("String", "KAKAO_NATIVE_APP_KEY", "\"${localProperties.getProperty("kakao.nativeAppKey", "")}\"")
manifestPlaceholders["NATIVE_APP_KEY"] = localProperties.getProperty("kakao.nativeAppKey", "")
Expand Down Expand Up @@ -59,7 +60,8 @@ android {
signingConfig = signingConfigs.getByName("release")
}
release {
isMinifyEnabled = false
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
Expand Down Expand Up @@ -104,6 +106,7 @@ dependencies {

implementation(platform(libs.firebase.bom))
implementation(libs.firebase.messaging)
implementation(libs.firebase.crashlytics)

ksp(libs.hilt.compiler)

Expand Down
192 changes: 171 additions & 21 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -1,21 +1,171 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
# ============================================================
# [Common] Android
# ============================================================
-keepattributes SourceFile,LineNumberTable
-keepattributes *Annotation*
-keepattributes Signature
-keepattributes InnerClasses
-keepattributes Exceptions
-dontwarn sun.misc.**
-dontwarn javax.annotation.**

# Enum 보존
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}

# Parcelable 보존
-keep class * implements android.os.Parcelable {
public static final ** CREATOR;
}

# Serializable 보존
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
}

# ============================================================
# [Kotlin]
# ============================================================
-keep class kotlin.Metadata { *; }
-dontwarn kotlin.**
-keepclassmembers class **$WhenMappings {
<fields>;
}

# ============================================================
# [Kotlinx Coroutines]
# ============================================================
-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
-keepclassmembernames class kotlinx.** {
volatile <fields>;
}
-dontwarn kotlinx.coroutines.**

# ============================================================
# [Kotlinx Serialization]
# ============================================================
-keep class kotlinx.serialization.** { *; }
-keepclassmembers @kotlinx.serialization.Serializable class * {
*** Companion;
*** INSTANCE;
kotlinx.serialization.KSerializer serializer(...);
}
-keepclassmembers class * {
@kotlinx.serialization.Serializable *;
}
-keepclassmembers class * {
kotlinx.serialization.KSerializer serializer(...);
}
-keepclassmembers class kotlinx.serialization.json.** {
*** Companion;
}
-dontwarn kotlinx.serialization.**

# ============================================================
# [Hilt / Dagger]
# ============================================================
-keep class dagger.hilt.** { *; }
-keep class com.google.dagger.** { *; }
-keep @dagger.hilt.android.lifecycle.HiltViewModel class * { *; }
-keep @dagger.hilt.InstallIn class * { *; }
-keep interface * extends javax.inject.Provider
-dontwarn dagger.hilt.**

# ============================================================
# [Retrofit / OkHttp]
# ============================================================
-keep class retrofit2.** { *; }
-dontwarn retrofit2.**
-keepclassmembers class * {
@retrofit2.http.* <methods>;
}
-keepclassmembernames interface * {
@retrofit2.http.* <methods>;
}
-keep class okhttp3.** { *; }
-dontwarn okhttp3.**
-dontwarn okio.**

# ============================================================
# [Jetpack Compose]
# ============================================================
-keep class androidx.compose.runtime.RecomposeScopeImpl { *; }
-keep class * implements androidx.compose.runtime.Stable { *; }
-keepclassmembers class * {
@androidx.compose.runtime.Composable <methods>;
}

# ============================================================
# [AndroidX Lifecycle / ViewModel]
# ============================================================
-keep class * extends androidx.lifecycle.ViewModel { *; }
-keep class * extends androidx.lifecycle.AndroidViewModel { *; }
-keepclassmembers class * extends androidx.lifecycle.ViewModel {
<init>(...);
}

# ============================================================
# [Navigation Compose]
# ============================================================
-keep class androidx.navigation.** { *; }

# ============================================================
# [AndroidX DataStore]
# ============================================================
-keep class androidx.datastore.** { *; }

# ============================================================
# [AndroidX Credentials / Google Sign-In]
# ============================================================
-keep class androidx.credentials.** { *; }
-keep class com.google.android.libraries.identity.googleid.** { *; }
-dontwarn com.google.android.libraries.identity.googleid.**

# ============================================================
# [Coil]
# ============================================================
-keep class coil.** { *; }
-dontwarn coil.**

# ============================================================
# [Firebase]
# ============================================================
-keep class com.google.firebase.** { *; }
-dontwarn com.google.firebase.**

# Crashlytics: 스택 트레이스 보존을 위해 Exception 하위 클래스 유지
-keep public class * extends java.lang.Exception

# ============================================================
# [Lottie]
# ============================================================
-keep class com.airbnb.lottie.** { *; }
-dontwarn com.airbnb.lottie.**

# ============================================================
# [Kakao SDK]
# ============================================================
-keep class com.kakao.sdk.** { *; }
-dontwarn com.kakao.sdk.**

# ============================================================
# [Domain / Data Models] - API 통신 데이터 클래스 보존
# ============================================================
# Domain models
-keep class com.sseotdabwa.buyornot.domain.model.** { *; }

# Network DTOs (request / response)
-keep class com.sseotdabwa.buyornot.core.network.dto.** { *; }

# DataStore preferences holder
-keep class com.sseotdabwa.buyornot.core.datastore.** { *; }

# ============================================================
# [App] Firebase Cloud Messaging Service
# ============================================================
-keep class com.sseotdabwa.buyornot.notification.** { *; }
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="com.google.android.gms.permission.AD_ID" />

<application
android:name=".BuyOrNotApplication"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import com.sseotdabwa.buyornot.feature.auth.navigation.splashScreen
import com.sseotdabwa.buyornot.feature.home.navigation.homeScreen
import com.sseotdabwa.buyornot.feature.home.navigation.navigateToHome
import com.sseotdabwa.buyornot.feature.home.navigation.navigateToHomeWithTab
import com.sseotdabwa.buyornot.feature.home.viewmodel.HomeTab
import com.sseotdabwa.buyornot.feature.home.ui.HomeTab
import com.sseotdabwa.buyornot.feature.mypage.navigation.myPageGraph
import com.sseotdabwa.buyornot.feature.mypage.navigation.navigateToMyPage
import com.sseotdabwa.buyornot.feature.notification.navigation.navigateToNotification
Expand Down
17 changes: 13 additions & 4 deletions app/src/main/java/com/sseotdabwa/buyornot/ui/BuyOrNotApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavDestination
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
Expand Down Expand Up @@ -37,28 +39,35 @@ import com.sseotdabwa.buyornot.navigation.BuyOrNotNavHost
*
* @param authEventBus 인증 관련 이벤트 버스
* @param onBackPressed 홈 화면에서 뒤로가기 시 앱 종료를 위한 콜백
* @param viewModel 앱 공통 ViewModel
*/
@Composable
fun BuyOrNotApp(
authEventBus: AuthEventBus,
onBackPressed: () -> Unit = {},
viewModel: BuyOrNotViewModel = hiltViewModel(),
) {
val navController = rememberNavController()
val snackbarState = rememberBuyOrNotSnackbarState()
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination

val isFirstRun by viewModel.isFirstRun.collectAsStateWithLifecycle()

// 홈 화면에서 뒤로가기 시 앱 종료
BackHandler(enabled = currentDestination?.route == HOME_ROUTE) {
onBackPressed()
}

// 앱 진입 시 알림 권한 자동 요청
// 앱 진입 시 최초 1회만 알림 권한 자동 요청
val (hasNotificationPermission, requestNotificationPermission) = rememberNotificationPermission()

LaunchedEffect(Unit) {
if (!hasNotificationPermission) {
requestNotificationPermission()
LaunchedEffect(isFirstRun) {
if (isFirstRun) {
if (!hasNotificationPermission) {
requestNotificationPermission()
}
viewModel.updateIsFirstRun(false)
}
}

Expand Down
29 changes: 29 additions & 0 deletions app/src/main/java/com/sseotdabwa/buyornot/ui/BuyOrNotViewModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.sseotdabwa.buyornot.ui

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.sseotdabwa.buyornot.domain.repository.AppPreferencesRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class BuyOrNotViewModel @Inject constructor(
private val appPreferencesRepository: AppPreferencesRepository,
) : ViewModel() {
val isFirstRun =
appPreferencesRepository.isFirstRun
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = false,
)

fun updateIsFirstRun(isFirstRun: Boolean) {
viewModelScope.launch {
appPreferencesRepository.updateIsFirstRun(isFirstRun)
}
}
}
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ plugins {
alias(libs.plugins.ksp) apply false
alias(libs.plugins.ktlint) apply false
alias(libs.plugins.google.services) apply false
alias(libs.plugins.firebase.crashlytics) apply false
}

apply(from = "gradle/dependencyGraph.gradle")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,14 @@ class AppPreferencesRepositoryImpl @Inject constructor(
override val hasRequestedNotificationPermission: Flow<Boolean> =
appPreferencesDataSource.hasRequestedNotificationPermission

override val isFirstRun: Flow<Boolean> =
appPreferencesDataSource.isFirstRun

override suspend fun updateNotificationPermissionRequested(requested: Boolean) {
appPreferencesDataSource.updateNotificationPermissionRequested(requested)
}

override suspend fun updateIsFirstRun(isFirstRun: Boolean) {
appPreferencesDataSource.updateIsFirstRun(isFirstRun)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class AuthRepositoryImpl @Inject constructor(
authApiService.logout(RefreshRequest(refreshToken)).getOrThrow()
}

override suspend fun clearTokens() {
userPreferencesDataSource.clearTokens()
override suspend fun clearUserInfo() {
userPreferencesDataSource.clearUserInfo()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ private fun FeedItemDto.toDomain(): Feed =
Feed(
feedId = feedId,
content = content,
price = price,
price = String.format(java.util.Locale.KOREA, "%,d", price),
category = category.toFeedCategory(),
yesCount = yesCount,
noCount = noCount,
Expand Down
Loading
Loading