Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
3f19286
Merge pull request #62 from Nexters/release/0.0.1
DongChyeon Feb 25, 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
c8db3da
Merge pull request #61 from Nexters/feat#60-qa-before-demo
DongChyeon Feb 26, 2026
ca97bff
chore: 0.0.1 (2) -> 0.0.2 (3)
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
4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ android {

defaultConfig {
applicationId = "com.sseotdabwa.buyornot"
versionCode = 2
versionName = "0.0.1"
versionCode = 3
versionName = "0.0.2"

buildConfigField("String", "KAKAO_NATIVE_APP_KEY", "\"${localProperties.getProperty("kakao.nativeAppKey", "")}\"")
manifestPlaceholders["NATIVE_APP_KEY"] = localProperties.getProperty("kakao.nativeAppKey", "")
Expand Down
139 changes: 125 additions & 14 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -1,60 +1,171 @@
# ============================================================
# [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.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.**
-keepattributes *Annotation*
-keepclassmembers class * {
@retrofit2.http.* <methods>;
}
-keepclassmembernames interface * {
@retrofit2.http.* <methods>;
}
-keep class okhttp3.** { *; }
-dontwarn okhttp3.**
-dontwarn okio.**

# [Kotlinx Serialization]
-keepattributes *Annotation*, InnerClasses
# ============================================================
# [Jetpack Compose]
# ============================================================
-keep class androidx.compose.runtime.RecomposeScopeImpl { *; }
-keep class * implements androidx.compose.runtime.Stable { *; }
-keepclassmembers class * {
@kotlinx.serialization.Serializable *;
@androidx.compose.runtime.Composable <methods>;
}
-keepclassmembers class * {
kotlinx.serialization.KSerializer serializer(...);

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

# [Domain/Data Models] - API 톡신 데이터 클래슀 보쑴
# λͺ¨λ“  λͺ¨λ“ˆμ˜ domain.model, network.model ν•˜μœ„μ˜ ν΄λž˜μŠ€λ“€μ„ λ³΄μ‘΄ν•©λ‹ˆλ‹€.
-keep class com.sseotdabwa.buyornot.domain.model.** { *; }
-keep class com.sseotdabwa.buyornot.core.network.model.** { *; }
# ============================================================
# [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.**

# [Jetpack Compose]
-keep class androidx.compose.runtime.RecomposeScopeImpl { *; }
-keep class * implements androidx.compose.runtime.Parcelable { *; }
# ============================================================
# [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.** { *; }
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)
}
}
}
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
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package com.sseotdabwa.buyornot.core.data.repository

import com.sseotdabwa.buyornot.core.datastore.UserPreferencesDataSource
import com.sseotdabwa.buyornot.domain.model.UserPreferences
import com.sseotdabwa.buyornot.domain.model.UserToken
import com.sseotdabwa.buyornot.domain.model.UserType
import com.sseotdabwa.buyornot.domain.repository.UserPreferencesRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject
import com.sseotdabwa.buyornot.core.datastore.UserPreferences as DatastoreUserPreferences
import com.sseotdabwa.buyornot.core.datastore.UserType as DatastoreUserType

/**
Expand All @@ -15,14 +18,47 @@ import com.sseotdabwa.buyornot.core.datastore.UserType as DatastoreUserType
class UserPreferencesRepositoryImpl @Inject constructor(
private val userPreferencesDataSource: UserPreferencesDataSource,
) : UserPreferencesRepository {
override val userPreferences: Flow<UserPreferences> =
userPreferencesDataSource.preferences.map { it.toDomain() }

override val userToken: Flow<UserToken> =
userPreferencesDataSource.preferences.map { it.toTokenDomain() }

override val userType: Flow<UserType> =
userPreferencesDataSource.userType.map { it.toDomain() }

override suspend fun updateUserType(userType: UserType) {
userPreferencesDataSource.updateUserType(userType.toDatastore())
}

override suspend fun updateDisplayName(newName: String) {
userPreferencesDataSource.updateDisplayName(newName)
}

override suspend fun updateProfileImageUrl(newUrl: String) {
userPreferencesDataSource.updateProfileImageUrl(newUrl)
}
}

/**
* DataStore UserPreferences β†’ Domain UserPreferences
*/
private fun DatastoreUserPreferences.toDomain(): UserPreferences =
UserPreferences(
displayName = displayName,
profileImageUrl = profileImageUrl,
userType = userType.toDomain(),
)

/**
* DataStore UserPreferences β†’ Domain UserToken
*/
private fun DatastoreUserPreferences.toTokenDomain(): UserToken =
UserToken(
accessToken = accessToken,
refreshToken = refreshToken,
)

/**
* DataStore UserType β†’ Domain UserType
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,7 @@ data class AppPreferences(
*/
val hasRequestedNotificationPermission: Boolean = false,
/**
* ν–₯ν›„ 좔가될 μ•± μ„€μ •λ“€
* 예: 닀크λͺ¨λ“œ, μ–Έμ–΄ μ„€μ •, 첫 μ‹€ν–‰ μ—¬λΆ€ λ“±
*
* val isDarkModeEnabled: Boolean = false,
* val preferredLanguage: String = "ko",
* val isFirstLaunch: Boolean = true,
* 졜초 μ‹€ν–‰ μ—¬λΆ€
*/
val isFirstRun: Boolean = true,
)
Loading
Loading