Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
39f8a13
[chore] #51 드래그 관련 상수 네이밍 변경
Ojongseok Jan 24, 2026
99149f5
[design] #51 BottomNavigationBar 디자인 변경사항 반영
Ojongseok Jan 24, 2026
0c2d70e
[refactor] #51 AnchoredDraggablePanel 드래그 레벨별 높이 조절 방식 로직 변경
Ojongseok Jan 24, 2026
a1b3208
[feat] #51 '현 위치에서 탐색' 컴포넌트 정의
Ojongseok Jan 24, 2026
60e8997
[feat] #51 지도 버튼 컴포넌트 .noRippleClickableSingle로 변경
Ojongseok Jan 24, 2026
bf53d05
[feat] #51 하단 패널 DragLevel.THIRD 상태 높이 화면 90%로 변경
Ojongseok Jan 24, 2026
d1240e8
[chore] #51 불필요한 패널 높이 변수 제거
Ojongseok Jan 24, 2026
d2fd2c9
[feat] #51 DragLevel First, Second 상태의 '현 위치에서 탐색' 버튼 노출
Ojongseok Jan 24, 2026
01c3df2
[feat] #51 NaverMap isZoomControlEnabled 속성 false로 설정
Ojongseok Jan 24, 2026
3ba09fa
[feat] #51 '현 위치에서 탐색' 버튼 UI 로직 구성
Ojongseok Jan 24, 2026
8c906cc
[feat] #51 현위치에 따른 길찾기 앱 이동 로직 수정
Ojongseok Jan 24, 2026
8c5f0d5
[feat] #51 core:data-api dataStore 의존성 추가
Ojongseok Jan 24, 2026
38a0713
[feat] #51 feat:map:impl activity-compose 의존성 추가
Ojongseok Jan 24, 2026
3cbe6c9
[feat] #51 Boolean 타입을 저장할 DataStoreRepository 인터페이스 작성
Ojongseok Jan 24, 2026
6f5c983
[feat] #51 DataStore Key 정의 경로 변경
Ojongseok Jan 24, 2026
39d7e54
[feat] #51 accessToken, refreshToken 조회 로직 nonNull 타입으로 변경
Ojongseok Jan 24, 2026
c601a61
[feat] #51 네컷지도 위치권한 확인 로직 추가
Ojongseok Jan 24, 2026
c815bc7
[feat] #51 권한 관련 공통 로직을 작성하는 PermissionManager.kt 정의
Ojongseok Jan 24, 2026
329983b
[feat] #51 위치 권한 확인 시점 추가(네컷지도 화면 진입 시, 길찾기 버튼 선택 시)
Ojongseok Jan 24, 2026
00b046a
[build] #51 detekt 룰 적용
Ojongseok Jan 24, 2026
7baf582
[fix] #51 clearTokens()가 accessToken과 refreshToken만 지우도록 수정
Ojongseok Jan 24, 2026
33e368f
[fix] #51 MapContract 내 현위치 nullable하도록 수정
Ojongseok Jan 24, 2026
593a1b8
[fix] #51 일부 Brand~ -> PhotoBooth 네이밍 변경 및 CloseButton 분리
Ojongseok Jan 24, 2026
eb2980e
[build] #51 detekt 룰 적용
Ojongseok Jan 24, 2026
9d6cd4b
[fix] #51 권한 관련 매니저 클래스 권한 단위로 분리
Ojongseok Jan 25, 2026
f6ff2f6
[fix] #51 드래그 관련 높이 상수 네이밍 변경
Ojongseok Jan 25, 2026
f51fa03
[fix] #51 MapRefreshChip.kt 수평 패딩 수정
Ojongseok Jan 25, 2026
34a6471
[fix] #51 가까운 네컷 사진 브랜드 더미데이터 조회 로직 수정
Ojongseok Jan 25, 2026
6a1ce94
[fix] #51 @Qualifier 활용해 Token / Auth DataStore 인스턴스 분리
Ojongseok Jan 25, 2026
0739538
[fix] #51 현위치 조회하지 못한 경우 토스트메시지 NekiToast로 변경
Ojongseok Jan 25, 2026
d409dd7
[fix] #51 위치 권한 여부에 따른 Intent, Effect 처리 로직 개선
Ojongseok Jan 25, 2026
dbf64bb
[fix] #51 위치권한 여부 체크 프로세스 변경
Ojongseok Jan 25, 2026
d62e281
[fix] #51 패널 관련 상수 네이밍 변경
Ojongseok Jan 25, 2026
42ea247
[fix] #51 토스트메시지 remember 변수 정의 제거
Ojongseok Jan 25, 2026
7647462
[fix] #51 위치 권한 이전 shouldShowRationale 확인 로직 추가
Ojongseok Jan 25, 2026
68da56c
[build] #51 detekt 룰 적용
Ojongseok Jan 25, 2026
10b6dca
[design] #51 pinShadow() 확장함수 추가 및 PhotoBoothMarker shadow 효과 적용
Ojongseok Jan 25, 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 @@ -86,9 +86,9 @@ fun BottomNavigationBarItem(
color = NekiTheme.colorScheme.white,
) {
Column(
modifier = Modifier.padding(vertical = 8.dp),
modifier = Modifier.padding(vertical = 4.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(1.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
Icon(
modifier = Modifier.size(24.dp),
Expand All @@ -99,7 +99,7 @@ fun BottomNavigationBarItem(
Text(
text = stringResource(tab.iconStringRes),
color = textColor,
style = NekiTheme.typography.caption11SemiBold,
style = NekiTheme.typography.caption12SemiBold,
)
Comment thread
ikseong00 marked this conversation as resolved.
}
}
Expand Down
3 changes: 2 additions & 1 deletion core/common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ android {
dependencies {
api(libs.timber)
implementation(libs.androidx.security.crypto)
implementation(libs.androidx.core.ktx)

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.neki.android.core.common.permission

import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.provider.Settings
import androidx.core.content.ContextCompat
import androidx.core.content.PermissionChecker

object PermissionManager {
Comment thread
Ojongseok marked this conversation as resolved.
Outdated
val LOCATION_PERMISSIONS = arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
)

fun hasLocationPermission(context: Context): Boolean {
return ContextCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION,
) == PermissionChecker.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_COARSE_LOCATION,
) == PermissionChecker.PERMISSION_GRANTED
}

fun shouldShowLocationRationale(activity: Activity): Boolean {
return activity.shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)
}

fun navigateToAppSettings(context: Context) {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.fromParts("package", context.packageName, null)
}
context.startActivity(intent)
Comment thread
Ojongseok marked this conversation as resolved.
Outdated
}
}
2 changes: 1 addition & 1 deletion core/data-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ plugins {
dependencies {
implementation(projects.core.model)
implementation(libs.kotlinx.coroutines.core)

api(libs.androidx.datastore.preferences)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.neki.android.core.dataapi.datastore

import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey

object DataStoreKey {
Comment thread
ikseong00 marked this conversation as resolved.
val ACCESS_TOKEN = stringPreferencesKey("access_token")
val REFRESH_TOKEN = stringPreferencesKey("refresh_token")

val IS_FIRST_LOCATION_PERMISSION = booleanPreferencesKey("is_first_location_permission")
Comment thread
Ojongseok marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package com.neki.android.core.dataapi.repository

import androidx.datastore.preferences.core.Preferences
import kotlinx.coroutines.flow.Flow

interface DataStoreRepository {
suspend fun saveJwtTokens(
accessToken: String,
refreshToken: String,
)

fun isSavedJwtTokens(): Flow<Boolean>
fun getAccessToken(): Flow<String?>
fun getRefreshToken(): Flow<String?>
fun getAccessToken(): Flow<String>
fun getRefreshToken(): Flow<String>
suspend fun clearTokens()

suspend fun setBoolean(key: Preferences.Key<Boolean>, value: Boolean)
fun getBoolean(key: Preferences.Key<Boolean>): Flow<Boolean>
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import io.ktor.http.HttpHeaders
import io.ktor.http.encodedPath
import io.ktor.serialization.kotlinx.json.json
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.serialization.json.Json
import timber.log.Timber
import javax.inject.Singleton
Expand Down Expand Up @@ -82,8 +81,8 @@ internal object NetworkModule {
Timber.d("BearerAuth - loadTokens")
if (dataStoreRepository.isSavedJwtTokens().first()) {
BearerTokens(
accessToken = dataStoreRepository.getAccessToken().firstOrNull() ?: "",
refreshToken = dataStoreRepository.getRefreshToken().firstOrNull() ?: "",
accessToken = dataStoreRepository.getAccessToken().first(),
refreshToken = dataStoreRepository.getRefreshToken().first(),
)
} else null
}
Expand All @@ -95,7 +94,7 @@ internal object NetworkModule {
val response = client.post("/api/auth/refresh") {
setBody(
RefreshTokenRequest(
refreshToken = dataStoreRepository.getRefreshToken().firstOrNull() ?: "",
refreshToken = dataStoreRepository.getRefreshToken().first(),
),
)
}.body<BasicResponse<AuthResponse>>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package com.neki.android.core.data.repository.impl
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import com.neki.android.core.common.crypto.CryptoManager
import com.neki.android.core.dataapi.datastore.DataStoreKey
import com.neki.android.core.dataapi.repository.DataStoreRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
Expand All @@ -13,43 +13,51 @@ import javax.inject.Inject
class DataStoreRepositoryImpl @Inject constructor(
private val dataStore: DataStore<Preferences>,
) : DataStoreRepository {
companion object {
private val ACCESS_TOKEN = stringPreferencesKey("access_token")
private val REFRESH_TOKEN = stringPreferencesKey("refresh_token")
}

override suspend fun saveJwtTokens(
accessToken: String,
refreshToken: String,
) {
dataStore.edit { preferences ->
preferences[ACCESS_TOKEN] = CryptoManager.encrypt(accessToken)
preferences[REFRESH_TOKEN] = CryptoManager.encrypt(refreshToken)
preferences[DataStoreKey.ACCESS_TOKEN] = CryptoManager.encrypt(accessToken)
preferences[DataStoreKey.REFRESH_TOKEN] = CryptoManager.encrypt(refreshToken)
}
}

override fun isSavedJwtTokens(): Flow<Boolean> {
return dataStore.data.map { preferences ->
val accessToken = preferences[ACCESS_TOKEN]?.let { CryptoManager.decrypt(it) }
val refreshToken = preferences[REFRESH_TOKEN]?.let { CryptoManager.decrypt(it) }
val accessToken = preferences[DataStoreKey.ACCESS_TOKEN]?.let { CryptoManager.decrypt(it) }
val refreshToken = preferences[DataStoreKey.REFRESH_TOKEN]?.let { CryptoManager.decrypt(it) }

!accessToken.isNullOrBlank() && !refreshToken.isNullOrBlank()
}
}

override fun getAccessToken(): Flow<String?> {
override fun getAccessToken(): Flow<String> {
return dataStore.data.map { preferences ->
preferences[ACCESS_TOKEN]?.let { CryptoManager.decrypt(it) }
preferences[DataStoreKey.ACCESS_TOKEN]?.let { CryptoManager.decrypt(it) } ?: ""
}
}

override fun getRefreshToken(): Flow<String?> {
override fun getRefreshToken(): Flow<String> {
return dataStore.data.map { preferences ->
preferences[REFRESH_TOKEN]?.let { CryptoManager.decrypt(it) }
preferences[DataStoreKey.REFRESH_TOKEN]?.let { CryptoManager.decrypt(it) } ?: ""
}
}

override suspend fun clearTokens() {
dataStore.edit { it.clear() }
}
Comment thread
Ojongseok marked this conversation as resolved.
Outdated

override suspend fun setBoolean(key: Preferences.Key<Boolean>, value: Boolean) {
dataStore.edit { preferences ->
preferences[key] = value
}
}

override fun getBoolean(key: Preferences.Key<Boolean>): Flow<Boolean> {
return dataStore.data.map { preferences ->
preferences[key] ?: false
}
}
}
20 changes: 20 additions & 0 deletions core/designsystem/src/main/res/drawable/icon_rotation.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M23,4V10H17"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#3C3E48"
android:strokeLineCap="round"/>
<path
android:pathData="M20.49,15C19.84,16.84 18.609,18.419 16.984,19.499C15.359,20.578 13.426,21.101 11.478,20.987C9.53,20.873 7.672,20.129 6.184,18.867C4.695,17.605 3.657,15.893 3.226,13.99C2.795,12.087 2.994,10.095 3.794,8.315C4.593,6.535 5.949,5.063 7.658,4.121C9.367,3.178 11.336,2.817 13.268,3.091C15.2,3.365 16.99,4.26 18.37,5.64L23,10"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#3C3E48"
android:strokeLineCap="round"/>
</vector>
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import com.neki.android.core.ui.MviIntentStore
import com.neki.android.core.ui.mviIntentStore
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
Expand Down Expand Up @@ -47,7 +46,7 @@ class LoginViewModel @Inject constructor(
if (dataStoreRepository.isSavedJwtTokens().first()) {
Timber.d("JWT 토큰 O")
authRepository.updateAccessToken(
refreshToken = dataStoreRepository.getRefreshToken().firstOrNull() ?: "",
refreshToken = dataStoreRepository.getRefreshToken().first(),
).onSuccess {
postSideEffect(LoginSideEffect.NavigateToHome)
}.onFailure {
Expand Down
1 change: 1 addition & 0 deletions feature/map/impl/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ dependencies {

implementation(libs.kotlinx.collections.immutable)
implementation(libs.coil.compose)
implementation(libs.androidx.activity.compose)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import kotlinx.collections.immutable.persistentListOf

data class MapState(
val isLoading: Boolean = false,
val dragState: DragValue = DragValue.Bottom,
val currentLocation: Pair<Double, Double> = Pair(0.0, 0.0),
val dragLevel: DragLevel = DragLevel.FIRST,
val brands: ImmutableList<Brand> = persistentListOf(),
val nearbyBrands: ImmutableList<BrandInfo> = persistentListOf(),
val focusedMarkerPosition: Pair<Double, Double> = Pair(0.0, 0.0),
val selectedBrandInfo: BrandInfo? = null,
val isShowInfoDialog: Boolean = false,
val isShowDirectionBottomSheet: Boolean = false,
val isShowLocationPermissionDialog: Boolean = false,
)

sealed interface MapIntent {
Expand All @@ -32,6 +33,9 @@ sealed interface MapIntent {
val longitude: Double,
) : MapIntent

data object ClickRefresh : MapIntent
data class UpdateCurrentLocation(val latitude: Double, val longitude: Double) : MapIntent

// in 패널
data object ClickCurrentLocation : MapIntent
data object ClickInfoIcon : MapIntent
Expand All @@ -42,10 +46,16 @@ sealed interface MapIntent {
data object ClickCloseBrandCard : MapIntent
data object CloseDirectionBottomSheet : MapIntent
data class ClickDirectionItem(val app: DirectionApp) : MapIntent
data class ChangeDragValue(val dragValue: DragValue) : MapIntent
data class ChangeDragLevel(val dragLevel: DragLevel) : MapIntent

// 위치 권한
data class RequestLocationPermission(val shouldShowRationale: Boolean) : MapIntent
data object DismissLocationPermissionDialog : MapIntent
data object ConfirmLocationPermissionDialog : MapIntent
}

sealed interface MapEffect {
data object RefreshPhotoBooth : MapEffect
data object RefreshCurrentLocation : MapEffect
data class ShowToastMessage(val message: String) : MapEffect
data class MoveCameraToPosition(
Expand All @@ -55,7 +65,14 @@ sealed interface MapEffect {

data class MoveDirectionApp(
val app: DirectionApp,
val startLatitude: Double,
val startLongitude: Double,
val endLatitude: Double,
val endLongitude: Double,
) : MapEffect

data object NavigateToAppSettings : MapEffect
data object RequestLocationPermission : MapEffect
}

enum class DragValue { Bottom, Center, Top, Invisible }
enum class DragLevel { FIRST, SECOND, THIRD, INVISIBLE }
Loading