diff --git a/.github/workflows/android_ci.yml b/.github/workflows/android_ci.yml
index 3a6961504..8d9401e41 100644
--- a/.github/workflows/android_ci.yml
+++ b/.github/workflows/android_ci.yml
@@ -2,7 +2,7 @@ name: NAAGA ANDROID CI
on:
pull_request:
- branches: [ dev ]
+ branches: [ dev_android ]
paths:
- 'android/**'
diff --git a/.github/workflows/backend_dev_merge_workflow.yml b/.github/workflows/backend_dev_merge_workflow.yml
index 11aaf056d..867e515ac 100644
--- a/.github/workflows/backend_dev_merge_workflow.yml
+++ b/.github/workflows/backend_dev_merge_workflow.yml
@@ -2,7 +2,8 @@ name: NAAGA BACKEND MERGE CI
on:
push:
branches:
- - dev
+ - dev_backend
+
jobs:
deploy:
runs-on: naaga
diff --git a/.github/workflows/backend_dev_pr_workflow.yml b/.github/workflows/backend_dev_pr_workflow.yml
index cd38b349c..36ba39d66 100644
--- a/.github/workflows/backend_dev_pr_workflow.yml
+++ b/.github/workflows/backend_dev_pr_workflow.yml
@@ -3,7 +3,7 @@ name: NAAGA BACKEND PULL REQUEST CI
on:
pull_request:
branches:
- - dev
+ - dev_backend
paths:
- backend/**
@@ -52,3 +52,16 @@ jobs:
- name: Test with Gradle
run: ./gradlew test
+
+ - name: 테스트 결과를 PR에 코멘트로 등록합니다
+ uses: EnricoMi/publish-unit-test-result-action@v1
+ if: always()
+ with:
+ files: 'backend/build/test-results/test/TEST-*.xml'
+
+ - name: 테스트 실패 시, 실패한 코드 라인에 Check 코멘트를 등록합니다
+ uses: mikepenz/action-junit-report@v3
+ if: always()
+ with:
+ report_paths: '**/build/test-results/test/TEST-*.xml'
+ token: ${{ github.token }}
diff --git a/.github/workflows/backend_prod_merge_workflow.yml b/.github/workflows/backend_prod_merge_workflow.yml
index 69cb6861a..9615d5c08 100644
--- a/.github/workflows/backend_prod_merge_workflow.yml
+++ b/.github/workflows/backend_prod_merge_workflow.yml
@@ -4,7 +4,9 @@ on:
push:
branches:
- main
-
+ paths:
+ - backend/**
+
jobs:
deploy:
runs-on: naaga
@@ -38,3 +40,22 @@ jobs:
cd /home/ubuntu/prod
chmod +x ./deploy_prod.sh
sudo ./deploy_prod.sh
+
+ sync-dev_backend:
+ needs: deploy
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ with:
+ ref: main
+
+ - name: merge main -> dev_backend
+ uses: devmasx/merge-branch@master
+ with:
+ type: now
+ from_branch: main
+ target_branch: dev_backend
+ github_token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/backend_rel_merge_workflow.yml b/.github/workflows/backend_rel_merge_workflow.yml
new file mode 100644
index 000000000..3ac3a3e3b
--- /dev/null
+++ b/.github/workflows/backend_rel_merge_workflow.yml
@@ -0,0 +1,60 @@
+name: NAAGA BACKEND RELEASE SERVER MERGE CI/CD
+
+on:
+ push:
+ branches:
+ - release/**
+ paths:
+ - backend/**
+
+jobs:
+ deploy:
+ runs-on: naaga
+ steps:
+ - name: change permission
+ run: |
+ sudo chown -R ubuntu:ubuntu /home/ubuntu/actions-runner/naaga/2023-naaga/2023-naaga
+
+ - name: checkout
+ uses: actions/checkout@v3
+ with:
+ submodules: true
+ ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
+
+ - name: project remove
+ run: |
+ sudo rm -rf /home/ubuntu/prod/2023-naaga
+
+ - name: project copy
+ run: |
+ sudo cp -r /home/ubuntu/actions-runner/naaga/2023-naaga/2023-naaga /home/ubuntu/prod
+
+ - name: build
+ run: |
+ cd /home/ubuntu/prod/2023-naaga/backend
+ sudo chmod +x ./gradlew
+ sudo ./gradlew clean bootJar
+
+ - name: transfer & run
+ run: |
+ cd /home/ubuntu/prod
+ chmod +x ./deploy_prod.sh
+ sudo ./deploy_prod.sh
+
+ sync-dev_backend:
+ needs: deploy
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ with:
+ ref: main
+
+ - name: merge release -> dev_backend
+ uses: devmasx/merge-branch@master
+ with:
+ type: now
+ target_branch: dev_backend
+ github_token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..0a5d4cc38
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.idea
\ No newline at end of file
diff --git a/android/app/build.gradle b/android/app/build.gradle
index cd7f67f95..bbe975504 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -5,6 +5,8 @@ plugins {
id 'kotlin-parcelize'
id 'com.google.gms.google-services'
id 'com.google.firebase.crashlytics'
+ id 'kotlin-kapt'
+ id 'com.google.dagger.hilt.android'
}
Properties properties = new Properties()
@@ -18,10 +20,11 @@ android {
applicationId "com.now.naaga"
minSdk 28
targetSdk 33
- versionCode 4
- versionName "1.0.1"
+ versionCode 5
+ versionName "1.1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ testInstrumentationRunner "com.now.naaga.HiltTestRunner"
buildConfigField "String", "BASE_URL", properties["BASE_URL"]
buildConfigField "String", "KAKAO_NATIVE_APP_KEY", properties["KAKAO_NATIVE_APP_KEY"]
resValue "string", "kakao_redirection_scheme", properties["kakao_redirection_scheme"]
@@ -31,6 +34,8 @@ android {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+
+ ndk.debugSymbolLevel 'FULL'
}
}
compileOptions {
@@ -43,15 +48,20 @@ android {
dataBinding {
enable = true
}
+ testOptions {
+ unitTests {
+ includeAndroidResources = true
+ }
+ }
}
dependencies {
-
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation project(path: ':domain')
+ implementation 'androidx.test:runner:1.5.2'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
@@ -88,6 +98,7 @@ dependencies {
// coroutines
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.4.2")
+ testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3")
// splash
implementation "androidx.core:core-splashscreen:1.0.0"
@@ -96,6 +107,7 @@ dependencies {
implementation(platform('com.google.firebase:firebase-bom:32.2.0'))
implementation "com.google.firebase:firebase-analytics-ktx"
implementation("com.google.firebase:firebase-crashlytics-ktx")
+ implementation("com.google.firebase:firebase-config-ktx")
// kakao Login
implementation "com.kakao.sdk:v2-user:2.15.0"
@@ -105,4 +117,26 @@ dependencies {
// lottie
implementation "com.airbnb.android:lottie:6.1.0"
+
+ // ViewModel Test
+ testImplementation("androidx.arch.core:core-testing:2.2.0")
+
+ // Robolectric
+ testImplementation("org.robolectric:robolectric:4.9")
+
+ // hilt
+ implementation "com.google.dagger:hilt-android:2.44"
+ kapt "com.google.dagger:hilt-compiler:2.44"
+
+ implementation "androidx.activity:activity-ktx:1.7.2"
+
+ // For Robolectric tests.
+ testImplementation 'com.google.dagger:hilt-android-testing:2.44'
+ // ...with Kotlin.
+ kaptTest 'com.google.dagger:hilt-android-compiler:2.44'
+}
+
+// Allow references to generated code
+kapt {
+ correctErrorTypes true
}
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 513434637..f8a7b38c7 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -30,8 +30,12 @@
+ android:screenOrientation="portrait">
+
+
+
+
+
-
-
-
-
-
-
+ android:exported="false" />
+
diff --git a/android/app/src/main/ic_launcher-playstore.png b/android/app/src/main/ic_launcher-playstore.png
new file mode 100644
index 000000000..27f053269
Binary files /dev/null and b/android/app/src/main/ic_launcher-playstore.png differ
diff --git a/android/app/src/main/java/com/now/naaga/NaagaApplication.kt b/android/app/src/main/java/com/now/naaga/NaagaApplication.kt
index 31015517a..e68c27296 100644
--- a/android/app/src/main/java/com/now/naaga/NaagaApplication.kt
+++ b/android/app/src/main/java/com/now/naaga/NaagaApplication.kt
@@ -5,7 +5,9 @@ import androidx.appcompat.app.AppCompatDelegate
import com.kakao.sdk.common.KakaoSdk
import com.now.naaga.data.local.AuthDataSource
import com.now.naaga.data.local.DefaultAuthDataSource
+import dagger.hilt.android.HiltAndroidApp
+@HiltAndroidApp
class NaagaApplication : Application() {
override fun onCreate() {
diff --git a/android/app/src/main/java/com/now/naaga/data/firebase/analytics/ButtonNames.kt b/android/app/src/main/java/com/now/naaga/data/firebase/analytics/ButtonNames.kt
index e3a115fce..34b5d465f 100644
--- a/android/app/src/main/java/com/now/naaga/data/firebase/analytics/ButtonNames.kt
+++ b/android/app/src/main/java/com/now/naaga/data/firebase/analytics/ButtonNames.kt
@@ -2,7 +2,7 @@ package com.now.naaga.data.firebase.analytics
// BeginAdventureActivity
const val BEGIN_BEGIN_ADVENTURE = "BEGIN_ADVENTURE"
-const val BEGIN_GO_RANK = "GO_RANK"
+const val BEGIN_GO_SETTING = "GO_SETTING"
const val BEGIN_GO_UPLOAD = "GO_UPLOAD"
const val BEGIN_GO_MYPAGE = "GO_MYPAGE"
diff --git a/android/app/src/main/java/com/now/naaga/data/firebase/analytics/ServerErrorNames.kt b/android/app/src/main/java/com/now/naaga/data/firebase/analytics/ServerErrorNames.kt
index 9fd47360a..ea7e7acac 100644
--- a/android/app/src/main/java/com/now/naaga/data/firebase/analytics/ServerErrorNames.kt
+++ b/android/app/src/main/java/com/now/naaga/data/firebase/analytics/ServerErrorNames.kt
@@ -6,3 +6,4 @@ const val ADVENTURE_RESULT = "ADVENTURE_RESULT"
const val LOGIN_AUTH = "LOGIN"
const val MY_PAGE_STATISTICS = "MY_PAGE"
const val RANK_RANK = "RANK"
+const val SPLASH_MY_PAGE_STATISTICS = "SPLASH_BUT_MY_PAGE_API"
diff --git a/android/app/src/main/java/com/now/naaga/data/local/AuthDataSource.kt b/android/app/src/main/java/com/now/naaga/data/local/AuthDataSource.kt
index e0a07683e..0616b9227 100644
--- a/android/app/src/main/java/com/now/naaga/data/local/AuthDataSource.kt
+++ b/android/app/src/main/java/com/now/naaga/data/local/AuthDataSource.kt
@@ -5,4 +5,5 @@ interface AuthDataSource {
fun setAccessToken(newToken: String)
fun getRefreshToken(): String?
fun setRefreshToken(newToken: String)
+ fun resetToken()
}
diff --git a/android/app/src/main/java/com/now/naaga/data/local/DefaultAuthDataSource.kt b/android/app/src/main/java/com/now/naaga/data/local/DefaultAuthDataSource.kt
index 9b9757482..f7a2ef711 100644
--- a/android/app/src/main/java/com/now/naaga/data/local/DefaultAuthDataSource.kt
+++ b/android/app/src/main/java/com/now/naaga/data/local/DefaultAuthDataSource.kt
@@ -19,7 +19,8 @@ class DefaultAuthDataSource(context: Context) : AuthDataSource {
)
override fun getAccessToken(): String? {
- return authPreference.getString(ACCESS_TOKEN, null)
+ val accessToken = authPreference.getString(ACCESS_TOKEN, null) ?: return null
+ return BEARER + accessToken
}
override fun setAccessToken(newToken: String) {
@@ -27,16 +28,24 @@ class DefaultAuthDataSource(context: Context) : AuthDataSource {
}
override fun getRefreshToken(): String? {
- return authPreference.getString(REFRESH_TOKEN, null)
+ val refreshToken = authPreference.getString(REFRESH_TOKEN, null) ?: return null
+ return BEARER + refreshToken
}
override fun setRefreshToken(newToken: String) {
authPreference.edit().putString(REFRESH_TOKEN, newToken).apply()
}
+ override fun resetToken() {
+ authPreference.edit().putString(ACCESS_TOKEN, DEFAULT_TOKEN).apply()
+ authPreference.edit().putString(REFRESH_TOKEN, DEFAULT_TOKEN).apply()
+ }
+
companion object {
+ private const val DEFAULT_TOKEN = ""
private const val AUTH_ENCRYPTED_PREFERENCE = "AUTH_ENCRYPTED_PREFERENCE"
private const val ACCESS_TOKEN = "ACCESS_TOKEN"
private const val REFRESH_TOKEN = "REFRESH_TOKEN"
+ private const val BEARER = "Bearer "
}
}
diff --git a/android/app/src/main/java/com/now/naaga/data/mapper/PlaceMapper.kt b/android/app/src/main/java/com/now/naaga/data/mapper/PlaceMapper.kt
index 7f10ab138..c254fa327 100644
--- a/android/app/src/main/java/com/now/naaga/data/mapper/PlaceMapper.kt
+++ b/android/app/src/main/java/com/now/naaga/data/mapper/PlaceMapper.kt
@@ -2,6 +2,7 @@ package com.now.naaga.data.mapper
import com.now.domain.model.Place
import com.now.naaga.data.remote.dto.PlaceDto
+import com.now.naaga.data.remote.dto.PostPlaceDto
fun Place.toDto(): PlaceDto {
return PlaceDto(
@@ -22,3 +23,13 @@ fun PlaceDto.toDomain(): Place {
description = description,
)
}
+
+fun PostPlaceDto.toDomain(): Place {
+ return Place(
+ id = id,
+ name = name,
+ coordinate = coordinate.toDomain(),
+ image = imageUrl,
+ description = description,
+ )
+}
diff --git a/android/app/src/main/java/com/now/naaga/data/remote/dto/PostPlaceDto.kt b/android/app/src/main/java/com/now/naaga/data/remote/dto/PostPlaceDto.kt
new file mode 100644
index 000000000..149455438
--- /dev/null
+++ b/android/app/src/main/java/com/now/naaga/data/remote/dto/PostPlaceDto.kt
@@ -0,0 +1,20 @@
+package com.now.naaga.data.remote.dto
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class PostPlaceDto(
+ @SerialName("id")
+ val id: Long,
+ @SerialName("name")
+ val name: String,
+ @SerialName("coordinate")
+ val coordinate: CoordinateDto,
+ @SerialName("imageUrl")
+ val imageUrl: String,
+ @SerialName("description")
+ val description: String,
+ @SerialName("registeredPlayerId")
+ val registeredPlayerId: Long,
+)
diff --git a/android/app/src/main/java/com/now/naaga/data/remote/dto/RefreshTokenDto.kt b/android/app/src/main/java/com/now/naaga/data/remote/dto/RefreshTokenDto.kt
new file mode 100644
index 000000000..6c6ed2410
--- /dev/null
+++ b/android/app/src/main/java/com/now/naaga/data/remote/dto/RefreshTokenDto.kt
@@ -0,0 +1,10 @@
+package com.now.naaga.data.remote.dto
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class RefreshTokenDto(
+ @SerialName("refreshToken")
+ val refreshToken: String,
+)
diff --git a/android/app/src/main/java/com/now/naaga/data/remote/retrofit/AuthInterceptor.kt b/android/app/src/main/java/com/now/naaga/data/remote/retrofit/AuthInterceptor.kt
index 6848c6653..bfe90be5a 100644
--- a/android/app/src/main/java/com/now/naaga/data/remote/retrofit/AuthInterceptor.kt
+++ b/android/app/src/main/java/com/now/naaga/data/remote/retrofit/AuthInterceptor.kt
@@ -6,6 +6,7 @@ import com.now.naaga.BuildConfig
import com.now.naaga.NaagaApplication
import com.now.naaga.data.remote.dto.FailureDto
import com.now.naaga.data.remote.dto.NaagaAuthDto
+import com.now.naaga.data.throwable.DataThrowable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
@@ -13,6 +14,7 @@ import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
+import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import org.json.JSONObject
@@ -22,72 +24,64 @@ class AuthInterceptor : Interceptor {
private val client = OkHttpClient.Builder().build()
override fun intercept(chain: Interceptor.Chain): Response {
- if (isLoginRequest(chain.request())) {
- return chain.proceed(chain.request())
- }
+ val accessToken = getAccessToken() ?: return chain.proceed(chain.request())
- val headerAddedRequest = chain.request().newBuilder().addHeader(AUTH_KEY, getAccessToken()).build()
- val response = chain.proceed(headerAddedRequest)
+ val headerAddedRequest = chain.request().newBuilder().addHeader(AUTH_KEY, accessToken).build()
+ val response: Response = chain.proceed(headerAddedRequest)
if (response.code == 401) {
- val failureDto = response.getDto()
- when (failureDto.code) {
- 101 -> throw IllegalStateException(AUTH_ERROR.format(headerAddedRequest.url.toString()))
- 102 -> {
- response.close()
- return reRequest(chain)
- }
-
- 103 -> throw IllegalStateException(REFRESH_EXPIRED)
- }
+ val newAccessToken = getRefreshedToken(accessToken).getOrElse { return response }
+ return chain.proceed(chain.request().newBuilder().addHeader(AUTH_KEY, newAccessToken).build())
}
return response
}
- private fun isLoginRequest(request: Request): Boolean {
- val path: String = request.url.encodedPath.substringAfter(BuildConfig.BASE_URL)
- return path == AUTH_PATH
- }
+ private fun getRefreshedToken(accessToken: String): Result {
+ val requestBody = createRefreshRequestBody()
+ val request = createRefreshRequest(requestBody, accessToken)
- private fun reRequest(chain: Interceptor.Chain): Response {
- val token: String = getRefreshedToken()
- val request = chain.request().newBuilder().addHeader(AUTH_KEY, token).build()
- return chain.proceed(request)
+ val auth: NaagaAuthDto = requestRefresh(request).getOrElse {
+ return Result.failure(it)
+ }
+ storeToken(auth.accessToken, auth.refreshToken)
+ return Result.success(auth.accessToken)
}
- private fun getRefreshedToken(): String {
- val body = JSONObject()
+ private fun createRefreshRequestBody(): RequestBody {
+ return JSONObject()
.put(AUTH_REFRESH_KEY, getRefreshToken())
.toString()
.toRequestBody(contentType = "application/json".toMediaType())
+ }
- val request = Request.Builder()
+ private fun createRefreshRequest(requestBody: RequestBody, accessToken: String): Request {
+ return Request.Builder()
.url(BuildConfig.BASE_URL + AUTH_REFRESH_PATH)
- .post(body)
- .addHeader(AUTH_KEY, getAccessToken())
+ .post(requestBody)
+ .addHeader(AUTH_KEY, accessToken)
.build()
-
- val auth = requestRefresh(request)
- storeToken(auth.accessToken, auth.refreshToken)
- return auth.accessToken
}
- private fun requestRefresh(request: Request): NaagaAuthDto {
+ private fun requestRefresh(request: Request): Result {
val response: Response = runBlocking {
withContext(Dispatchers.IO) { client.newCall(request).execute() }
}
if (response.isSuccessful) {
- return response.getDto()
+ return Result.success(response.getDto())
+ }
+ val failedResponse = response.getDto()
+ if (failedResponse.code == 101) {
+ return Result.failure(DataThrowable.AuthorizationThrowable(failedResponse.code, failedResponse.message))
}
- throw IllegalStateException(REFRESH_FAILURE)
+ return Result.failure(IllegalStateException(REFRESH_FAILURE))
}
- private fun getAccessToken(): String {
- return NaagaApplication.authDataSource.getAccessToken() ?: throw IllegalStateException(NO_ACCESS_TOKEN)
+ private fun getAccessToken(): String? {
+ return NaagaApplication.authDataSource.getAccessToken()
}
private fun getRefreshToken(): String {
- return NaagaApplication.authDataSource.getRefreshToken() ?: throw IllegalStateException(NO_REFRESH_TOKEN)
+ return requireNotNull(NaagaApplication.authDataSource.getRefreshToken()) { NO_REFRESH_TOKEN }
}
private fun storeToken(accessToken: String, refreshToken: String) {
@@ -107,9 +101,6 @@ class AuthInterceptor : Interceptor {
const val AUTH_PATH = "auth"
const val AUTH_REFRESH_PATH = "auth/refresh"
- const val AUTH_ERROR = "%s에서 인증 오류 발생"
- const val REFRESH_EXPIRED = "리프레시 토큰 만료"
- const val NO_ACCESS_TOKEN = "엑세스 토큰이 없습니다"
const val NO_REFRESH_TOKEN = "리프레시 토큰이 없습니다"
const val REFRESH_FAILURE = "토큰 리프레시 실패"
}
diff --git a/android/app/src/main/java/com/now/naaga/data/remote/retrofit/RetrofitFactory.kt b/android/app/src/main/java/com/now/naaga/data/remote/retrofit/RetrofitFactory.kt
index 2aef2ac2a..c798eeef8 100644
--- a/android/app/src/main/java/com/now/naaga/data/remote/retrofit/RetrofitFactory.kt
+++ b/android/app/src/main/java/com/now/naaga/data/remote/retrofit/RetrofitFactory.kt
@@ -2,11 +2,10 @@ package com.now.naaga.data.remote.retrofit
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import com.now.naaga.BuildConfig
-import com.now.naaga.NaagaApplication
import kotlinx.serialization.json.Json
-import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
+import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
object RetrofitFactory {
@@ -18,20 +17,10 @@ object RetrofitFactory {
.client(createOkHttpClient())
.build()
- private fun createInterceptor(): Interceptor = Interceptor { chain ->
- val token: String = NaagaApplication.authDataSource.getAccessToken() ?: ""
- with(chain) {
- val newRequest = request().newBuilder()
- .addHeader("Authorization", "Bearer $token")
- .addHeader("Content-Type", "application/json")
- .build()
- proceed(newRequest)
- }
- }
-
private fun createOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder().apply {
- addInterceptor(createInterceptor())
+ addInterceptor(AuthInterceptor())
+ addInterceptor(HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BASIC })
}.build()
}
}
diff --git a/android/app/src/main/java/com/now/naaga/data/remote/retrofit/RetrofitUtil.kt b/android/app/src/main/java/com/now/naaga/data/remote/retrofit/RetrofitUtil.kt
deleted file mode 100644
index e40c26f76..000000000
--- a/android/app/src/main/java/com/now/naaga/data/remote/retrofit/RetrofitUtil.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-package com.now.naaga.data.remote.retrofit
-
-import com.now.naaga.data.throwable.DataThrowable.AuthorizationThrowable
-import com.now.naaga.data.throwable.DataThrowable.GameThrowable
-import com.now.naaga.data.throwable.DataThrowable.IllegalStateThrowable
-import com.now.naaga.data.throwable.DataThrowable.PlaceThrowable
-import com.now.naaga.data.throwable.DataThrowable.PlayerThrowable
-import com.now.naaga.data.throwable.DataThrowable.UniversalThrowable
-import org.json.JSONObject
-import retrofit2.Call
-import retrofit2.Callback
-import retrofit2.Response
-
-fun Response.codeIn400s(): Boolean {
- return this.code() in 400..499
-}
-
-fun Response.codeIn500s(): Boolean {
- return this.code() in 500..599
-}
-
-fun Call.fetchResponse(
- onSuccess: (T) -> Unit,
- onFailure: (Throwable) -> Unit,
-) {
- enqueue(
- object : Callback {
- override fun onResponse(call: Call, response: Response) {
- if (response.isSuccessful) {
- val body = response.body() ?: return onFailure(IllegalStateThrowable())
- onSuccess(body)
- }
-
- if (response.codeIn500s()) {
- return onFailure(IllegalStateThrowable())
- }
-
- if (response.codeIn400s()) {
- val errorResponse = response.errorBody()?.string()
- val jsonObject = errorResponse?.let { JSONObject(it) }
- val code = jsonObject?.getInt("code") ?: 0
- val message = jsonObject?.getString("message") ?: ""
-
- when (code) {
- in 100..199 -> { onFailure(AuthorizationThrowable(code, message)) }
- in 200..299 -> { onFailure(UniversalThrowable(code, message)) }
- in 300..399 -> { onFailure(PlayerThrowable(code, message)) }
- in 400..499 -> { onFailure(GameThrowable(code, message)) }
- in 500..599 -> { onFailure(PlaceThrowable(code, message)) }
- }
- }
- }
-
- override fun onFailure(call: Call, t: Throwable) {
- onFailure(IllegalStateThrowable())
- }
- },
- )
-}
diff --git a/android/app/src/main/java/com/now/naaga/data/remote/retrofit/ServicePool.kt b/android/app/src/main/java/com/now/naaga/data/remote/retrofit/ServicePool.kt
deleted file mode 100644
index 74ca786a3..000000000
--- a/android/app/src/main/java/com/now/naaga/data/remote/retrofit/ServicePool.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.now.naaga.data.remote.retrofit
-
-import com.now.naaga.data.remote.retrofit.RetrofitFactory.retrofit
-import com.now.naaga.data.remote.retrofit.service.AdventureService
-import com.now.naaga.data.remote.retrofit.service.AuthService
-import com.now.naaga.data.remote.retrofit.service.PlaceService
-import com.now.naaga.data.remote.retrofit.service.RankService
-import com.now.naaga.data.remote.retrofit.service.StatisticsService
-
-object ServicePool {
- val adventureService = retrofit.create(AdventureService::class.java)
- val rankService = retrofit.create(RankService::class.java)
- val statisticsService = retrofit.create(StatisticsService::class.java)
- val placeService = retrofit.create(PlaceService::class.java)
- val authService = retrofit.create(AuthService::class.java)
-}
diff --git a/android/app/src/main/java/com/now/naaga/data/remote/retrofit/service/AdventureService.kt b/android/app/src/main/java/com/now/naaga/data/remote/retrofit/service/AdventureService.kt
index 96ac30141..b21316daf 100644
--- a/android/app/src/main/java/com/now/naaga/data/remote/retrofit/service/AdventureService.kt
+++ b/android/app/src/main/java/com/now/naaga/data/remote/retrofit/service/AdventureService.kt
@@ -6,7 +6,7 @@ import com.now.naaga.data.remote.dto.AdventureStatusDto
import com.now.naaga.data.remote.dto.CoordinateDto
import com.now.naaga.data.remote.dto.FinishGameDto
import com.now.naaga.data.remote.dto.HintDto
-import retrofit2.Call
+import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.PATCH
@@ -16,49 +16,49 @@ import retrofit2.http.Query
interface AdventureService {
@GET("/games")
- fun getMyGames(): Call>
+ suspend fun getMyGames(): Response>
@GET("/games/{gameId}")
- fun getGame(
+ suspend fun getGame(
@Path("gameId") gameId: Long,
- ): Call
+ ): Response
@GET("/games")
- fun getGamesByStatus(
+ suspend fun getGamesByStatus(
@Query("status") status: String,
- ): Call>
+ ): Response>
@POST("/games")
- fun beginGame(
+ suspend fun beginGame(
@Body coordinateDto: CoordinateDto,
- ): Call
+ ): Response
@PATCH("/games/{gameId}")
- fun endGame(
+ suspend fun endGame(
@Path("gameId") gameId: Long,
@Body finishGameDto: FinishGameDto,
- ): Call
+ ): Response
@GET("/games/{gameId}/result")
- fun getGameResult(
+ suspend fun getGameResult(
@Path("gameId") gameId: Long,
- ): Call
+ ): Response
@GET("/games/results")
- fun getMyGameResults(
+ suspend fun getMyGameResults(
@Query("sort-by") sortBy: String,
@Query("order") order: String,
- ): Call>
+ ): Response>
@GET("/games/{gameId}/hints/{hintId}")
- fun getHint(
+ suspend fun getHint(
@Path("gameId") gameId: Long,
@Path("hintId") hingId: Long,
- ): Call
+ ): Response
@POST("/games/{gameId}/hints")
- fun requestHint(
+ suspend fun requestHint(
@Path("gameId") gameId: Long,
@Body coordinateDto: CoordinateDto,
- ): Call
+ ): Response
}
diff --git a/android/app/src/main/java/com/now/naaga/data/remote/retrofit/service/AuthService.kt b/android/app/src/main/java/com/now/naaga/data/remote/retrofit/service/AuthService.kt
index 73fdde4e0..7308ce262 100644
--- a/android/app/src/main/java/com/now/naaga/data/remote/retrofit/service/AuthService.kt
+++ b/android/app/src/main/java/com/now/naaga/data/remote/retrofit/service/AuthService.kt
@@ -2,13 +2,20 @@ package com.now.naaga.data.remote.retrofit.service
import com.now.naaga.data.remote.dto.NaagaAuthDto
import com.now.naaga.data.remote.dto.PlatformAuthDto
-import retrofit2.Call
+import retrofit2.Response
import retrofit2.http.Body
+import retrofit2.http.DELETE
import retrofit2.http.POST
interface AuthService {
@POST("/auth")
- fun requestAuth(
+ suspend fun requestAuth(
@Body platformAuthDto: PlatformAuthDto,
- ): Call
+ ): Response
+
+ @DELETE("/auth/unlink")
+ suspend fun withdrawalMember(): Response
+
+ @DELETE("/auth")
+ suspend fun requestLogout(): Response
}
diff --git a/android/app/src/main/java/com/now/naaga/data/remote/retrofit/service/PlaceService.kt b/android/app/src/main/java/com/now/naaga/data/remote/retrofit/service/PlaceService.kt
index 35e395eb1..0fb325c3b 100644
--- a/android/app/src/main/java/com/now/naaga/data/remote/retrofit/service/PlaceService.kt
+++ b/android/app/src/main/java/com/now/naaga/data/remote/retrofit/service/PlaceService.kt
@@ -1,34 +1,34 @@
package com.now.naaga.data.remote.retrofit.service
import com.now.naaga.data.remote.dto.PlaceDto
+import com.now.naaga.data.remote.dto.PostPlaceDto
import okhttp3.MultipartBody
import okhttp3.RequestBody
-import retrofit2.Call
+import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.Part
+import retrofit2.http.PartMap
import retrofit2.http.Path
import retrofit2.http.Query
interface PlaceService {
@GET("/places")
- fun getMyPlace(
+ suspend fun getMyPlace(
@Query("sort-by") sort: String,
@Query("order") order: String,
- ): Call>
+ ): Response>
@GET("/places/{placeId}")
- fun getPlace(
+ suspend fun getPlace(
@Path("placeId") placeId: Long,
- ): Call
+ ): Response
@Multipart
- @POST("/places")
- fun registerPlace(
- @Part name: RequestBody,
- @Part description: RequestBody,
- @Part coordinate: RequestBody,
+ @POST("/temporary-places")
+ suspend fun registerPlace(
+ @PartMap postData: HashMap,
@Part imageFile: MultipartBody.Part,
- ): Call
+ ): Response
}
diff --git a/android/app/src/main/java/com/now/naaga/data/remote/retrofit/service/RankService.kt b/android/app/src/main/java/com/now/naaga/data/remote/retrofit/service/RankService.kt
index bcf36d65b..cf35b3032 100644
--- a/android/app/src/main/java/com/now/naaga/data/remote/retrofit/service/RankService.kt
+++ b/android/app/src/main/java/com/now/naaga/data/remote/retrofit/service/RankService.kt
@@ -1,17 +1,17 @@
package com.now.naaga.data.remote.retrofit.service
import com.now.naaga.data.remote.dto.RankDto
-import retrofit2.Call
+import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Query
interface RankService {
@GET("/ranks")
- fun getAllRanks(
+ suspend fun getAllRanks(
@Query("sort-by") sortBy: String,
@Query("order") order: String,
- ): Call>
+ ): Response>
@GET("/ranks/my")
- fun getMyRank(): Call
+ suspend fun getMyRank(): Response
}
diff --git a/android/app/src/main/java/com/now/naaga/data/remote/retrofit/service/StatisticsService.kt b/android/app/src/main/java/com/now/naaga/data/remote/retrofit/service/StatisticsService.kt
index 5d95028a9..0682a0d62 100644
--- a/android/app/src/main/java/com/now/naaga/data/remote/retrofit/service/StatisticsService.kt
+++ b/android/app/src/main/java/com/now/naaga/data/remote/retrofit/service/StatisticsService.kt
@@ -1,10 +1,10 @@
package com.now.naaga.data.remote.retrofit.service
import com.now.naaga.data.remote.dto.StatisticsDto
-import retrofit2.Call
+import retrofit2.Response
import retrofit2.http.GET
interface StatisticsService {
@GET("/statistics/my")
- fun getMyStatistics(): Call
+ suspend fun getMyStatistics(): Response
}
diff --git a/android/app/src/main/java/com/now/naaga/data/repository/DefaultAdventureRepository.kt b/android/app/src/main/java/com/now/naaga/data/repository/DefaultAdventureRepository.kt
index c4546626a..ff0815e2e 100644
--- a/android/app/src/main/java/com/now/naaga/data/repository/DefaultAdventureRepository.kt
+++ b/android/app/src/main/java/com/now/naaga/data/repository/DefaultAdventureRepository.kt
@@ -11,127 +11,63 @@ import com.now.domain.model.SortType
import com.now.domain.repository.AdventureRepository
import com.now.naaga.data.mapper.toDomain
import com.now.naaga.data.mapper.toDto
-import com.now.naaga.data.remote.dto.AdventureDto
import com.now.naaga.data.remote.dto.FinishGameDto
-import com.now.naaga.data.remote.retrofit.ServicePool
-import com.now.naaga.data.remote.retrofit.fetchResponse
+import com.now.naaga.data.remote.retrofit.service.AdventureService
+import com.now.naaga.util.getValueOrThrow
-class DefaultAdventureRepository : AdventureRepository {
- override fun fetchMyAdventures(callback: (Result>) -> Unit) {
- val call = ServicePool.adventureService.getMyGames()
- call.fetchResponse(
- onSuccess = { adventureDtos: List ->
- callback(Result.success(adventureDtos.map { it.toDomain() }))
- },
- onFailure = {
- callback(Result.failure(it))
- },
- )
+class DefaultAdventureRepository(
+ private val adventureService: AdventureService,
+) : AdventureRepository {
+ override suspend fun fetchMyAdventures(): List {
+ val response = adventureService.getMyGames().getValueOrThrow()
+ return response.map { it.toDomain() }
}
- override fun fetchAdventure(adventureId: Long, callback: (Result) -> Unit) {
- val call = ServicePool.adventureService.getGame(adventureId)
- call.fetchResponse(
- onSuccess = { adventureDto ->
- callback(Result.success(adventureDto.toDomain()))
- },
- onFailure = {
- callback(Result.failure(it))
- },
- )
+ override suspend fun fetchAdventure(adventureId: Long): Adventure {
+ val response = adventureService.getGame(adventureId).getValueOrThrow()
+ return response.toDomain()
}
- override fun fetchAdventureByStatus(status: AdventureStatus, callback: (Result>) -> Unit) {
- val call = ServicePool.adventureService.getGamesByStatus(status.name)
- call.fetchResponse(
- onSuccess = { adventureDtos: List ->
- callback(Result.success(adventureDtos.map { it.toDomain() }))
- },
- onFailure = {
- callback(Result.failure(it))
- },
- )
+ override suspend fun fetchAdventureByStatus(status: AdventureStatus): List {
+ val response = adventureService.getGamesByStatus(status.name).getValueOrThrow()
+ return response.map { it.toDomain() }
}
- override fun beginAdventure(coordinate: Coordinate, callback: (Result) -> Unit) {
- val call = ServicePool.adventureService.beginGame(coordinate.toDto())
- call.fetchResponse(
- onSuccess = { adventureDto ->
- callback(Result.success(adventureDto.toDomain()))
- },
- onFailure = {
- callback(Result.failure(it))
- },
- )
+ override suspend fun beginAdventure(coordinate: Coordinate): Adventure {
+ val response = adventureService.beginGame(coordinate.toDto()).getValueOrThrow()
+ return response.toDomain()
}
- override fun endGame(
+ override suspend fun endGame(
adventureId: Long,
endType: AdventureEndType,
coordinate: Coordinate,
- callback: (Result) -> Unit,
- ) {
+ ): AdventureStatus {
val finishGameDto = FinishGameDto(endType.name, coordinate.toDto())
- val call = ServicePool.adventureService.endGame(adventureId, finishGameDto)
- call.fetchResponse(
- onSuccess = { adventureStatusDto ->
- callback(Result.success(AdventureStatus.getStatus(adventureStatusDto.gameStatus)))
- },
- onFailure = {
- callback(Result.failure(it))
- },
- )
+ val response = adventureService.endGame(adventureId, finishGameDto).getValueOrThrow()
+ return AdventureStatus.getStatus(response.gameStatus)
}
- override fun fetchAdventureResult(adventureId: Long, callback: (Result) -> Unit) {
- val call = ServicePool.adventureService.getGameResult(adventureId)
- call.fetchResponse(
- onSuccess = { adventureResultDto ->
- callback(Result.success(adventureResultDto.toDomain()))
- },
- onFailure = {
- callback(Result.failure(it))
- },
- )
+ override suspend fun fetchAdventureResult(adventureId: Long): AdventureResult {
+ val response = adventureService.getGameResult(adventureId).getValueOrThrow()
+ return response.toDomain()
}
- override fun fetchMyAdventureResults(
+ override suspend fun fetchMyAdventureResults(
sortBy: SortType,
order: OrderType,
- callback: (Result>) -> Unit,
- ) {
- val call = ServicePool.adventureService.getMyGameResults(sortBy.name, order.name)
- call.fetchResponse(
- onSuccess = { adventureResultDtos ->
- callback(Result.success(adventureResultDtos.map { it.toDomain() }))
- },
- onFailure = {
- callback(Result.failure(it))
- },
- )
+ ): List {
+ val response = adventureService.getMyGameResults(sortBy.name, order.name).getValueOrThrow()
+ return response.map { it.toDomain() }
}
- override fun fetchHint(adventureId: Long, hintId: Long, callback: (Result) -> Unit) {
- val call = ServicePool.adventureService.getHint(adventureId, hintId)
- call.fetchResponse(
- onSuccess = { hintDto ->
- callback(Result.success(hintDto.toDomain()))
- },
- onFailure = {
- callback(Result.failure(it))
- },
- )
+ override suspend fun fetchHint(adventureId: Long, hintId: Long): Hint {
+ val response = adventureService.getHint(adventureId, hintId).getValueOrThrow()
+ return response.toDomain()
}
- override fun makeHint(adventureId: Long, coordinate: Coordinate, callback: (Result) -> Unit) {
- val call = ServicePool.adventureService.requestHint(adventureId, coordinate.toDto())
- call.fetchResponse(
- onSuccess = { hintDto ->
- callback(Result.success(hintDto.toDomain()))
- },
- onFailure = {
- callback(Result.failure(it))
- },
- )
+ override suspend fun makeHint(adventureId: Long, coordinate: Coordinate): Hint {
+ val response = adventureService.requestHint(adventureId, coordinate.toDto()).getValueOrThrow()
+ return response.toDomain()
}
}
diff --git a/android/app/src/main/java/com/now/naaga/data/repository/DefaultAuthRepository.kt b/android/app/src/main/java/com/now/naaga/data/repository/DefaultAuthRepository.kt
index 09bf224a5..429643c00 100644
--- a/android/app/src/main/java/com/now/naaga/data/repository/DefaultAuthRepository.kt
+++ b/android/app/src/main/java/com/now/naaga/data/repository/DefaultAuthRepository.kt
@@ -4,22 +4,42 @@ import com.now.domain.model.PlatformAuth
import com.now.domain.repository.AuthRepository
import com.now.naaga.data.local.AuthDataSource
import com.now.naaga.data.mapper.toDto
-import com.now.naaga.data.remote.retrofit.ServicePool
-import com.now.naaga.data.remote.retrofit.fetchResponse
+import com.now.naaga.data.remote.retrofit.service.AuthService
+import com.now.naaga.util.getValueOrThrow
+import com.now.naaga.util.unlinkWithKakao
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
-class DefaultAuthRepository(private val authDataSource: AuthDataSource) : AuthRepository {
- override fun getToken(
- platformAuth: PlatformAuth,
- callback: (Result) -> Unit,
- ) {
- val call = ServicePool.authService.requestAuth(platformAuth.toDto())
- call.fetchResponse(
- onSuccess = { naagaAuthDto ->
+class DefaultAuthRepository(
+ private val authDataSource: AuthDataSource,
+ private val authService: AuthService,
+ private val dispatcher: CoroutineDispatcher = Dispatchers.IO,
+) : AuthRepository {
+
+ override suspend fun getToken(platformAuth: PlatformAuth): Boolean {
+ return withContext(dispatcher) {
+ val response = authService.requestAuth(platformAuth.toDto())
+ runCatching {
+ val naagaAuthDto = response.getValueOrThrow()
authDataSource.setAccessToken(naagaAuthDto.accessToken)
authDataSource.setRefreshToken(naagaAuthDto.refreshToken)
- callback(Result.success(true))
- },
- onFailure = { callback(Result.failure(it)) },
- )
+ return@withContext true
+ }
+ return@withContext false
+ }
+ }
+
+ override suspend fun logout() {
+ withContext(dispatcher) {
+ val response = authService.requestLogout()
+ authDataSource.resetToken()
+ response.getValueOrThrow()
+ }
+ }
+
+ override suspend fun withdrawalMember() {
+ authService.withdrawalMember()
+ unlinkWithKakao()
}
}
diff --git a/android/app/src/main/java/com/now/naaga/data/repository/DefaultPlaceRepository.kt b/android/app/src/main/java/com/now/naaga/data/repository/DefaultPlaceRepository.kt
index 16571c10c..76a77d1b9 100644
--- a/android/app/src/main/java/com/now/naaga/data/repository/DefaultPlaceRepository.kt
+++ b/android/app/src/main/java/com/now/naaga/data/repository/DefaultPlaceRepository.kt
@@ -4,68 +4,60 @@ import com.now.domain.model.Coordinate
import com.now.domain.model.Place
import com.now.domain.repository.PlaceRepository
import com.now.naaga.data.mapper.toDomain
-import com.now.naaga.data.mapper.toDto
-import com.now.naaga.data.remote.dto.PlaceDto
-import com.now.naaga.data.remote.retrofit.ServicePool.placeService
-import com.now.naaga.data.remote.retrofit.fetchResponse
-import kotlinx.serialization.encodeToString
-import kotlinx.serialization.json.Json
+import com.now.naaga.data.remote.retrofit.service.PlaceService
+import com.now.naaga.util.getValueOrThrow
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
+import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.File
-class DefaultPlaceRepository : PlaceRepository {
- override fun fetchMyPlaces(
+class DefaultPlaceRepository(
+ private val placeService: PlaceService,
+) : PlaceRepository {
+ override suspend fun fetchMyPlaces(
sortBy: String,
order: String,
- callback: (Result>) -> Unit,
- ) {
- val call = placeService.getMyPlace(sortBy, order)
- call.fetchResponse(
- onSuccess = { placeDtos: List ->
- callback(Result.success(placeDtos.map { it.toDomain() }))
- },
- onFailure = {
- callback(Result.failure(it))
- },
- )
+ ): List {
+ val response = placeService.getMyPlace(sortBy, order)
+ return response.getValueOrThrow().map { it.toDomain() }
}
- override fun fetchPlace(placeId: Long, callback: (Result) -> Unit) {
- val call = placeService.getPlace(placeId)
- call.fetchResponse(
- onSuccess = { placeDto ->
- callback(Result.success(placeDto.toDomain()))
- },
- onFailure = {
- callback(Result.failure(it))
- },
- )
+ override suspend fun fetchPlace(placeId: Long): Place {
+ val response = placeService.getPlace(placeId)
+ return response.getValueOrThrow().toDomain()
}
- // TODO : 업로드 기능 구현 시 fetchResponse로 변경해야 함
- override fun postPlace(
+ override suspend fun postPlace(
name: String,
description: String,
coordinate: Coordinate,
image: String,
- callback: (Result) -> Unit,
- ) {
+ ): Place {
val file = File(image)
val requestFile = file.asRequestBody("image/jpeg".toMediaTypeOrNull())
val imagePart = MultipartBody.Part.createFormData(
- "imageFile",
+ KEY_IMAGE_FILE,
file.name,
requestFile,
)
- val namePart = name.toRequestBody("text/plain".toMediaTypeOrNull())
- val descriptionPart = description.toRequestBody("text/plain".toMediaTypeOrNull())
- val coordinatePart =
- Json.encodeToString(coordinate.toDto()).toRequestBody("application/json".toMediaTypeOrNull())
+ val postData = HashMap()
+ postData[KEY_NAME] = name.toRequestBody("text/plain".toMediaTypeOrNull())
+ postData[KEY_DESCRIPTION] = description.toRequestBody("text/plain".toMediaTypeOrNull())
+ postData[KEY_LATITUDE] = coordinate.latitude.toString().toRequestBody("text/plain".toMediaTypeOrNull())
+ postData[KEY_LONGITUDE] = coordinate.longitude.toString().toRequestBody("text/plain".toMediaTypeOrNull())
+
+ val response = placeService.registerPlace(postData, imagePart)
+ return response.getValueOrThrow().toDomain()
+ }
- val call = placeService.registerPlace(namePart, descriptionPart, coordinatePart, imagePart)
+ companion object {
+ const val KEY_NAME = "name"
+ const val KEY_DESCRIPTION = "description"
+ const val KEY_LATITUDE = "latitude"
+ const val KEY_LONGITUDE = "longitude"
+ const val KEY_IMAGE_FILE = "imageFile"
}
}
diff --git a/android/app/src/main/java/com/now/naaga/data/repository/DefaultRankRepository.kt b/android/app/src/main/java/com/now/naaga/data/repository/DefaultRankRepository.kt
index 006c6d636..ac447c91b 100644
--- a/android/app/src/main/java/com/now/naaga/data/repository/DefaultRankRepository.kt
+++ b/android/app/src/main/java/com/now/naaga/data/repository/DefaultRankRepository.kt
@@ -3,36 +3,22 @@ package com.now.naaga.data.repository
import com.now.domain.model.Rank
import com.now.domain.repository.RankRepository
import com.now.naaga.data.mapper.toDomain
-import com.now.naaga.data.remote.dto.RankDto
-import com.now.naaga.data.remote.retrofit.ServicePool.rankService
-import com.now.naaga.data.remote.retrofit.fetchResponse
+import com.now.naaga.data.remote.retrofit.service.RankService
+import com.now.naaga.util.getValueOrThrow
-class DefaultRankRepository : RankRepository {
- override fun getAllRanks(
+class DefaultRankRepository(
+ private val rankService: RankService,
+) : RankRepository {
+ override suspend fun getAllRanks(
sortBy: String,
order: String,
- callback: (Result>) -> Unit,
- ) {
- val call = rankService.getAllRanks(sortBy, order)
- call.fetchResponse(
- onSuccess = { rankDtos: List ->
- callback(Result.success(rankDtos.map { it.toDomain() }))
- },
- onFailure = {
- callback(Result.failure(it))
- },
- )
+ ): List {
+ val response = rankService.getAllRanks(sortBy, order)
+ return response.getValueOrThrow().map { it.toDomain() }
}
- override fun getMyRank(callback: (Result) -> Unit) {
- val call = rankService.getMyRank()
- call.fetchResponse(
- onSuccess = { rankDto ->
- callback(Result.success(rankDto.toDomain()))
- },
- onFailure = {
- callback(Result.failure(it))
- },
- )
+ override suspend fun getMyRank(): Rank {
+ val response = rankService.getMyRank()
+ return response.getValueOrThrow().toDomain()
}
}
diff --git a/android/app/src/main/java/com/now/naaga/data/repository/DefaultStatisticsRepository.kt b/android/app/src/main/java/com/now/naaga/data/repository/DefaultStatisticsRepository.kt
index c983f9779..7f1d70332 100644
--- a/android/app/src/main/java/com/now/naaga/data/repository/DefaultStatisticsRepository.kt
+++ b/android/app/src/main/java/com/now/naaga/data/repository/DefaultStatisticsRepository.kt
@@ -3,19 +3,14 @@ package com.now.naaga.data.repository
import com.now.domain.model.Statistics
import com.now.domain.repository.StatisticsRepository
import com.now.naaga.data.mapper.toDomain
-import com.now.naaga.data.remote.retrofit.ServicePool.statisticsService
-import com.now.naaga.data.remote.retrofit.fetchResponse
+import com.now.naaga.data.remote.retrofit.service.StatisticsService
+import com.now.naaga.util.getValueOrThrow
-class DefaultStatisticsRepository : StatisticsRepository {
- override fun getMyStatistics(callback: (Result) -> Unit) {
- val call = statisticsService.getMyStatistics()
- call.fetchResponse(
- onSuccess = { statisticsDto ->
- callback(Result.success(statisticsDto.toDomain()))
- },
- onFailure = {
- callback(Result.failure(it))
- },
- )
+class DefaultStatisticsRepository(
+ private val statisticsService: StatisticsService,
+) : StatisticsRepository {
+ override suspend fun getMyStatistics(): Statistics {
+ val response = statisticsService.getMyStatistics()
+ return response.getValueOrThrow().toDomain()
}
}
diff --git a/android/app/src/main/java/com/now/naaga/di/DataSourceModule.kt b/android/app/src/main/java/com/now/naaga/di/DataSourceModule.kt
new file mode 100644
index 000000000..4765ecf64
--- /dev/null
+++ b/android/app/src/main/java/com/now/naaga/di/DataSourceModule.kt
@@ -0,0 +1,19 @@
+package com.now.naaga.di
+
+import android.content.Context
+import com.now.naaga.data.local.AuthDataSource
+import com.now.naaga.data.local.DefaultAuthDataSource
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+class DataSourceModule {
+ @Singleton
+ @Provides
+ fun provideAuthDatasource(@ApplicationContext context: Context): AuthDataSource = DefaultAuthDataSource(context)
+}
diff --git a/android/app/src/main/java/com/now/naaga/di/RepositoryModule.kt b/android/app/src/main/java/com/now/naaga/di/RepositoryModule.kt
new file mode 100644
index 000000000..e7beb9286
--- /dev/null
+++ b/android/app/src/main/java/com/now/naaga/di/RepositoryModule.kt
@@ -0,0 +1,50 @@
+package com.now.naaga.di
+
+import com.now.domain.repository.AdventureRepository
+import com.now.domain.repository.AuthRepository
+import com.now.domain.repository.PlaceRepository
+import com.now.domain.repository.RankRepository
+import com.now.domain.repository.StatisticsRepository
+import com.now.naaga.data.local.AuthDataSource
+import com.now.naaga.data.remote.retrofit.service.AdventureService
+import com.now.naaga.data.remote.retrofit.service.AuthService
+import com.now.naaga.data.remote.retrofit.service.PlaceService
+import com.now.naaga.data.remote.retrofit.service.RankService
+import com.now.naaga.data.remote.retrofit.service.StatisticsService
+import com.now.naaga.data.repository.DefaultAdventureRepository
+import com.now.naaga.data.repository.DefaultAuthRepository
+import com.now.naaga.data.repository.DefaultPlaceRepository
+import com.now.naaga.data.repository.DefaultRankRepository
+import com.now.naaga.data.repository.DefaultStatisticsRepository
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+class RepositoryModule {
+ @Singleton
+ @Provides
+ fun provideRankRepository(rankService: RankService): RankRepository = DefaultRankRepository(rankService)
+
+ @Singleton
+ @Provides
+ fun provideAuthRepository(authDataSource: AuthDataSource, authService: AuthService): AuthRepository =
+ DefaultAuthRepository(authDataSource, authService)
+
+ @Singleton
+ @Provides
+ fun provideAdventureRepository(adventureService: AdventureService): AdventureRepository =
+ DefaultAdventureRepository(adventureService)
+
+ @Singleton
+ @Provides
+ fun providePlaceRepository(placeService: PlaceService): PlaceRepository = DefaultPlaceRepository(placeService)
+
+ @Singleton
+ @Provides
+ fun provideStatisticsRepository(statisticsService: StatisticsService): StatisticsRepository =
+ DefaultStatisticsRepository(statisticsService)
+}
diff --git a/android/app/src/main/java/com/now/naaga/di/ServiceModule.kt b/android/app/src/main/java/com/now/naaga/di/ServiceModule.kt
new file mode 100644
index 000000000..266411eb9
--- /dev/null
+++ b/android/app/src/main/java/com/now/naaga/di/ServiceModule.kt
@@ -0,0 +1,59 @@
+package com.now.naaga.di
+
+import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
+import com.now.naaga.BuildConfig
+import com.now.naaga.data.remote.retrofit.AuthInterceptor
+import com.now.naaga.data.remote.retrofit.service.AdventureService
+import com.now.naaga.data.remote.retrofit.service.AuthService
+import com.now.naaga.data.remote.retrofit.service.PlaceService
+import com.now.naaga.data.remote.retrofit.service.RankService
+import com.now.naaga.data.remote.retrofit.service.StatisticsService
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import kotlinx.serialization.json.Json
+import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.OkHttpClient
+import retrofit2.Retrofit
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+class ServiceModule {
+ private val BASE_URL = BuildConfig.BASE_URL
+
+ @Singleton
+ @Provides
+ fun provideOkHttpClient(): OkHttpClient = OkHttpClient.Builder().apply {
+ addInterceptor(AuthInterceptor())
+ }.build()
+
+ @Singleton
+ @Provides
+ fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit = Retrofit.Builder()
+ .baseUrl(BASE_URL)
+ .addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
+ .client(okHttpClient)
+ .build()
+
+ @Singleton
+ @Provides
+ fun provideRankService(retrofit: Retrofit): RankService = retrofit.create(RankService::class.java)
+
+ @Singleton
+ @Provides
+ fun provideStatisticsService(retrofit: Retrofit): StatisticsService = retrofit.create(StatisticsService::class.java)
+
+ @Singleton
+ @Provides
+ fun provideAdventureService(retrofit: Retrofit): AdventureService = retrofit.create(AdventureService::class.java)
+
+ @Singleton
+ @Provides
+ fun providePlaceService(retrofit: Retrofit): PlaceService = retrofit.create(PlaceService::class.java)
+
+ @Singleton
+ @Provides
+ fun provideAuthService(retrofit: Retrofit): AuthService = retrofit.create(AuthService::class.java)
+}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/adventurehistory/AdventureHistoryActivity.kt b/android/app/src/main/java/com/now/naaga/presentation/adventurehistory/AdventureHistoryActivity.kt
index e4e776905..20ed236f9 100644
--- a/android/app/src/main/java/com/now/naaga/presentation/adventurehistory/AdventureHistoryActivity.kt
+++ b/android/app/src/main/java/com/now/naaga/presentation/adventurehistory/AdventureHistoryActivity.kt
@@ -3,15 +3,17 @@ package com.now.naaga.presentation.adventurehistory
import android.content.Context
import android.content.Intent
import android.os.Bundle
+import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
-import androidx.lifecycle.ViewModelProvider
import com.now.domain.model.AdventureResult
import com.now.naaga.databinding.ActivityAdventureHistoryBinding
import com.now.naaga.presentation.adventurehistory.recyclerview.AdventureHistoryAdapter
+import dagger.hilt.android.AndroidEntryPoint
+@AndroidEntryPoint
class AdventureHistoryActivity : AppCompatActivity() {
private lateinit var binding: ActivityAdventureHistoryBinding
- private lateinit var viewModel: AdventureHistoryViewModel
+ private val viewModel: AdventureHistoryViewModel by viewModels()
private val historyAdapter = AdventureHistoryAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
@@ -19,18 +21,12 @@ class AdventureHistoryActivity : AppCompatActivity() {
binding = ActivityAdventureHistoryBinding.inflate(layoutInflater)
setContentView(binding.root)
- initViewModel()
initRecyclerView()
viewModel.fetchHistories()
subscribe()
setClickListeners()
}
- private fun initViewModel() {
- viewModel = ViewModelProvider(this, AdventureHistoryViewModel.Factory)[AdventureHistoryViewModel::class.java]
- binding.lifecycleOwner = this
- }
-
private fun initRecyclerView() {
binding.rvAdventureHistoryVisitedPlaces.apply {
adapter = historyAdapter
diff --git a/android/app/src/main/java/com/now/naaga/presentation/adventurehistory/AdventureHistoryViewModel.kt b/android/app/src/main/java/com/now/naaga/presentation/adventurehistory/AdventureHistoryViewModel.kt
index 5fceb76c8..8a879729f 100644
--- a/android/app/src/main/java/com/now/naaga/presentation/adventurehistory/AdventureHistoryViewModel.kt
+++ b/android/app/src/main/java/com/now/naaga/presentation/adventurehistory/AdventureHistoryViewModel.kt
@@ -3,16 +3,20 @@ package com.now.naaga.presentation.adventurehistory
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
import com.now.domain.model.AdventureResult
import com.now.domain.model.OrderType
import com.now.domain.model.SortType
import com.now.domain.repository.AdventureRepository
-import com.now.naaga.data.repository.DefaultAdventureRepository
import com.now.naaga.data.throwable.DataThrowable
import com.now.naaga.data.throwable.DataThrowable.PlayerThrowable
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.launch
+import javax.inject.Inject
-class AdventureHistoryViewModel(private val adventureRepository: AdventureRepository) : ViewModel() {
+@HiltViewModel
+class AdventureHistoryViewModel @Inject constructor(private val adventureRepository: AdventureRepository) :
+ ViewModel() {
private val _adventureResults = MutableLiveData>()
val adventureResults: LiveData> = _adventureResults
@@ -20,28 +24,24 @@ class AdventureHistoryViewModel(private val adventureRepository: AdventureReposi
val errorMessage: LiveData = _errorMessage
fun fetchHistories() {
- adventureRepository.fetchMyAdventureResults(SortType.TIME, OrderType.DESCENDING) { result ->
- result
- .onSuccess { _adventureResults.value = it }
- .onFailure { setErrorMessage(it as DataThrowable) }
+ viewModelScope.launch {
+ runCatching {
+ adventureRepository.fetchMyAdventureResults(SortType.TIME, OrderType.DESCENDING)
+ }.onSuccess { results: List ->
+ _adventureResults.value = results
+ }.onFailure {
+ setErrorMessage(it as DataThrowable)
+ }
}
}
private fun setErrorMessage(throwable: DataThrowable) {
when (throwable) {
- is PlayerThrowable -> { _errorMessage.value = throwable.message }
- else -> {}
- }
- }
-
- companion object {
- val Factory = AdventureHistoryFactory(DefaultAdventureRepository())
-
- class AdventureHistoryFactory(private val adventureRepository: AdventureRepository) :
- ViewModelProvider.Factory {
- override fun create(modelClass: Class): T {
- return AdventureHistoryViewModel(adventureRepository) as T
+ is PlayerThrowable -> {
+ _errorMessage.value = throwable.message
}
+
+ else -> {}
}
}
}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/adventurehistory/recyclerview/AdventureHistoryViewHolder.kt b/android/app/src/main/java/com/now/naaga/presentation/adventurehistory/recyclerview/AdventureHistoryViewHolder.kt
index 8110faa47..5cc167318 100644
--- a/android/app/src/main/java/com/now/naaga/presentation/adventurehistory/recyclerview/AdventureHistoryViewHolder.kt
+++ b/android/app/src/main/java/com/now/naaga/presentation/adventurehistory/recyclerview/AdventureHistoryViewHolder.kt
@@ -22,8 +22,8 @@ class AdventureHistoryViewHolder(private val binding: ItemHistoryBinding) : Recy
private fun setSuccessOrFailure(resultType: AdventureResultType) {
when (resultType) {
- AdventureResultType.SUCCESS -> binding.ivAdventureHistoryStamp.setImageResource(R.drawable.ic_success)
- AdventureResultType.FAIL -> binding.ivAdventureHistoryStamp.setImageResource(R.drawable.ic_fail)
+ AdventureResultType.SUCCESS -> binding.ivAdventureHistoryStamp.setImageResource(R.drawable.ic_success_label)
+ AdventureResultType.FAIL -> binding.ivAdventureHistoryStamp.setImageResource(R.drawable.ic_fail_label)
AdventureResultType.NONE -> {}
}
}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/adventureresult/AdventureResultActivity.kt b/android/app/src/main/java/com/now/naaga/presentation/adventureresult/AdventureResultActivity.kt
index 258636ac0..05a0c8dd7 100644
--- a/android/app/src/main/java/com/now/naaga/presentation/adventureresult/AdventureResultActivity.kt
+++ b/android/app/src/main/java/com/now/naaga/presentation/adventureresult/AdventureResultActivity.kt
@@ -4,8 +4,8 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
+import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
-import androidx.lifecycle.ViewModelProvider
import com.bumptech.glide.Glide
import com.now.domain.model.AdventureResult
import com.now.domain.model.AdventureResultType
@@ -15,10 +15,12 @@ import com.now.naaga.data.firebase.analytics.AnalyticsDelegate
import com.now.naaga.data.firebase.analytics.DefaultAnalyticsDelegate
import com.now.naaga.data.firebase.analytics.RESULT_RESULT_RETURN
import com.now.naaga.databinding.ActivityAdventureResultBinding
+import dagger.hilt.android.AndroidEntryPoint
+@AndroidEntryPoint
class AdventureResultActivity : AppCompatActivity(), AnalyticsDelegate by DefaultAnalyticsDelegate() {
private lateinit var binding: ActivityAdventureResultBinding
- private lateinit var viewModel: AdventureResultViewModel
+ private val viewModel: AdventureResultViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -42,7 +44,6 @@ class AdventureResultActivity : AppCompatActivity(), AnalyticsDelegate by Defaul
}
private fun initViewModel() {
- viewModel = ViewModelProvider(this, AdventureResultViewModel.Factory)[AdventureResultViewModel::class.java]
binding.viewModel = viewModel
binding.lifecycleOwner = this
}
@@ -70,12 +71,12 @@ class AdventureResultActivity : AppCompatActivity(), AnalyticsDelegate by Defaul
}
private fun setSuccessTypeView(destinationName: String) {
- binding.ivAdventureResultStamp.setImageResource(R.drawable.ic_success)
+ binding.ivAdventureResultStamp.setImageResource(R.drawable.ic_success_label)
binding.tvAdventureResultDestination.text = destinationName
}
private fun setFailTypeView() {
- binding.ivAdventureResultStamp.setImageResource(R.drawable.ic_fail)
+ binding.ivAdventureResultStamp.setImageResource(R.drawable.ic_fail_label)
binding.tvAdventureResultDestination.text = getString(R.string.adventureResult_fail_destination_name)
}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/adventureresult/AdventureResultViewModel.kt b/android/app/src/main/java/com/now/naaga/presentation/adventureresult/AdventureResultViewModel.kt
index 0b469b41c..dadce03a4 100644
--- a/android/app/src/main/java/com/now/naaga/presentation/adventureresult/AdventureResultViewModel.kt
+++ b/android/app/src/main/java/com/now/naaga/presentation/adventureresult/AdventureResultViewModel.kt
@@ -3,16 +3,18 @@ package com.now.naaga.presentation.adventureresult
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
import com.now.domain.model.AdventureResult
import com.now.domain.repository.AdventureRepository
import com.now.domain.repository.RankRepository
-import com.now.naaga.data.repository.DefaultAdventureRepository
-import com.now.naaga.data.repository.DefaultRankRepository
import com.now.naaga.data.throwable.DataThrowable
import com.now.naaga.data.throwable.DataThrowable.GameThrowable
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.launch
+import javax.inject.Inject
-class AdventureResultViewModel(
+@HiltViewModel
+class AdventureResultViewModel @Inject constructor(
private val adventureRepository: AdventureRepository,
private val rankRepository: RankRepository,
) : ViewModel() {
@@ -27,44 +29,36 @@ class AdventureResultViewModel(
val throwable: LiveData = _throwable
fun fetchGameResult(adventureId: Long) {
- adventureRepository.fetchAdventureResult(
- adventureId,
- callback = { result ->
- result
- .onSuccess { adventureResult -> _adventureResult.value = adventureResult }
- .onFailure { setErrorMessage(it as DataThrowable) }
- },
- )
+ viewModelScope.launch {
+ runCatching {
+ adventureRepository.fetchAdventureResult(adventureId)
+ }.onSuccess { adventureResult ->
+ _adventureResult.value = adventureResult
+ }.onFailure {
+ setErrorMessage(it as DataThrowable)
+ }
+ }
}
fun fetchMyRank() {
- rankRepository.getMyRank(
- callback = { result ->
- result
- .onSuccess { rank -> _myRank.value = rank.rank }
- .onFailure { setErrorMessage(it as DataThrowable) }
- },
- )
+ viewModelScope.launch {
+ runCatching {
+ rankRepository.getMyRank()
+ }.onSuccess { rank ->
+ _myRank.value = rank.rank
+ }.onFailure {
+ setErrorMessage(it as DataThrowable)
+ }
+ }
}
private fun setErrorMessage(throwable: DataThrowable) {
when (throwable) {
- is GameThrowable -> { _throwable.value = throwable }
- else -> {}
- }
- }
-
- companion object {
- val Factory = AdventureResultFactory(DefaultAdventureRepository(), DefaultRankRepository())
-
- class AdventureResultFactory(
- private val adventureRepository: AdventureRepository,
- private val rankRepository: RankRepository,
- ) :
- ViewModelProvider.Factory {
- override fun create(modelClass: Class): T {
- return AdventureResultViewModel(adventureRepository, rankRepository) as T
+ is GameThrowable -> {
+ _throwable.value = throwable
}
+
+ else -> {}
}
}
}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/beginadventure/BeginAdventureActivity.kt b/android/app/src/main/java/com/now/naaga/presentation/beginadventure/BeginAdventureActivity.kt
index 6b42cffed..79f211617 100644
--- a/android/app/src/main/java/com/now/naaga/presentation/beginadventure/BeginAdventureActivity.kt
+++ b/android/app/src/main/java/com/now/naaga/presentation/beginadventure/BeginAdventureActivity.kt
@@ -10,26 +10,30 @@ import android.provider.Settings
import android.view.View
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
+import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
-import androidx.lifecycle.ViewModelProvider
import com.now.domain.model.AdventureStatus
import com.now.naaga.R
import com.now.naaga.data.firebase.analytics.AnalyticsDelegate
import com.now.naaga.data.firebase.analytics.BEGIN_BEGIN_ADVENTURE
import com.now.naaga.data.firebase.analytics.BEGIN_GO_MYPAGE
-import com.now.naaga.data.firebase.analytics.BEGIN_GO_RANK
+import com.now.naaga.data.firebase.analytics.BEGIN_GO_SETTING
import com.now.naaga.data.firebase.analytics.BEGIN_GO_UPLOAD
import com.now.naaga.data.firebase.analytics.DefaultAnalyticsDelegate
import com.now.naaga.data.throwable.DataThrowable
import com.now.naaga.databinding.ActivityBeginAdventureBinding
-import com.now.naaga.presentation.beginadventure.LocationPermissionDialog.Companion.TAG_LOCATION_DIALOG
+import com.now.naaga.presentation.common.dialog.DialogType
+import com.now.naaga.presentation.common.dialog.PermissionDialog
import com.now.naaga.presentation.mypage.MyPageActivity
import com.now.naaga.presentation.onadventure.OnAdventureActivity
-import com.now.naaga.presentation.rank.RankActivity
+import com.now.naaga.presentation.setting.SettingActivity
+import com.now.naaga.presentation.upload.UploadActivity
+import dagger.hilt.android.AndroidEntryPoint
+@AndroidEntryPoint
class BeginAdventureActivity : AppCompatActivity(), AnalyticsDelegate by DefaultAnalyticsDelegate() {
private lateinit var binding: ActivityBeginAdventureBinding
- private lateinit var viewModel: BeginAdventureViewModel
+ private val viewModel: BeginAdventureViewModel by viewModels()
private val onAdventureActivityLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { _ ->
@@ -62,7 +66,6 @@ class BeginAdventureActivity : AppCompatActivity(), AnalyticsDelegate by Default
startLoading()
registerAnalytics(this.lifecycle)
- initViewModel()
fetchInProgressAdventure()
requestLocationPermission()
setClickListeners()
@@ -72,10 +75,6 @@ class BeginAdventureActivity : AppCompatActivity(), AnalyticsDelegate by Default
binding.lifecycleOwner = this
}
- private fun initViewModel() {
- viewModel = ViewModelProvider(this, BeginAdventureViewModel.Factory)[BeginAdventureViewModel::class.java]
- }
-
private fun fetchInProgressAdventure() {
viewModel.fetchAdventure(AdventureStatus.IN_PROGRESS)
}
@@ -115,28 +114,27 @@ class BeginAdventureActivity : AppCompatActivity(), AnalyticsDelegate by Default
}
private fun setClickListeners() {
- binding.clBeginAdventureBegin.setOnClickListener {
+ binding.btnBeginAdventureBegin.setOnClickListener {
logClickEvent(getViewEntryName(it), BEGIN_BEGIN_ADVENTURE)
checkPermissionAndBeginAdventure()
}
- binding.ivBeginAdventureRank.setOnClickListener {
- logClickEvent(getViewEntryName(it), BEGIN_GO_RANK)
- startActivity(RankActivity.getIntent(this))
- }
- binding.ivBeginAdventureUpload.setOnClickListener {
+ binding.btnBeginAdventureUpload.setOnClickListener {
logClickEvent(getViewEntryName(it), BEGIN_GO_UPLOAD)
- Toast.makeText(this, getString(R.string.beginAdventure_features_not_ready), Toast.LENGTH_SHORT).show()
-// startActivity(UploadActivity.getIntent(this))
+ startActivity(UploadActivity.getIntent(this))
}
- binding.ivBeginAdventureMypage.setOnClickListener {
+ binding.btnBeginAdventureMyPage.setOnClickListener {
logClickEvent(getViewEntryName(it), BEGIN_GO_MYPAGE)
startActivity(MyPageActivity.getIntent(this))
}
+ binding.ivBeginAdventureSetting.setOnClickListener {
+ logClickEvent(getViewEntryName(it), BEGIN_GO_SETTING)
+ startActivity(SettingActivity.getIntent(this))
+ }
}
private fun checkPermissionAndBeginAdventure() {
if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_DENIED) {
- LocationPermissionDialog().show(supportFragmentManager, TAG_LOCATION_DIALOG)
+ PermissionDialog(DialogType.LOCATION).show(supportFragmentManager)
} else {
checkLocationPermissionInStatusBar()
}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/beginadventure/BeginAdventureViewModel.kt b/android/app/src/main/java/com/now/naaga/presentation/beginadventure/BeginAdventureViewModel.kt
index 1a43660f1..a1c45e2d3 100644
--- a/android/app/src/main/java/com/now/naaga/presentation/beginadventure/BeginAdventureViewModel.kt
+++ b/android/app/src/main/java/com/now/naaga/presentation/beginadventure/BeginAdventureViewModel.kt
@@ -3,14 +3,17 @@ package com.now.naaga.presentation.beginadventure
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
import com.now.domain.model.Adventure
import com.now.domain.model.AdventureStatus
import com.now.domain.repository.AdventureRepository
-import com.now.naaga.data.repository.DefaultAdventureRepository
import com.now.naaga.data.throwable.DataThrowable
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.launch
+import javax.inject.Inject
-class BeginAdventureViewModel(private val adventureRepository: AdventureRepository) : ViewModel() {
+@HiltViewModel
+class BeginAdventureViewModel @Inject constructor(private val adventureRepository: AdventureRepository) : ViewModel() {
private val _adventure = MutableLiveData()
val adventure: LiveData = _adventure
@@ -22,26 +25,14 @@ class BeginAdventureViewModel(private val adventureRepository: AdventureReposito
fun fetchAdventure(adventureStatus: AdventureStatus) {
_loading.value = true
- adventureRepository.fetchAdventureByStatus(adventureStatus) { result ->
- _loading.value = false
- result
- .onSuccess {
- _adventure.value = it.firstOrNull()
- }
- .onFailure {
- _error.value = it as DataThrowable
- }
- }
- }
-
- companion object {
- val Factory = BeginAdventureViewModelFactory(DefaultAdventureRepository())
-
- class BeginAdventureViewModelFactory(
- private val adventureRepository: AdventureRepository,
- ) : ViewModelProvider.Factory {
- override fun create(modelClass: Class): T {
- return BeginAdventureViewModel(adventureRepository) as T
+ viewModelScope.launch {
+ runCatching {
+ adventureRepository.fetchAdventureByStatus(adventureStatus)
+ }.onSuccess {
+ _loading.value = false
+ _adventure.value = it.firstOrNull()
+ }.onFailure {
+ _error.value = it as DataThrowable
}
}
}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/common/dialog/DialogType.kt b/android/app/src/main/java/com/now/naaga/presentation/common/dialog/DialogType.kt
new file mode 100644
index 000000000..5d5bdeb18
--- /dev/null
+++ b/android/app/src/main/java/com/now/naaga/presentation/common/dialog/DialogType.kt
@@ -0,0 +1,5 @@
+package com.now.naaga.presentation.common.dialog
+
+enum class DialogType {
+ LOCATION, CAMERA
+}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/onadventure/NaagaAlertDialog.kt b/android/app/src/main/java/com/now/naaga/presentation/common/dialog/NaagaAlertDialog.kt
similarity index 89%
rename from android/app/src/main/java/com/now/naaga/presentation/onadventure/NaagaAlertDialog.kt
rename to android/app/src/main/java/com/now/naaga/presentation/common/dialog/NaagaAlertDialog.kt
index 1169f4501..a83e31bf1 100644
--- a/android/app/src/main/java/com/now/naaga/presentation/onadventure/NaagaAlertDialog.kt
+++ b/android/app/src/main/java/com/now/naaga/presentation/common/dialog/NaagaAlertDialog.kt
@@ -1,4 +1,4 @@
-package com.now.naaga.presentation.onadventure
+package com.now.naaga.presentation.common.dialog
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
@@ -20,7 +20,7 @@ class NaagaAlertDialog private constructor() : DialogFragment() {
private lateinit var positiveAction: () -> Unit
private lateinit var negativeAction: () -> Unit
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = DialogNaagaAlertBinding.inflate(layoutInflater)
return binding.root
}
@@ -62,15 +62,8 @@ class NaagaAlertDialog private constructor() : DialogFragment() {
binding.tvAlertDialogNegative.text = negativeText
}
- fun setPositiveButton(action: () -> Unit) {
- positiveAction = action
- }
-
- fun setNegativeButton(action: () -> Unit) {
- negativeAction = action
- }
-
class Builder() {
+ private var isCancelable = true
fun build(
title: String,
@@ -87,8 +80,14 @@ class NaagaAlertDialog private constructor() : DialogFragment() {
this.negativeText = negativeText
this.positiveAction = positiveAction
this.negativeAction = negativeAction
+ this.isCancelable = this@Builder.isCancelable
}
}
+
+ fun setCancelable(isCancelable: Boolean): Builder {
+ this.isCancelable = isCancelable
+ return this
+ }
}
companion object {
diff --git a/android/app/src/main/java/com/now/naaga/presentation/beginadventure/LocationPermissionDialog.kt b/android/app/src/main/java/com/now/naaga/presentation/common/dialog/PermissionDialog.kt
similarity index 53%
rename from android/app/src/main/java/com/now/naaga/presentation/beginadventure/LocationPermissionDialog.kt
rename to android/app/src/main/java/com/now/naaga/presentation/common/dialog/PermissionDialog.kt
index f6ad86cbc..b541e51d7 100644
--- a/android/app/src/main/java/com/now/naaga/presentation/beginadventure/LocationPermissionDialog.kt
+++ b/android/app/src/main/java/com/now/naaga/presentation/common/dialog/PermissionDialog.kt
@@ -1,8 +1,7 @@
-package com.now.naaga.presentation.beginadventure
+package com.now.naaga.presentation.common.dialog
-import android.Manifest.permission.ACCESS_COARSE_LOCATION
+import android.annotation.SuppressLint
import android.content.Intent
-import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.net.Uri
@@ -11,29 +10,53 @@ import android.provider.Settings
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import androidx.core.content.ContextCompat.checkSelfPermission
+import androidx.appcompat.content.res.AppCompatResources
import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.FragmentManager
import com.now.naaga.R
import com.now.naaga.data.firebase.analytics.AnalyticsDelegate
+import com.now.naaga.data.firebase.analytics.CAMERA_PERMISSION_OPEN_SETTING
import com.now.naaga.data.firebase.analytics.DefaultAnalyticsDelegate
-import com.now.naaga.data.firebase.analytics.LOCATION_PERMISSION_OPEN_SETTING
-import com.now.naaga.databinding.DialogLocationPermissionBinding
+import com.now.naaga.databinding.DialogPermissionBinding
import com.now.naaga.util.dpToPx
import com.now.naaga.util.getWidthProportionalToDevice
-class LocationPermissionDialog : DialogFragment(), AnalyticsDelegate by DefaultAnalyticsDelegate() {
- private lateinit var binding: DialogLocationPermissionBinding
+class PermissionDialog(private val type: DialogType) :
+ DialogFragment(),
+ AnalyticsDelegate by DefaultAnalyticsDelegate() {
+ private lateinit var binding: DialogPermissionBinding
+ @SuppressLint("UseCompatLoadingForDrawables")
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
- binding = DialogLocationPermissionBinding.inflate(layoutInflater)
+ binding = DialogPermissionBinding.inflate(layoutInflater)
dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+ initView()
return binding.root
}
+ private fun initView() {
+ binding.btnDialogPermissionSetting.text = getString(R.string.permissionDialog_setting)
+ when (type) {
+ DialogType.LOCATION -> {
+ binding.ivDialogPermissionIcon.setImageDrawable(
+ AppCompatResources.getDrawable(requireContext(), R.drawable.ic_location_dialog),
+ )
+ binding.tvDialogPermissionDescription.text = getString(R.string.permissionDialog_location_description)
+ }
+
+ DialogType.CAMERA -> {
+ binding.ivDialogPermissionIcon.setImageDrawable(
+ AppCompatResources.getDrawable(requireContext(), R.drawable.ic_camera_dialog),
+ )
+ binding.tvDialogPermissionDescription.text = getString(R.string.permissionDialog_camera_description)
+ }
+ }
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
registerAnalytics(this.lifecycle)
@@ -41,10 +64,9 @@ class LocationPermissionDialog : DialogFragment(), AnalyticsDelegate by DefaultA
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- checkPermission()
setSize()
- binding.btnDialogLocationSetting.setOnClickListener {
- logClickEvent(requireContext().getViewEntryName(it), LOCATION_PERMISSION_OPEN_SETTING)
+ binding.btnDialogPermissionSetting.setOnClickListener {
+ logClickEvent(requireContext().getViewEntryName(it), CAMERA_PERMISSION_OPEN_SETTING)
openSetting()
dismiss()
}
@@ -64,27 +86,12 @@ class LocationPermissionDialog : DialogFragment(), AnalyticsDelegate by DefaultA
startActivity(appDetailsIntent)
}
- private fun checkPermission() {
- if (checkSelfPermission(requireContext(), ACCESS_COARSE_LOCATION) == PERMISSION_GRANTED) {
- setDescription(true)
- return
- }
- setDescription(false)
- }
-
- private fun setDescription(isApproximateAccessGranted: Boolean) {
- val description: String = if (isApproximateAccessGranted) {
- getString(R.string.locationDialog_approximate_description)
- } else {
- getString(R.string.locationDialog_description)
- }
-
- binding.tvDialogLocationDescription.text = description
+ fun show(manager: FragmentManager) {
+ show(manager, type.name)
}
companion object {
const val WIDTH_RATE = 0.83f
const val HEIGHT = 400
- const val TAG_LOCATION_DIALOG = "LOCATION"
}
}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/onadventure/PolaroidDialog.kt b/android/app/src/main/java/com/now/naaga/presentation/common/dialog/PolaroidDialog.kt
similarity index 97%
rename from android/app/src/main/java/com/now/naaga/presentation/onadventure/PolaroidDialog.kt
rename to android/app/src/main/java/com/now/naaga/presentation/common/dialog/PolaroidDialog.kt
index c781a384d..2a88ab1ad 100644
--- a/android/app/src/main/java/com/now/naaga/presentation/onadventure/PolaroidDialog.kt
+++ b/android/app/src/main/java/com/now/naaga/presentation/common/dialog/PolaroidDialog.kt
@@ -1,4 +1,4 @@
-package com.now.naaga.presentation.onadventure
+package com.now.naaga.presentation.common.dialog
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
diff --git a/android/app/src/main/java/com/now/naaga/presentation/login/LoginActivity.kt b/android/app/src/main/java/com/now/naaga/presentation/login/LoginActivity.kt
index e81fd810d..1e2f9f673 100644
--- a/android/app/src/main/java/com/now/naaga/presentation/login/LoginActivity.kt
+++ b/android/app/src/main/java/com/now/naaga/presentation/login/LoginActivity.kt
@@ -2,25 +2,25 @@ package com.now.naaga.presentation.login
import android.content.Context
import android.content.Intent
+import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.os.Bundle
import android.widget.Toast
+import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
-import androidx.core.view.WindowInsetsControllerCompat
-import androidx.lifecycle.ViewModelProvider
import com.now.domain.model.AuthPlatformType
-import com.now.naaga.NaagaApplication
-import com.now.naaga.R
import com.now.naaga.data.firebase.analytics.AnalyticsDelegate
import com.now.naaga.data.firebase.analytics.DefaultAnalyticsDelegate
import com.now.naaga.data.firebase.analytics.LOGIN_AUTH
-import com.now.naaga.data.repository.DefaultAuthRepository
import com.now.naaga.databinding.ActivityLoginBinding
import com.now.naaga.presentation.beginadventure.BeginAdventureActivity
import com.now.naaga.util.loginWithKakao
+import dagger.hilt.android.AndroidEntryPoint
+@AndroidEntryPoint
class LoginActivity : AppCompatActivity(), AnalyticsDelegate by DefaultAnalyticsDelegate() {
private lateinit var binding: ActivityLoginBinding
- private lateinit var viewModel: LoginViewModel
+ private val viewModel: LoginViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -28,15 +28,8 @@ class LoginActivity : AppCompatActivity(), AnalyticsDelegate by DefaultAnalytics
binding.lifecycleOwner = this
setContentView(binding.root)
registerAnalytics(this.lifecycle)
- initViewModel()
subscribe()
setClickListeners()
- setStatusBar()
- }
-
- private fun initViewModel() {
- val factory = LoginViewModelFactory(DefaultAuthRepository(NaagaApplication.authDataSource))
- viewModel = ViewModelProvider(this, factory)[LoginViewModel::class.java]
}
private fun subscribe() {
@@ -58,13 +51,6 @@ class LoginActivity : AppCompatActivity(), AnalyticsDelegate by DefaultAnalytics
}
}
- private fun setStatusBar() {
- window.apply {
- statusBarColor = getColor(R.color.white)
- WindowInsetsControllerCompat(this, this.decorView).isAppearanceLightStatusBars = true
- }
- }
-
private fun navigateHome() {
val intent = BeginAdventureActivity.getIntent(this)
startActivity(intent)
@@ -75,5 +61,12 @@ class LoginActivity : AppCompatActivity(), AnalyticsDelegate by DefaultAnalytics
fun getIntent(context: Context): Intent {
return Intent(context, LoginActivity::class.java)
}
+
+ fun getIntentWithTop(context: Context): Intent {
+ return Intent(context, LoginActivity::class.java).apply {
+ addFlags(FLAG_ACTIVITY_NEW_TASK)
+ addFlags(FLAG_ACTIVITY_CLEAR_TASK)
+ }
+ }
}
}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/login/LoginViewModel.kt b/android/app/src/main/java/com/now/naaga/presentation/login/LoginViewModel.kt
index 788c29670..0637fb344 100644
--- a/android/app/src/main/java/com/now/naaga/presentation/login/LoginViewModel.kt
+++ b/android/app/src/main/java/com/now/naaga/presentation/login/LoginViewModel.kt
@@ -3,12 +3,17 @@ package com.now.naaga.presentation.login
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
import com.now.domain.model.AuthPlatformType
import com.now.domain.model.PlatformAuth
import com.now.domain.repository.AuthRepository
import com.now.naaga.data.throwable.DataThrowable
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.launch
+import javax.inject.Inject
-class LoginViewModel(
+@HiltViewModel
+class LoginViewModel @Inject constructor(
private val authRepository: AuthRepository,
) : ViewModel() {
private val _isLoginSucceed = MutableLiveData()
@@ -18,10 +23,14 @@ class LoginViewModel(
val throwable: LiveData = _throwable
fun signIn(token: String, platformType: AuthPlatformType) {
- authRepository.getToken(PlatformAuth(token, platformType)) { result ->
- result
- .onSuccess { _isLoginSucceed.value = it }
- .onFailure { _throwable.value = it as DataThrowable }
+ viewModelScope.launch {
+ runCatching {
+ authRepository.getToken(PlatformAuth(token, platformType))
+ }.onSuccess { status ->
+ _isLoginSucceed.value = status
+ }.onFailure {
+ _throwable.value = it as DataThrowable
+ }
}
}
}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/login/LoginViewModelFactory.kt b/android/app/src/main/java/com/now/naaga/presentation/login/LoginViewModelFactory.kt
deleted file mode 100644
index 12b5a8f5b..000000000
--- a/android/app/src/main/java/com/now/naaga/presentation/login/LoginViewModelFactory.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.now.naaga.presentation.login
-
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import com.now.domain.repository.AuthRepository
-
-class LoginViewModelFactory(
- private val authRepository: AuthRepository,
-) :
- ViewModelProvider.Factory {
- override fun create(modelClass: Class): T {
- if (modelClass.isAssignableFrom(LoginViewModel::class.java)) {
- return LoginViewModel(authRepository) as T
- } else {
- throw IllegalArgumentException()
- }
- }
-}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/mypage/MyPageActivity.kt b/android/app/src/main/java/com/now/naaga/presentation/mypage/MyPageActivity.kt
index 355a02b41..392bff895 100644
--- a/android/app/src/main/java/com/now/naaga/presentation/mypage/MyPageActivity.kt
+++ b/android/app/src/main/java/com/now/naaga/presentation/mypage/MyPageActivity.kt
@@ -4,18 +4,23 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
+import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
-import androidx.lifecycle.ViewModelProvider
+import com.now.domain.model.Statistics
import com.now.naaga.data.firebase.analytics.AnalyticsDelegate
import com.now.naaga.data.firebase.analytics.DefaultAnalyticsDelegate
import com.now.naaga.data.firebase.analytics.MYPAGE_GO_RESULTS
import com.now.naaga.data.firebase.analytics.MY_PAGE_STATISTICS
import com.now.naaga.databinding.ActivityMyPageBinding
import com.now.naaga.presentation.adventurehistory.AdventureHistoryActivity
+import com.now.naaga.presentation.mypage.statistics.MyPageStatisticsAdapter
+import com.now.naaga.presentation.uimodel.model.StatisticsUiModel
+import dagger.hilt.android.AndroidEntryPoint
+@AndroidEntryPoint
class MyPageActivity : AppCompatActivity(), AnalyticsDelegate by DefaultAnalyticsDelegate() {
private lateinit var binding: ActivityMyPageBinding
- private lateinit var viewModel: MyPageViewModel
+ private val viewModel: MyPageViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -29,7 +34,6 @@ class MyPageActivity : AppCompatActivity(), AnalyticsDelegate by DefaultAnalytic
}
private fun initViewModel() {
- viewModel = ViewModelProvider(this, MyPageViewModel.Factory)[MyPageViewModel::class.java]
binding.viewModel = viewModel
binding.lifecycleOwner = this
}
@@ -38,7 +42,7 @@ class MyPageActivity : AppCompatActivity(), AnalyticsDelegate by DefaultAnalytic
binding.ivMypageBack.setOnClickListener {
finish()
}
- binding.btnMypageAdventureResults.setOnClickListener {
+ binding.btnMypageAdventureHistory.setOnClickListener {
logClickEvent(getViewEntryName(it), MYPAGE_GO_RESULTS)
val intent = AdventureHistoryActivity.getIntent(this)
startActivity(intent)
@@ -53,11 +57,11 @@ class MyPageActivity : AppCompatActivity(), AnalyticsDelegate by DefaultAnalytic
private fun subscribe() {
viewModel.statistics.observe(this) { statistics ->
- binding.customGridMypageStatistics.initContent(statistics.toUiModel(this))
+ initRecyclerView(statistics)
}
viewModel.places.observe(this) { places ->
- val placesUiModel = places.map { it.toUiModel() }
- binding.customGridMypagePlaces.initContent(placesUiModel)
+ val placesUiModels = places.map { it.toUiModel() }
+ binding.customGridMypagePlaces.initContent(placesUiModels)
}
viewModel.throwable.observe(this) { throwable ->
Toast.makeText(this, throwable.message, Toast.LENGTH_SHORT).show()
@@ -65,6 +69,16 @@ class MyPageActivity : AppCompatActivity(), AnalyticsDelegate by DefaultAnalytic
}
}
+ private fun initRecyclerView(statistics: Statistics) {
+ val statisticsUiModels = mutableListOf(
+ StatisticsUiModel.getSuccessAdventureStatisticsModel(statistics.successCount),
+ StatisticsUiModel.getFailAdventureStatisticsModel(statistics.failureCount),
+ StatisticsUiModel.getAllAdventureStatisticsModel(statistics.adventureCount),
+ )
+ val adapter = MyPageStatisticsAdapter(statisticsUiModels)
+ binding.rvMypageStatistics.adapter = adapter
+ }
+
companion object {
fun getIntent(context: Context): Intent {
return Intent(context, MyPageActivity::class.java)
diff --git a/android/app/src/main/java/com/now/naaga/presentation/mypage/MyPageAdapter.kt b/android/app/src/main/java/com/now/naaga/presentation/mypage/MyPageAdapter.kt
deleted file mode 100644
index 79672c0c8..000000000
--- a/android/app/src/main/java/com/now/naaga/presentation/mypage/MyPageAdapter.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.now.naaga.presentation.mypage
-
-import android.view.ViewGroup
-import androidx.recyclerview.widget.RecyclerView
-
-class MyPageAdapter(
- private val contents: List,
-) : RecyclerView.Adapter() {
-
- override fun getItemCount(): Int {
- return contents.size
- }
-
- override fun getItemViewType(position: Int): Int {
- return when (contents[position].viewType) {
- MyPageViewType.PLACES -> MyPageViewType.PLACES.ordinal
- MyPageViewType.STATISTICS -> MyPageViewType.STATISTICS.ordinal
- }
- }
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
- return when (viewType) {
- MyPageViewType.PLACES.ordinal -> MyPagePlaceViewHolder(MyPagePlaceViewHolder.getView(parent))
- MyPageViewType.STATISTICS.ordinal -> MyPageStatisticsViewHolder(MyPageStatisticsViewHolder.getView(parent))
- else -> throw IllegalArgumentException()
- }
- }
-
- override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
- when (holder) {
- is MyPagePlaceViewHolder -> holder.bind(contents[position] as MyPagePlaceUiModel)
- is MyPageStatisticsViewHolder -> holder.bind(contents[position] as MyPageStatisticsUiModel)
- }
- }
-}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/mypage/MyPageCustomGrid.kt b/android/app/src/main/java/com/now/naaga/presentation/mypage/MyPageCustomGrid.kt
deleted file mode 100644
index 620920495..000000000
--- a/android/app/src/main/java/com/now/naaga/presentation/mypage/MyPageCustomGrid.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-package com.now.naaga.presentation.mypage
-
-import android.content.Context
-import android.util.AttributeSet
-import android.view.LayoutInflater
-import androidx.constraintlayout.widget.ConstraintLayout
-import com.now.naaga.R
-import com.now.naaga.databinding.CustomMypageGridBinding
-import com.now.naaga.databinding.CustomMypageGridEmptyBinding
-
-class MyPageCustomGrid(context: Context, attrs: AttributeSet? = null) : ConstraintLayout(context, attrs) {
- private lateinit var layoutInflater: LayoutInflater
- private lateinit var notEmptybinding: CustomMypageGridBinding
- private lateinit var emptyBinding: CustomMypageGridEmptyBinding
- private lateinit var adapter: MyPageAdapter
-
- private fun initNotEmptyView() {
- layoutInflater = LayoutInflater.from(context)
- notEmptybinding = CustomMypageGridBinding.inflate(layoutInflater, this, true)
- }
-
- private fun initEmptyView() {
- layoutInflater = LayoutInflater.from(context)
- emptyBinding = CustomMypageGridEmptyBinding.inflate(layoutInflater, this, true)
- }
-
- fun initContent(data: List) {
- if (data.isNotEmpty()) {
- initNotEmptyView()
- makeAdapter(data)
- notEmptybinding.tvMypageItemTitle.text = data.first().viewType.text
- notEmptybinding.rvMypageItemContent.adapter = adapter
- } else {
- initEmptyView()
- emptyBinding.tvMypageEmptyItemTitle.text = context.getString(R.string.mypage_empty_item_title)
- emptyBinding.tvMypageEmptyDescription.text = context.getString(R.string.mypage_empty_description)
- }
- }
-
- private fun makeAdapter(data: List) {
- adapter = if (data.first().viewType == MyPageViewType.PLACES) {
- val lastIndex = if (data.size > END_PLACE_INDEX) END_PLACE_INDEX else data.size
- MyPageAdapter(data.subList(START_PLACE_INDEX, lastIndex))
- } else {
- MyPageAdapter(data)
- }
- }
-
- companion object {
- const val START_PLACE_INDEX = 0
- const val END_PLACE_INDEX = 3
- }
-}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/mypage/MyPageItemUiModel.kt b/android/app/src/main/java/com/now/naaga/presentation/mypage/MyPageItemUiModel.kt
deleted file mode 100644
index 02f2c1430..000000000
--- a/android/app/src/main/java/com/now/naaga/presentation/mypage/MyPageItemUiModel.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.now.naaga.presentation.mypage
-
-abstract class MyPageItemUiModel(val viewType: MyPageViewType)
-
-data class MyPagePlaceUiModel(
- val image: String,
- val description: String,
-) : MyPageItemUiModel(MyPageViewType.PLACES)
-
-data class MyPageStatisticsUiModel(
- val value: Int,
- val unit: String,
- val description: String,
-) : MyPageItemUiModel(MyPageViewType.STATISTICS)
-
-enum class MyPageViewType(val text: String) {
- STATISTICS("Statistics"),
- PLACES("My Places"),
-}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/mypage/MyPageMapper.kt b/android/app/src/main/java/com/now/naaga/presentation/mypage/MyPageMapper.kt
index afc20423d..7910a3aeb 100644
--- a/android/app/src/main/java/com/now/naaga/presentation/mypage/MyPageMapper.kt
+++ b/android/app/src/main/java/com/now/naaga/presentation/mypage/MyPageMapper.kt
@@ -1,44 +1,7 @@
package com.now.naaga.presentation.mypage
-import android.content.Context
import com.now.domain.model.Place
-import com.now.domain.model.Statistics
-import com.now.naaga.R
-
-fun Statistics.toUiModel(context: Context): List {
- return listOf(
- MyPageStatisticsUiModel(
- adventureCount,
- context.getString(R.string.mypage_count),
- context.getString(R.string.mypage_total_adventure),
- ),
- MyPageStatisticsUiModel(
- successCount,
- context.getString(R.string.mypage_count),
- context.getString(R.string.mypage_adventure_success),
- ),
- MyPageStatisticsUiModel(
- failureCount,
- context.getString(R.string.mypage_count),
- context.getString(R.string.mypage_adventure_failure),
- ),
- MyPageStatisticsUiModel(
- totalPlayTime,
- context.getString(R.string.mypage_minute),
- context.getString(R.string.mypage_total_play_time),
- ),
- MyPageStatisticsUiModel(
- totalDistance,
- context.getString(R.string.mypage_meter),
- context.getString(R.string.mypage_total_adventure_distance),
- ),
- MyPageStatisticsUiModel(
- totalHintUses,
- context.getString(R.string.mypage_number),
- context.getString(R.string.mypage_total_hint_uses),
- ),
- )
-}
+import com.now.naaga.presentation.uimodel.model.MyPagePlaceUiModel
fun Place.toUiModel(): MyPagePlaceUiModel {
return MyPagePlaceUiModel(
diff --git a/android/app/src/main/java/com/now/naaga/presentation/mypage/MyPageViewHolder.kt b/android/app/src/main/java/com/now/naaga/presentation/mypage/MyPageViewHolder.kt
deleted file mode 100644
index 69bf03b26..000000000
--- a/android/app/src/main/java/com/now/naaga/presentation/mypage/MyPageViewHolder.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-package com.now.naaga.presentation.mypage
-
-import android.view.LayoutInflater
-import android.view.ViewGroup
-import androidx.recyclerview.widget.RecyclerView
-import com.bumptech.glide.Glide
-import com.now.naaga.databinding.RvMypageItemPlaceBinding
-import com.now.naaga.databinding.RvMypageItemStatisticsBinding
-
-class MyPagePlaceViewHolder(private val binding: RvMypageItemPlaceBinding) : RecyclerView.ViewHolder(binding.root) {
- fun bind(data: MyPagePlaceUiModel) {
- binding.model = data
- Glide.with(binding.ivMypageItem).load(data.image).into(binding.ivMypageItem)
- binding.ivMypageItem.clipToOutline = true
- }
-
- companion object {
- fun getView(parent: ViewGroup): RvMypageItemPlaceBinding {
- val layoutInflater: LayoutInflater = LayoutInflater.from(parent.context)
- return RvMypageItemPlaceBinding.inflate(layoutInflater, parent, false)
- }
- }
-}
-
-class MyPageStatisticsViewHolder(private val binding: RvMypageItemStatisticsBinding) :
- RecyclerView.ViewHolder(binding.root) {
- fun bind(data: MyPageStatisticsUiModel) {
- binding.model = data
-
- // 숫자가 1자리일 때 36, 2자리일 때 32 ... 이렇게 4단위로 내려가도록 계산함.
- // 단 10자리 부터는 0혹은 음수가 나올 수 있음
- val fontSize = (10 - (data.value / 10)) * 4
- binding.tvMypageItemValue.textSize = fontSize.toFloat()
- }
-
- companion object {
- fun getView(parent: ViewGroup): RvMypageItemStatisticsBinding {
- val layoutInflater: LayoutInflater = LayoutInflater.from(parent.context)
- return RvMypageItemStatisticsBinding.inflate(layoutInflater, parent, false)
- }
- }
-}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/mypage/MyPageViewModel.kt b/android/app/src/main/java/com/now/naaga/presentation/mypage/MyPageViewModel.kt
index 12cabaa4c..c7b8979a9 100644
--- a/android/app/src/main/java/com/now/naaga/presentation/mypage/MyPageViewModel.kt
+++ b/android/app/src/main/java/com/now/naaga/presentation/mypage/MyPageViewModel.kt
@@ -3,7 +3,7 @@ package com.now.naaga.presentation.mypage
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
import com.now.domain.model.OrderType
import com.now.domain.model.Place
import com.now.domain.model.Rank
@@ -12,14 +12,15 @@ import com.now.domain.model.Statistics
import com.now.domain.repository.PlaceRepository
import com.now.domain.repository.RankRepository
import com.now.domain.repository.StatisticsRepository
-import com.now.naaga.data.repository.DefaultPlaceRepository
-import com.now.naaga.data.repository.DefaultRankRepository
-import com.now.naaga.data.repository.DefaultStatisticsRepository
import com.now.naaga.data.throwable.DataThrowable
import com.now.naaga.data.throwable.DataThrowable.PlaceThrowable
import com.now.naaga.data.throwable.DataThrowable.PlayerThrowable
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.launch
+import javax.inject.Inject
-class MyPageViewModel(
+@HiltViewModel
+class MyPageViewModel @Inject constructor(
private val rankRepository: RankRepository,
private val statisticsRepository: StatisticsRepository,
private val placeRepository: PlaceRepository,
@@ -37,48 +38,52 @@ class MyPageViewModel(
val throwable: LiveData = _throwable
fun fetchRank() {
- rankRepository.getMyRank { result: Result ->
- result
- .onSuccess { rank -> _rank.value = rank }
- .onFailure { setErrorMessage(it as DataThrowable) }
+ viewModelScope.launch {
+ runCatching {
+ rankRepository.getMyRank()
+ }.onSuccess { rank ->
+ _rank.value = rank
+ }.onFailure {
+ setErrorMessage(it as DataThrowable)
+ }
}
}
fun fetchStatistics() {
- statisticsRepository.getMyStatistics { result: Result ->
- result
- .onSuccess { statistics -> _statistics.value = statistics }
- .onFailure { setErrorMessage(it as DataThrowable) }
+ viewModelScope.launch {
+ runCatching {
+ statisticsRepository.getMyStatistics()
+ }.onSuccess { statistics ->
+ _statistics.value = statistics
+ }.onFailure {
+ setErrorMessage(it as DataThrowable)
+ }
}
}
fun fetchPlaces() {
- placeRepository.fetchMyPlaces(SortType.TIME.name, OrderType.DESCENDING.name) { result ->
- result
- .onSuccess { places -> _places.value = places }
- .onFailure { setErrorMessage(it as DataThrowable) }
+ viewModelScope.launch {
+ runCatching {
+ placeRepository.fetchMyPlaces(SortType.TIME.name, OrderType.DESCENDING.name)
+ }.onSuccess { places ->
+ _places.value = places
+ }.onFailure {
+ setErrorMessage(it as DataThrowable)
+ }
}
}
private fun setErrorMessage(throwable: Throwable) {
when (throwable) {
- is PlayerThrowable -> { _throwable.value = throwable }
- is PlaceThrowable -> { _throwable.value = throwable }
- else -> {}
- }
- }
-
- companion object {
- val Factory = MyPageFactory(DefaultRankRepository(), DefaultStatisticsRepository(), DefaultPlaceRepository())
+ is PlayerThrowable -> {
+ _throwable.value = throwable
+ }
- class MyPageFactory(
- private val rankRepository: RankRepository,
- private val statisticsRepository: StatisticsRepository,
- private val placeRepository: PlaceRepository,
- ) : ViewModelProvider.Factory {
- override fun create(modelClass: Class): T {
- return MyPageViewModel(rankRepository, statisticsRepository, placeRepository) as T
+ is PlaceThrowable -> {
+ _throwable.value = throwable
}
+
+ else -> {}
}
}
}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/mypage/place/MyPageCustomGrid.kt b/android/app/src/main/java/com/now/naaga/presentation/mypage/place/MyPageCustomGrid.kt
new file mode 100644
index 000000000..1b96fbfb6
--- /dev/null
+++ b/android/app/src/main/java/com/now/naaga/presentation/mypage/place/MyPageCustomGrid.kt
@@ -0,0 +1,34 @@
+package com.now.naaga.presentation.mypage.place
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import androidx.constraintlayout.widget.ConstraintLayout
+import com.now.naaga.databinding.CustomMypageGridBinding
+import com.now.naaga.databinding.CustomMypageGridEmptyBinding
+import com.now.naaga.presentation.uimodel.model.MyPagePlaceUiModel
+
+class MyPageCustomGrid(context: Context, attrs: AttributeSet? = null) : ConstraintLayout(context, attrs) {
+ private lateinit var layoutInflater: LayoutInflater
+ private lateinit var placeBinding: CustomMypageGridBinding
+ private lateinit var emptyBinding: CustomMypageGridEmptyBinding
+
+ private fun initPlaceBinding() {
+ layoutInflater = LayoutInflater.from(context)
+ placeBinding = CustomMypageGridBinding.inflate(layoutInflater, this, true)
+ }
+
+ private fun initEmptyBinding() {
+ layoutInflater = LayoutInflater.from(context)
+ emptyBinding = CustomMypageGridEmptyBinding.inflate(layoutInflater, this, true)
+ }
+
+ fun initContent(data: List) {
+ if (data.isNotEmpty()) {
+ initPlaceBinding()
+ placeBinding.rvMypageItemContent.adapter = MyPagePlaceAdapter(data)
+ } else {
+ initEmptyBinding()
+ }
+ }
+}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/mypage/place/MyPagePlaceAdapter.kt b/android/app/src/main/java/com/now/naaga/presentation/mypage/place/MyPagePlaceAdapter.kt
new file mode 100644
index 000000000..1721e3bec
--- /dev/null
+++ b/android/app/src/main/java/com/now/naaga/presentation/mypage/place/MyPagePlaceAdapter.kt
@@ -0,0 +1,22 @@
+package com.now.naaga.presentation.mypage.place
+
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import com.now.naaga.presentation.uimodel.model.MyPagePlaceUiModel
+
+class MyPagePlaceAdapter(
+ private val contents: List,
+) : RecyclerView.Adapter() {
+
+ override fun getItemCount(): Int {
+ return contents.size
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyPagePlaceViewHolder {
+ return MyPagePlaceViewHolder(parent)
+ }
+
+ override fun onBindViewHolder(holder: MyPagePlaceViewHolder, position: Int) {
+ holder.bind(contents[position])
+ }
+}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/mypage/place/MyPagePlaceViewHolder.kt b/android/app/src/main/java/com/now/naaga/presentation/mypage/place/MyPagePlaceViewHolder.kt
new file mode 100644
index 000000000..8a7af3d24
--- /dev/null
+++ b/android/app/src/main/java/com/now/naaga/presentation/mypage/place/MyPagePlaceViewHolder.kt
@@ -0,0 +1,27 @@
+package com.now.naaga.presentation.mypage.place
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import com.now.naaga.R
+import com.now.naaga.databinding.RvMypageItemPlaceBinding
+import com.now.naaga.presentation.uimodel.model.MyPagePlaceUiModel
+
+class MyPagePlaceViewHolder(parent: ViewGroup) : RecyclerView.ViewHolder(
+ LayoutInflater.from(parent.context).inflate(
+ R.layout.rv_mypage_item_place,
+ parent,
+ false,
+ ),
+) {
+ private val binding = RvMypageItemPlaceBinding.bind(itemView)
+
+ fun bind(data: MyPagePlaceUiModel) {
+ binding.apply {
+ model = data
+ Glide.with(ivMypageItem).load(data.image).into(ivMypageItem)
+ ivMypageItem.clipToOutline = true
+ }
+ }
+}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/mypage/statistics/MyPageStatisticsAdapter.kt b/android/app/src/main/java/com/now/naaga/presentation/mypage/statistics/MyPageStatisticsAdapter.kt
new file mode 100644
index 000000000..b7260a77e
--- /dev/null
+++ b/android/app/src/main/java/com/now/naaga/presentation/mypage/statistics/MyPageStatisticsAdapter.kt
@@ -0,0 +1,20 @@
+package com.now.naaga.presentation.mypage.statistics
+
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import com.now.naaga.presentation.uimodel.model.StatisticsUiModel
+
+class MyPageStatisticsAdapter(private val statisticsUiModels: List) :
+ RecyclerView.Adapter() {
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyPageStatisticsViewHolder {
+ return MyPageStatisticsViewHolder(parent)
+ }
+
+ override fun onBindViewHolder(holder: MyPageStatisticsViewHolder, position: Int) {
+ holder.bind(statisticsUiModels[position])
+ }
+
+ override fun getItemCount(): Int {
+ return statisticsUiModels.size
+ }
+}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/mypage/statistics/MyPageStatisticsViewHolder.kt b/android/app/src/main/java/com/now/naaga/presentation/mypage/statistics/MyPageStatisticsViewHolder.kt
new file mode 100644
index 000000000..20669b013
--- /dev/null
+++ b/android/app/src/main/java/com/now/naaga/presentation/mypage/statistics/MyPageStatisticsViewHolder.kt
@@ -0,0 +1,27 @@
+package com.now.naaga.presentation.mypage.statistics
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.appcompat.content.res.AppCompatResources
+import androidx.recyclerview.widget.RecyclerView
+import com.now.naaga.R
+import com.now.naaga.databinding.RvMypageItemAdventureBinding
+import com.now.naaga.presentation.uimodel.model.StatisticsUiModel
+
+class MyPageStatisticsViewHolder(parent: ViewGroup) : RecyclerView.ViewHolder(
+ LayoutInflater.from(parent.context).inflate(R.layout.rv_mypage_item_adventure, parent, false),
+) {
+ private val binding = RvMypageItemAdventureBinding.bind(itemView)
+
+ fun bind(statisticsUiModels: StatisticsUiModel) {
+ binding.apply {
+ ivMypageItemIcon.setImageDrawable(
+ AppCompatResources.getDrawable(itemView.context, statisticsUiModels.icon),
+ )
+ tvMypageItemAdventureCount.text = statisticsUiModels.count.toString()
+ tvMypageItemAdventureTitle.text = statisticsUiModels.detail
+ vMypageItemIconBackground.background =
+ AppCompatResources.getDrawable(itemView.context, statisticsUiModels.background)
+ }
+ }
+}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/onadventure/OnAdventureActivity.kt b/android/app/src/main/java/com/now/naaga/presentation/onadventure/OnAdventureActivity.kt
index 94d139bb9..831188b5c 100644
--- a/android/app/src/main/java/com/now/naaga/presentation/onadventure/OnAdventureActivity.kt
+++ b/android/app/src/main/java/com/now/naaga/presentation/onadventure/OnAdventureActivity.kt
@@ -6,10 +6,10 @@ import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
+import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
-import androidx.lifecycle.ViewModelProvider
import com.google.android.material.snackbar.Snackbar
import com.now.domain.model.Adventure
import com.now.domain.model.AdventureStatus
@@ -26,17 +26,21 @@ import com.now.naaga.data.firebase.analytics.ON_ADVENTURE_SHOW_POLAROID
import com.now.naaga.data.throwable.DataThrowable
import com.now.naaga.databinding.ActivityOnAdventureBinding
import com.now.naaga.presentation.adventureresult.AdventureResultActivity
+import com.now.naaga.presentation.common.dialog.NaagaAlertDialog
+import com.now.naaga.presentation.common.dialog.PolaroidDialog
import com.now.naaga.presentation.uimodel.mapper.toDomain
import com.now.naaga.presentation.uimodel.mapper.toUi
import com.now.naaga.presentation.uimodel.model.AdventureUiModel
-import com.now.naaga.util.getParcelableCompat
+import com.now.naaga.util.extension.getParcelableCompat
+import dagger.hilt.android.AndroidEntryPoint
+@AndroidEntryPoint
class OnAdventureActivity :
AppCompatActivity(),
NaverMapSettingDelegate by DefaultNaverMapSettingDelegate(),
AnalyticsDelegate by DefaultAnalyticsDelegate() {
private lateinit var binding: ActivityOnAdventureBinding
- private lateinit var viewModel: OnAdventureViewModel
+ private val viewModel: OnAdventureViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
setNaverMap(this, R.id.fcv_onAdventure_map)
@@ -69,15 +73,15 @@ class OnAdventureActivity :
}
private fun setClickListeners() {
- binding.ivOnAdventureGiveUp.setOnClickListener {
+ binding.clOnAdventureStop.setOnClickListener {
logClickEvent(getViewEntryName(it), ON_ADVENTURE_SHOW_GIVE_UP)
showGiveUpDialog()
}
- binding.ivOnAdventurePhoto.setOnClickListener {
+ binding.clOnAdventureShowPhoto.setOnClickListener {
logClickEvent(getViewEntryName(it), ON_ADVENTURE_SHOW_POLAROID)
showPolaroidDialog()
}
- binding.ivOnAdventureHint.setOnClickListener {
+ binding.clOnAdventureSearchDirection.setOnClickListener {
logClickEvent(getViewEntryName(it), ON_ADVENTURE_SHOW_HINT)
showHintDialog()
}
@@ -97,12 +101,13 @@ class OnAdventureActivity :
viewModel.hints.observe(this) { hints ->
drawHintMarkers(hints)
binding.lottieOnAdventureLoading.visibility = View.GONE
+ showPolaroidDialog()
}
viewModel.lastHint.observe(this) {
drawHintMarkers(listOf(it))
}
viewModel.remainingHintCount.observe(this) {
- binding.tvOnAdventureHintCount.text = it.toString()
+ // binding.tvOnAdventureHintCount.text = it.toString()
}
viewModel.error.observe(this) { error: DataThrowable ->
logServerError(ON_ADVENTURE_GAME, error.code, error.message.toString())
@@ -124,7 +129,6 @@ class OnAdventureActivity :
}
private fun initViewModel() {
- viewModel = ViewModelProvider(this, OnAdventureViewModel.Factory)[OnAdventureViewModel::class.java]
binding.viewModel = viewModel
binding.lifecycleOwner = this
}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/onadventure/OnAdventureViewModel.kt b/android/app/src/main/java/com/now/naaga/presentation/onadventure/OnAdventureViewModel.kt
index 66ce642ed..3119d6753 100644
--- a/android/app/src/main/java/com/now/naaga/presentation/onadventure/OnAdventureViewModel.kt
+++ b/android/app/src/main/java/com/now/naaga/presentation/onadventure/OnAdventureViewModel.kt
@@ -3,8 +3,8 @@ package com.now.naaga.presentation.onadventure
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.map
+import androidx.lifecycle.viewModelScope
import com.now.domain.model.Adventure
import com.now.domain.model.AdventureEndType
import com.now.domain.model.AdventureStatus
@@ -12,13 +12,16 @@ import com.now.domain.model.Coordinate
import com.now.domain.model.Hint
import com.now.domain.model.RemainingTryCount
import com.now.domain.repository.AdventureRepository
-import com.now.naaga.data.repository.DefaultAdventureRepository
import com.now.naaga.data.throwable.DataThrowable
import com.now.naaga.data.throwable.DataThrowable.Companion.hintThrowable
import com.now.naaga.data.throwable.DataThrowable.GameThrowable
import com.now.naaga.data.throwable.DataThrowable.UniversalThrowable
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.launch
+import javax.inject.Inject
-class OnAdventureViewModel(private val adventureRepository: AdventureRepository) : ViewModel() {
+@HiltViewModel
+class OnAdventureViewModel @Inject constructor(private val adventureRepository: AdventureRepository) : ViewModel() {
private val _adventure = MutableLiveData()
val adventure: LiveData = _adventure
val hints = DisposableLiveData>(_adventure.map { it.hints })
@@ -41,22 +44,30 @@ class OnAdventureViewModel(private val adventureRepository: AdventureRepository)
}
fun beginAdventure(currentCoordinate: Coordinate) {
- adventureRepository.beginAdventure(currentCoordinate) { result: Result ->
- result
- .onSuccess { setAdventure(it) }
- .onFailure { setError(it as DataThrowable) }
+ viewModelScope.launch {
+ runCatching {
+ adventureRepository.beginAdventure(currentCoordinate)
+ }.onSuccess {
+ setAdventure(it)
+ }.onFailure {
+ setError(it as DataThrowable)
+ }
}
}
fun giveUpAdventure() {
- adventureRepository.endGame(
- adventureId = adventure.value?.id ?: return,
- endType = AdventureEndType.GIVE_UP,
- coordinate = myCoordinate.value ?: return,
- ) { result: Result ->
- result
- .onSuccess { _adventure.value = adventure.value?.copy(adventureStatus = it) }
- .onFailure { setError(it as DataThrowable) }
+ viewModelScope.launch {
+ runCatching {
+ adventureRepository.endGame(
+ adventureId = adventure.value?.id ?: throw IllegalStateException(ADVENTURE_IS_NULL),
+ endType = AdventureEndType.GIVE_UP,
+ coordinate = myCoordinate.value ?: throw IllegalStateException(MY_COORDINATE_IS_NULL),
+ )
+ }.onSuccess { status: AdventureStatus ->
+ _adventure.value = adventure.value?.copy(adventureStatus = status)
+ }.onFailure {
+ setError(it as DataThrowable)
+ }
}
}
@@ -65,16 +76,18 @@ class OnAdventureViewModel(private val adventureRepository: AdventureRepository)
setError(hintThrowable)
return
}
- adventureRepository.makeHint(
- adventureId = adventure.value?.id ?: return,
- coordinate = myCoordinate.value ?: return,
- ) { result: Result ->
- result
- .onSuccess { hint ->
- _adventure.value = adventure.value?.copy(hints = ((adventure.value?.hints ?: listOf()) + hint))
- _lastHint.value = hint
- }
- .onFailure { setError(it as DataThrowable) }
+ viewModelScope.launch {
+ runCatching {
+ adventureRepository.makeHint(
+ adventureId = adventure.value?.id ?: throw IllegalStateException(ADVENTURE_IS_NULL),
+ coordinate = myCoordinate.value ?: throw IllegalStateException(MY_COORDINATE_IS_NULL),
+ )
+ }.onSuccess { hint: Hint ->
+ _adventure.value = adventure.value?.copy(hints = ((adventure.value?.hints ?: listOf()) + hint))
+ _lastHint.value = hint
+ }.onFailure {
+ setError(it as DataThrowable)
+ }
}
}
@@ -88,25 +101,25 @@ class OnAdventureViewModel(private val adventureRepository: AdventureRepository)
}
fun endAdventure() {
- adventureRepository.endGame(
- adventureId = adventure.value?.id ?: return,
- endType = AdventureEndType.ARRIVED,
- coordinate = myCoordinate.value ?: return,
- ) { result: Result ->
- result
- .onSuccess { _adventure.value = adventure.value?.copy(adventureStatus = it) }
- .onFailure {
- when ((it as DataThrowable).code) {
- NOT_ARRIVED -> {
- val currentRemainingTryCount = adventure.value?.remainingTryCount ?: return@onFailure
- _adventure.value = adventure.value?.copy(remainingTryCount = currentRemainingTryCount - 1)
- }
-
- TRY_COUNT_OVER ->
- _adventure.value = adventure.value?.copy(adventureStatus = AdventureStatus.DONE)
+ viewModelScope.launch {
+ runCatching {
+ adventureRepository.endGame(
+ adventureId = adventure.value?.id ?: throw IllegalStateException(ADVENTURE_IS_NULL),
+ endType = AdventureEndType.ARRIVED,
+ coordinate = myCoordinate.value ?: throw IllegalStateException(MY_COORDINATE_IS_NULL),
+ )
+ }.onSuccess {
+ _adventure.value = adventure.value?.copy(adventureStatus = it)
+ }.onFailure {
+ when ((it as DataThrowable).code) {
+ TRY_COUNT_OVER -> _adventure.value = adventure.value?.copy(adventureStatus = AdventureStatus.DONE)
+ NOT_ARRIVED -> {
+ val currentRemainingTryCount = adventure.value?.remainingTryCount ?: return@onFailure
+ _adventure.value = adventure.value?.copy(remainingTryCount = currentRemainingTryCount - 1)
}
- setError(it)
}
+ setError(it)
+ }
}
}
@@ -123,12 +136,8 @@ class OnAdventureViewModel(private val adventureRepository: AdventureRepository)
const val NO_DESTINATION = 406
const val NOT_ARRIVED = 415
const val TRY_COUNT_OVER = 416
- val Factory = ViewModelFactory(DefaultAdventureRepository())
-
- class ViewModelFactory(private val adventureRepository: AdventureRepository) : ViewModelProvider.Factory {
- override fun create(modelClass: Class): T {
- return OnAdventureViewModel(adventureRepository) as T
- }
- }
+ private const val ERROR_PREFIX = "[ERROR] OnAdventureViewModel:"
+ private const val ADVENTURE_IS_NULL = "$ERROR_PREFIX adventure가 널입니다."
+ private const val MY_COORDINATE_IS_NULL = "$ERROR_PREFIX myCoordinate가 널입니다."
}
}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/rank/RankActivity.kt b/android/app/src/main/java/com/now/naaga/presentation/rank/RankActivity.kt
index b0b1ab43d..ad41103b9 100644
--- a/android/app/src/main/java/com/now/naaga/presentation/rank/RankActivity.kt
+++ b/android/app/src/main/java/com/now/naaga/presentation/rank/RankActivity.kt
@@ -4,18 +4,20 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
+import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
-import androidx.lifecycle.ViewModelProvider
import com.now.domain.model.Rank
import com.now.naaga.data.firebase.analytics.AnalyticsDelegate
import com.now.naaga.data.firebase.analytics.DefaultAnalyticsDelegate
import com.now.naaga.data.firebase.analytics.RANK_RANK
import com.now.naaga.databinding.ActivityRankBinding
import com.now.naaga.presentation.rank.recyclerview.RankAdapter
+import dagger.hilt.android.AndroidEntryPoint
+@AndroidEntryPoint
class RankActivity : AppCompatActivity(), AnalyticsDelegate by DefaultAnalyticsDelegate() {
private lateinit var binding: ActivityRankBinding
- private lateinit var viewModel: RankViewModel
+ private val viewModel: RankViewModel by viewModels()
private val rankAdapter = RankAdapter()
@@ -33,7 +35,6 @@ class RankActivity : AppCompatActivity(), AnalyticsDelegate by DefaultAnalyticsD
}
private fun initViewModel() {
- viewModel = ViewModelProvider(this, RankViewModel.Factory)[RankViewModel::class.java]
binding.viewModel = viewModel
binding.lifecycleOwner = this
}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/rank/RankViewModel.kt b/android/app/src/main/java/com/now/naaga/presentation/rank/RankViewModel.kt
index 4c739e757..277615c06 100644
--- a/android/app/src/main/java/com/now/naaga/presentation/rank/RankViewModel.kt
+++ b/android/app/src/main/java/com/now/naaga/presentation/rank/RankViewModel.kt
@@ -3,16 +3,19 @@ package com.now.naaga.presentation.rank
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
import com.now.domain.model.OrderType
import com.now.domain.model.Rank
import com.now.domain.model.SortType
import com.now.domain.repository.RankRepository
-import com.now.naaga.data.repository.DefaultRankRepository
import com.now.naaga.data.throwable.DataThrowable
import com.now.naaga.data.throwable.DataThrowable.PlayerThrowable
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.launch
+import javax.inject.Inject
-class RankViewModel(private val rankRepository: RankRepository) : ViewModel() {
+@HiltViewModel
+class RankViewModel @Inject constructor(private val rankRepository: RankRepository) : ViewModel() {
private val _myName = MutableLiveData()
val myName: LiveData = _myName
@@ -29,29 +32,29 @@ class RankViewModel(private val rankRepository: RankRepository) : ViewModel() {
val throwable: LiveData = _throwable
fun fetchMyRank() {
- rankRepository.getMyRank(
- callback = { result ->
- result
- .onSuccess { rank ->
- _myName.value = rank.player.nickname
- _myScore.value = rank.player.score
- _myRank.value = rank.rank
- }
- .onFailure { setErrorMessage(it as DataThrowable) }
- },
- )
+ viewModelScope.launch {
+ runCatching {
+ rankRepository.getMyRank()
+ }.onSuccess { rank ->
+ _myName.value = rank.player.nickname
+ _myScore.value = rank.player.score
+ _myRank.value = rank.rank
+ }.onFailure {
+ setErrorMessage(it as DataThrowable)
+ }
+ }
}
fun fetchRanks() {
- rankRepository.getAllRanks(
- SortType.RANK.name,
- OrderType.ASCENDING.name,
- callback = { result ->
- result
- .onSuccess { ranks -> _ranks.value = ranks }
- .onFailure { setErrorMessage(it as DataThrowable) }
- },
- )
+ viewModelScope.launch {
+ runCatching {
+ rankRepository.getAllRanks(SortType.RANK.name, OrderType.ASCENDING.name)
+ }.onSuccess { ranks ->
+ _ranks.value = ranks
+ }.onFailure {
+ setErrorMessage(it as DataThrowable)
+ }
+ }
}
private fun setErrorMessage(throwable: DataThrowable) {
@@ -60,15 +63,4 @@ class RankViewModel(private val rankRepository: RankRepository) : ViewModel() {
else -> {}
}
}
-
- companion object {
- val Factory = RankFactory(DefaultRankRepository())
-
- class RankFactory(private val rankRepository: RankRepository) :
- ViewModelProvider.Factory {
- override fun create(modelClass: Class): T {
- return RankViewModel(rankRepository) as T
- }
- }
- }
}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/setting/SettingActivity.kt b/android/app/src/main/java/com/now/naaga/presentation/setting/SettingActivity.kt
new file mode 100644
index 000000000..e5b4efacb
--- /dev/null
+++ b/android/app/src/main/java/com/now/naaga/presentation/setting/SettingActivity.kt
@@ -0,0 +1,120 @@
+package com.now.naaga.presentation.setting
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.widget.Toast
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import com.now.naaga.R
+import com.now.naaga.data.throwable.DataThrowable
+import com.now.naaga.databinding.ActivitySettingBinding
+import com.now.naaga.presentation.common.dialog.NaagaAlertDialog
+import com.now.naaga.presentation.login.LoginActivity
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class SettingActivity : AppCompatActivity() {
+ private lateinit var binding: ActivitySettingBinding
+ private val viewModel: SettingViewModel by viewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivitySettingBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+ binding.lifecycleOwner = this
+ subscribe()
+ setClickListeners()
+ }
+
+ private fun subscribe() {
+ viewModel.isLoggedIn.observe(this) { isLoggedIn ->
+ if (!isLoggedIn) {
+ shortToast(getString(R.string.setting_logout_message))
+ startActivity(LoginActivity.getIntentWithTop(this))
+ }
+ }
+ viewModel.throwable.observe(this) { error: DataThrowable ->
+ when (error.code) {
+ WRONG_AUTH_ERROR_CODE -> shortToast(getString(R.string.setting_wrong_error_message))
+ EXPIRATION_AUTH_ERROR_CODE -> shortToast(getString(R.string.setting_expiration_error_message))
+ }
+ }
+ viewModel.withdrawalStatus.observe(this) { status ->
+ if (status == true) {
+ shortToast(getString(R.string.setting_withdrawal_success_message))
+ navigateLogin()
+ }
+ }
+ }
+
+ private fun setClickListeners() {
+ binding.tvSettingLogout.setOnClickListener {
+ showLogoutDialog()
+ }
+ binding.ivSettingBack.setOnClickListener {
+ finish()
+ }
+ binding.tvSettingUnlink.setOnClickListener {
+ showWithdrawalDialog()
+ }
+ binding.tvSettingInquiry.setOnClickListener {
+ sendEmail()
+ }
+ }
+
+ private fun navigateLogin() {
+ startActivity(LoginActivity.getIntentWithTop(this))
+ finish()
+ }
+
+ private fun shortToast(message: String) {
+ Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
+ }
+
+ private fun showWithdrawalDialog() {
+ NaagaAlertDialog.Builder().build(
+ title = getString(R.string.withdrawal_dialog_title),
+ description = getString(R.string.withdrawal_dialog_description),
+ positiveText = getString(R.string.withdrawal_dialog_negative),
+ negativeText = getString(R.string.withdrawal_dialog_positive),
+ positiveAction = { },
+ negativeAction = { viewModel.withdrawalMember() },
+ ).show(supportFragmentManager, WITHDRAWAL)
+ }
+
+ private fun showLogoutDialog() {
+ NaagaAlertDialog.Builder().build(
+ title = getString(R.string.logout_dialog_title),
+ description = getString(R.string.logout_dialog_description),
+ positiveText = getString(R.string.logout_dialog_positive_text),
+ negativeText = getString(R.string.logout_dialog_negative_text),
+ positiveAction = { },
+ negativeAction = { viewModel.logout() },
+ ).show(supportFragmentManager, LOGOUT)
+ }
+
+ private fun sendEmail() {
+ val intent = Intent(Intent.ACTION_SEND)
+ intent.apply {
+ type = INTENT_TYPE
+ val emails = arrayOf(getString(R.string.setting_question_email))
+ putExtra(Intent.EXTRA_EMAIL, emails)
+ putExtra(Intent.EXTRA_SUBJECT, getString(R.string.setting_question_email_title))
+ startActivity(this)
+ }
+ }
+
+ companion object {
+ private const val LOGOUT = "LOGOUT"
+ private const val WITHDRAWAL = "WITHDRAWAL"
+ private const val INTENT_TYPE = "plain/text"
+
+ private const val WRONG_AUTH_ERROR_CODE = 101
+ private const val EXPIRATION_AUTH_ERROR_CODE = 102
+
+ fun getIntent(context: Context): Intent {
+ return Intent(context, SettingActivity::class.java)
+ }
+ }
+}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/setting/SettingViewModel.kt b/android/app/src/main/java/com/now/naaga/presentation/setting/SettingViewModel.kt
new file mode 100644
index 000000000..3da61a6b6
--- /dev/null
+++ b/android/app/src/main/java/com/now/naaga/presentation/setting/SettingViewModel.kt
@@ -0,0 +1,47 @@
+package com.now.naaga.presentation.setting
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.now.domain.repository.AuthRepository
+import com.now.naaga.data.throwable.DataThrowable
+import com.now.naaga.data.throwable.DataThrowable.AuthorizationThrowable
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class SettingViewModel @Inject constructor(private val authRepository: AuthRepository) : ViewModel() {
+ private val _isLoggedIn = MutableLiveData(true)
+ val isLoggedIn: LiveData = _isLoggedIn
+
+ private val _throwable = MutableLiveData()
+ val throwable: LiveData = _throwable
+
+ private val _withdrawalStatus = MutableLiveData()
+ val withdrawalStatus: LiveData = _withdrawalStatus
+
+ fun logout() {
+ viewModelScope.launch {
+ runCatching { authRepository.logout() }
+ .onSuccess { _isLoggedIn.value = false }
+ .onFailure { setError(it as DataThrowable) }
+ }
+ }
+
+ fun withdrawalMember() {
+ viewModelScope.launch {
+ runCatching { authRepository.withdrawalMember() }
+ .onSuccess { _withdrawalStatus.value = true }
+ .onFailure { setError(it as DataThrowable) }
+ }
+ }
+
+ private fun setError(throwable: DataThrowable) {
+ when (throwable) {
+ is AuthorizationThrowable -> _throwable.value = throwable
+ else -> {}
+ }
+ }
+}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/splash/SplashActivity.kt b/android/app/src/main/java/com/now/naaga/presentation/splash/SplashActivity.kt
index 0ee04011b..fee2f0d9c 100644
--- a/android/app/src/main/java/com/now/naaga/presentation/splash/SplashActivity.kt
+++ b/android/app/src/main/java/com/now/naaga/presentation/splash/SplashActivity.kt
@@ -1,36 +1,105 @@
package com.now.naaga.presentation.splash
+import android.annotation.SuppressLint
+import android.content.Intent
+import android.net.Uri
import android.os.Bundle
+import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
-import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
-import androidx.lifecycle.ViewModelProvider
+import com.google.firebase.ktx.Firebase
+import com.google.firebase.remoteconfig.ktx.remoteConfig
+import com.google.firebase.remoteconfig.ktx.remoteConfigSettings
import com.now.naaga.R
+import com.now.naaga.data.firebase.analytics.AnalyticsDelegate
+import com.now.naaga.data.firebase.analytics.DefaultAnalyticsDelegate
+import com.now.naaga.data.firebase.analytics.SPLASH_MY_PAGE_STATISTICS
+import com.now.naaga.presentation.beginadventure.BeginAdventureActivity
+import com.now.naaga.presentation.common.dialog.NaagaAlertDialog
import com.now.naaga.presentation.login.LoginActivity
+import com.now.naaga.util.extension.getPackageInfoCompat
+import dagger.hilt.android.AndroidEntryPoint
+
+@SuppressLint("CustomSplashScreen")
+@AndroidEntryPoint
+class SplashActivity : AppCompatActivity(), AnalyticsDelegate by DefaultAnalyticsDelegate() {
+ private val viewModel: SplashViewModel by viewModels()
-class SplashActivity : AppCompatActivity() {
- private lateinit var viewModel: SplashViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- initViewModel()
- viewModel.fetchInProgressAdventure()
+ registerAnalytics(this.lifecycle)
+ updateCheck()
subscribe()
- installSplashScreen()
setContentView(R.layout.activity_splash)
}
- private fun initViewModel() {
- viewModel = ViewModelProvider(this, SplashViewModel.Factory)[SplashViewModel::class.java]
+ private fun updateCheck() {
+ val remoteConfig = Firebase.remoteConfig
+ val configSettings = remoteConfigSettings {
+ minimumFetchIntervalInSeconds = 3600
+ }
+ remoteConfig.setConfigSettingsAsync(configSettings)
+ remoteConfig.setDefaultsAsync(mapOf(MIN_VERSION to DEFAULT_VERSION))
+ val curVersion = packageManager.getPackageInfoCompat(packageName).versionName
+
+ remoteConfig.fetchAndActivate().addOnCompleteListener {
+ if (it.isSuccessful) {
+ val minVersion = remoteConfig.getString(MIN_VERSION)
+ if (minVersion > curVersion) {
+ showUpdateDialog()
+ } else {
+ viewModel.testTokenValid()
+ }
+ }
+ }
+ }
+
+ private fun showUpdateDialog() {
+ NaagaAlertDialog.Builder()
+ .setCancelable(false)
+ .build(
+ title = getString(R.string.confirm_dialog_title),
+ description = getString(R.string.confirm_dialog_description),
+ positiveText = getString(R.string.confirm_dialog_positive_text),
+ negativeText = getString(R.string.confirm_dialog_negative_text),
+ positiveAction = ::navigateToPlayStore,
+ negativeAction = ::finish,
+ ).show(supportFragmentManager, TAG_CONFIRM_DIALOG)
+ }
+
+ private fun navigateToPlayStore() {
+ val uri = PLAY_STORE_URI + packageName
+ val intent = Intent(Intent.ACTION_VIEW, Uri.parse(uri))
+ startActivity(intent)
+ finish()
}
private fun subscribe() {
- viewModel.adventureStatus.observe(this) {
- startNextActivity()
+ viewModel.isTokenValid.observe(this) { isTokenValid: Boolean ->
+ if (isTokenValid) {
+ startBeginAdventureActivity()
+ return@observe
+ }
+ startLoginActivity()
+ }
+ viewModel.error.observe(this) {
+ logServerError(SPLASH_MY_PAGE_STATISTICS, it.code, it.message.toString())
}
}
- private fun startNextActivity() {
- val intent = LoginActivity.getIntent(this)
- startActivity(intent)
+ private fun startBeginAdventureActivity() {
+ startActivity(BeginAdventureActivity.getIntent(this))
+ finish()
+ }
+
+ private fun startLoginActivity() {
+ startActivity(LoginActivity.getIntent(this))
finish()
}
+
+ companion object {
+ const val TAG_CONFIRM_DIALOG = "CONFIRM"
+ const val MIN_VERSION = "version"
+ const val PLAY_STORE_URI = "market://details?id="
+ const val DEFAULT_VERSION = "0.0.0"
+ }
}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/splash/SplashViewModel.kt b/android/app/src/main/java/com/now/naaga/presentation/splash/SplashViewModel.kt
index 37d240952..4d57b6570 100644
--- a/android/app/src/main/java/com/now/naaga/presentation/splash/SplashViewModel.kt
+++ b/android/app/src/main/java/com/now/naaga/presentation/splash/SplashViewModel.kt
@@ -3,42 +3,31 @@ package com.now.naaga.presentation.splash
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import com.now.domain.model.Adventure
-import com.now.domain.model.AdventureStatus
-import com.now.domain.repository.AdventureRepository
-import com.now.naaga.data.repository.DefaultAdventureRepository
+import androidx.lifecycle.viewModelScope
+import com.now.domain.repository.StatisticsRepository
+import com.now.naaga.data.throwable.DataThrowable
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.launch
+import javax.inject.Inject
-class SplashViewModel(private val adventureRepository: AdventureRepository) : ViewModel() {
- private val _adventure = MutableLiveData()
- val adventure: LiveData = _adventure
+@HiltViewModel
+class SplashViewModel @Inject constructor(private val statisticsRepository: StatisticsRepository) : ViewModel() {
- private val _adventureStatus = MutableLiveData()
- val adventureStatus: LiveData = _adventureStatus
+ private val _isTokenValid = MutableLiveData()
+ val isTokenValid: LiveData = _isTokenValid
- fun fetchInProgressAdventure() {
- adventureRepository.fetchAdventureByStatus(AdventureStatus.IN_PROGRESS) { result: Result> ->
- result
- .onSuccess { fetchAdventure(it) }
- .onFailure { _adventureStatus.value = AdventureStatus.NONE }
- }
- }
-
- private fun fetchAdventure(adventures: List) {
- if (adventures.isNotEmpty()) {
- _adventure.value = adventures.first()
- _adventureStatus.value = adventures.first().adventureStatus
- } else {
- _adventureStatus.value = AdventureStatus.NONE
- }
- }
-
- companion object {
- val Factory = ViewModelFactory(DefaultAdventureRepository())
+ private val _error = MutableLiveData()
+ val error: LiveData = _error
- class ViewModelFactory(private val adventureRepository: AdventureRepository) : ViewModelProvider.Factory {
- override fun create(modelClass: Class): T {
- return SplashViewModel(adventureRepository) as T
+ fun testTokenValid() {
+ viewModelScope.launch {
+ runCatching {
+ statisticsRepository.getMyStatistics()
+ }.onSuccess {
+ _isTokenValid.value = true
+ }.onFailure {
+ _isTokenValid.value = false
+ _error.value = it as DataThrowable.AuthorizationThrowable
}
}
}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/uimodel/model/MyPagePlaceUiModel.kt b/android/app/src/main/java/com/now/naaga/presentation/uimodel/model/MyPagePlaceUiModel.kt
new file mode 100644
index 000000000..0483c1fdf
--- /dev/null
+++ b/android/app/src/main/java/com/now/naaga/presentation/uimodel/model/MyPagePlaceUiModel.kt
@@ -0,0 +1,6 @@
+package com.now.naaga.presentation.uimodel.model
+
+data class MyPagePlaceUiModel(
+ val image: String,
+ val name: String,
+)
diff --git a/android/app/src/main/java/com/now/naaga/presentation/uimodel/model/StatisticsUiModel.kt b/android/app/src/main/java/com/now/naaga/presentation/uimodel/model/StatisticsUiModel.kt
new file mode 100644
index 000000000..771993aca
--- /dev/null
+++ b/android/app/src/main/java/com/now/naaga/presentation/uimodel/model/StatisticsUiModel.kt
@@ -0,0 +1,43 @@
+package com.now.naaga.presentation.uimodel.model
+
+import androidx.annotation.DrawableRes
+import com.now.naaga.R
+
+data class StatisticsUiModel(
+ @DrawableRes val icon: Int,
+ @DrawableRes val background: Int,
+ val detail: String,
+ val count: Int,
+) {
+ companion object {
+ fun getSuccessAdventureStatisticsModel(count: Int): StatisticsUiModel {
+ return StatisticsUiModel(
+ R.drawable.ic_success_adventure,
+ R.drawable.oval_orange_gradient,
+ SUCCESS_DETAIL,
+ count,
+ )
+ }
+
+ fun getFailAdventureStatisticsModel(count: Int): StatisticsUiModel {
+ return StatisticsUiModel(
+ R.drawable.ic_fail_adventure,
+ R.drawable.oval_yellow_gradient,
+ FAIL_DETAIL,
+ count,
+ )
+ }
+ fun getAllAdventureStatisticsModel(count: Int): StatisticsUiModel {
+ return StatisticsUiModel(
+ R.drawable.ic_all_adventure,
+ R.drawable.oval_blue_gradient,
+ ALL_DETAIL,
+ count,
+ )
+ }
+
+ private const val SUCCESS_DETAIL = "성공 모험"
+ private const val FAIL_DETAIL = "실패 모험"
+ private const val ALL_DETAIL = "전체 모험"
+ }
+}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/upload/CameraPermissionDialog.kt b/android/app/src/main/java/com/now/naaga/presentation/upload/CameraPermissionDialog.kt
deleted file mode 100644
index 9a8c7ba43..000000000
--- a/android/app/src/main/java/com/now/naaga/presentation/upload/CameraPermissionDialog.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-package com.now.naaga.presentation.upload
-
-import android.content.Intent
-import android.graphics.Color
-import android.graphics.drawable.ColorDrawable
-import android.net.Uri
-import android.os.Bundle
-import android.provider.Settings
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.fragment.app.DialogFragment
-import com.now.naaga.data.firebase.analytics.AnalyticsDelegate
-import com.now.naaga.data.firebase.analytics.CAMERA_PERMISSION_OPEN_SETTING
-import com.now.naaga.data.firebase.analytics.DefaultAnalyticsDelegate
-import com.now.naaga.databinding.DialogCameraPermissionBinding
-import com.now.naaga.presentation.beginadventure.LocationPermissionDialog
-import com.now.naaga.util.dpToPx
-import com.now.naaga.util.getWidthProportionalToDevice
-
-class CameraPermissionDialog : DialogFragment(), AnalyticsDelegate by DefaultAnalyticsDelegate() {
- private lateinit var binding: DialogCameraPermissionBinding
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?,
- ): View {
- binding = DialogCameraPermissionBinding.inflate(layoutInflater)
- dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
- return binding.root
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- registerAnalytics(this.lifecycle)
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- setSize()
- binding.btnDialogLocationSetting.setOnClickListener {
- logClickEvent(requireContext().getViewEntryName(it), CAMERA_PERMISSION_OPEN_SETTING)
- openSetting()
- dismiss()
- }
- }
-
- private fun setSize() {
- val dialogWidth = getWidthProportionalToDevice(requireContext(), LocationPermissionDialog.WIDTH_RATE)
- val dialogHeight = dpToPx(requireContext(), LocationPermissionDialog.HEIGHT)
- dialog?.window?.setLayout(dialogWidth, dialogHeight)
- }
-
- private fun openSetting() {
- val appDetailsIntent = Intent(
- Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
- Uri.parse("package:${requireContext().packageName}"),
- ).addCategory(Intent.CATEGORY_DEFAULT)
- startActivity(appDetailsIntent)
- }
-
- companion object {
- const val TAG_CAMERA_DIALOG = "CAMERA"
- }
-}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/upload/UploadActivity.kt b/android/app/src/main/java/com/now/naaga/presentation/upload/UploadActivity.kt
index 69efd99d1..820949ecc 100644
--- a/android/app/src/main/java/com/now/naaga/presentation/upload/UploadActivity.kt
+++ b/android/app/src/main/java/com/now/naaga/presentation/upload/UploadActivity.kt
@@ -7,30 +7,34 @@ import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.location.Location
-import android.location.LocationManager
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import android.view.View
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
+import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
-import androidx.lifecycle.ViewModelProvider
+import com.google.android.gms.location.LocationServices
+import com.google.android.gms.tasks.CancellationToken
+import com.google.android.gms.tasks.CancellationTokenSource
+import com.google.android.gms.tasks.OnTokenCanceledListener
import com.now.domain.model.Coordinate
+import com.now.naaga.R
import com.now.naaga.data.firebase.analytics.AnalyticsDelegate
import com.now.naaga.data.firebase.analytics.DefaultAnalyticsDelegate
import com.now.naaga.data.firebase.analytics.UPLOAD_OPEN_CAMERA
-import com.now.naaga.data.firebase.analytics.UPLOAD_SET_COORDINATE
-import com.now.naaga.data.repository.DefaultPlaceRepository
+import com.now.naaga.data.throwable.DataThrowable
import com.now.naaga.databinding.ActivityUploadBinding
-import com.now.naaga.presentation.beginadventure.LocationPermissionDialog
-import com.now.naaga.presentation.beginadventure.LocationPermissionDialog.Companion.TAG_LOCATION_DIALOG
-import com.now.naaga.presentation.upload.CameraPermissionDialog.Companion.TAG_CAMERA_DIALOG
+import com.now.naaga.presentation.common.dialog.DialogType
+import com.now.naaga.presentation.common.dialog.PermissionDialog
+import dagger.hilt.android.AndroidEntryPoint
+@AndroidEntryPoint
class UploadActivity : AppCompatActivity(), AnalyticsDelegate by DefaultAnalyticsDelegate() {
private lateinit var binding: ActivityUploadBinding
- private lateinit var viewModel: UploadViewModel
+ private val viewModel: UploadViewModel by viewModels()
private val cameraLauncher = registerForActivityResult(
ActivityResultContracts.TakePicturePreview(),
@@ -48,11 +52,11 @@ class UploadActivity : AppCompatActivity(), AnalyticsDelegate by DefaultAnalytic
if (isGranted.not()) {
when (entry.key) {
Manifest.permission.CAMERA -> {
- CameraPermissionDialog().show(supportFragmentManager, TAG_CAMERA_DIALOG)
+ PermissionDialog(DialogType.CAMERA).show(supportFragmentManager)
}
Manifest.permission.ACCESS_FINE_LOCATION -> {
- LocationPermissionDialog().show(supportFragmentManager, TAG_LOCATION_DIALOG)
+ PermissionDialog(DialogType.LOCATION).show(supportFragmentManager)
}
}
}
@@ -66,6 +70,7 @@ class UploadActivity : AppCompatActivity(), AnalyticsDelegate by DefaultAnalytic
setContentView(binding.root)
initViewModel()
+ subscribe()
registerAnalytics(this.lifecycle)
requestPermission()
setCoordinate()
@@ -73,12 +78,59 @@ class UploadActivity : AppCompatActivity(), AnalyticsDelegate by DefaultAnalytic
}
private fun initViewModel() {
- val repository = DefaultPlaceRepository()
- val factory = UploadFactory(application, repository)
- viewModel = ViewModelProvider(this, factory)[UploadViewModel::class.java]
binding.lifecycleOwner = this
binding.viewModel = viewModel
- setClickListeners()
+ }
+
+ private fun subscribe() {
+ viewModel.coordinate.observe(this) {
+ binding.lottieUploadLoading.visibility = View.GONE
+ }
+ viewModel.successUpload.observe(this) { uploadStatus ->
+ when (uploadStatus) {
+ UploadStatus.SUCCESS -> {
+ changeVisibility(binding.lottieUploadLoading, View.GONE)
+ shortToast(getString(R.string.upload_success_submit))
+ finish()
+ }
+
+ UploadStatus.PENDING -> {
+ changeVisibility(binding.lottieUploadLoading, View.VISIBLE)
+ }
+
+ UploadStatus.FAIL -> {
+ changeVisibility(binding.lottieUploadLoading, View.GONE)
+ shortToast(getString(R.string.upload_fail_submit))
+ }
+ }
+ }
+ viewModel.throwable.observe(this) { error: DataThrowable ->
+ when (error.code) {
+ UploadViewModel.ERROR_STORE_PHOTO -> {
+ shortToast(getString(R.string.upload_error_store_photo_message))
+ }
+
+ UploadViewModel.ALREADY_EXISTS_NEARBY -> {
+ shortToast(getString(R.string.upload_error_already_exists_nearby_message))
+ }
+
+ UploadViewModel.ERROR_POST_BODY -> {
+ shortToast(getString(R.string.upload_error_post_message))
+ }
+ }
+ }
+ }
+
+ private fun changeVisibility(view: View, status: Int) {
+ when (status) {
+ View.VISIBLE -> {
+ view.visibility = status
+ }
+
+ View.GONE -> {
+ view.visibility = status
+ }
+ }
}
private fun requestPermission() {
@@ -93,13 +145,23 @@ class UploadActivity : AppCompatActivity(), AnalyticsDelegate by DefaultAnalytic
private fun setCoordinate() {
if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
- val locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
- val location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
+ val fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
+ fusedLocationClient.getCurrentLocation(PRIORITY_HIGH_ACCURACY, createCancellationToken())
+ .addOnSuccessListener { location ->
+ location.let { viewModel.setCoordinate(getCoordinate(location)) }
+ }
+ .addOnFailureListener { }
+ }
+ }
- if (location != null) {
- val coordinate = getCoordinate(location)
- binding.tvUploadPhotoCoordinate.text = coordinate.toText()
- viewModel.setCoordinate(coordinate)
+ private fun createCancellationToken(): CancellationToken {
+ return object : CancellationToken() {
+ override fun onCanceledRequested(p0: OnTokenCanceledListener): CancellationToken {
+ return CancellationTokenSource().token
+ }
+
+ override fun isCancellationRequested(): Boolean {
+ return false
}
}
}
@@ -120,21 +182,16 @@ class UploadActivity : AppCompatActivity(), AnalyticsDelegate by DefaultAnalytic
logClickEvent(getViewEntryName(it), UPLOAD_OPEN_CAMERA)
checkCameraPermission()
}
-
binding.ivUploadPhoto.setOnClickListener {
logClickEvent(getViewEntryName(it), UPLOAD_OPEN_CAMERA)
checkCameraPermission()
}
- binding.ivUploadPhotoCoordinate.setOnClickListener {
- logClickEvent(getViewEntryName(it), UPLOAD_SET_COORDINATE)
- checkLocationPermission()
- }
- binding.ivUploadClose.setOnClickListener {
+ binding.ivUploadBack.setOnClickListener {
finish()
}
binding.btnUploadSubmit.setOnClickListener {
if (isFormValid().not()) {
- Toast.makeText(this, "모든 정보를 입력해주세요.", Toast.LENGTH_SHORT).show()
+ shortToast(getString(R.string.upload_error_insufficient_info_message))
} else {
viewModel.postPlace()
}
@@ -143,7 +200,7 @@ class UploadActivity : AppCompatActivity(), AnalyticsDelegate by DefaultAnalytic
private fun checkCameraPermission() {
if (checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_DENIED) {
- CameraPermissionDialog().show(supportFragmentManager, TAG_CAMERA_DIALOG)
+ PermissionDialog(DialogType.CAMERA).show(supportFragmentManager)
} else {
openCamera()
}
@@ -153,18 +210,10 @@ class UploadActivity : AppCompatActivity(), AnalyticsDelegate by DefaultAnalytic
cameraLauncher.launch(null)
}
- private fun checkLocationPermission() {
- if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_DENIED) {
- LocationPermissionDialog().show(supportFragmentManager, TAG_LOCATION_DIALOG)
- } else {
- setCoordinate()
- }
- }
-
private fun setImage(bitmap: Bitmap) {
binding.ivUploadCameraIcon.visibility = View.GONE
binding.ivUploadPhoto.setImageBitmap(bitmap)
- val uri = getImageUri(bitmap) ?: Uri.EMPTY
+ val uri = getAbsolutePathFromUri(getImageUri(bitmap) ?: Uri.EMPTY) ?: ""
viewModel.setUri(uri)
}
@@ -181,28 +230,24 @@ class UploadActivity : AppCompatActivity(), AnalyticsDelegate by DefaultAnalytic
return null
}
- private fun isFormValid(): Boolean {
- return (isEmptyPhoto() || isEmptyTitle() || isEmptyCoordinate() || isEmptyDescription()).not()
- }
-
- private fun isEmptyPhoto(): Boolean {
- return viewModel.hasUri().not()
- }
-
- private fun isEmptyTitle(): Boolean {
- return viewModel.title.value == null
- }
-
- private fun isEmptyCoordinate(): Boolean {
- return viewModel.hasCoordinate().not()
+ private fun getAbsolutePathFromUri(uri: Uri): String? {
+ val projection = arrayOf(MediaStore.Images.Media.DATA)
+ val cursor = applicationContext.contentResolver.query(uri, projection, null, null, null)
+ cursor?.use {
+ val columnIndex = it.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
+ if (it.moveToFirst()) {
+ return it.getString(columnIndex)
+ }
+ }
+ return null
}
- private fun isEmptyDescription(): Boolean {
- return viewModel.description.value == null
+ private fun shortToast(message: String) {
+ Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
- private fun Coordinate.toText(): String {
- return "$latitude, $longitude"
+ private fun isFormValid(): Boolean {
+ return viewModel.isFormValid()
}
companion object {
@@ -217,6 +262,8 @@ class UploadActivity : AppCompatActivity(), AnalyticsDelegate by DefaultAnalytic
put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
}
+ const val PRIORITY_HIGH_ACCURACY = 100
+
fun getIntent(context: Context): Intent {
return Intent(context, UploadActivity::class.java)
}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/upload/UploadFactory.kt b/android/app/src/main/java/com/now/naaga/presentation/upload/UploadFactory.kt
deleted file mode 100644
index c322500e5..000000000
--- a/android/app/src/main/java/com/now/naaga/presentation/upload/UploadFactory.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.now.naaga.presentation.upload
-
-import android.app.Application
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import com.now.domain.repository.PlaceRepository
-
-class UploadFactory(
- private val application: Application,
- private val placeRepository: PlaceRepository,
-) : ViewModelProvider.Factory {
- override fun create(modelClass: Class): T {
- if (modelClass.isAssignableFrom(UploadViewModel::class.java)) {
- return UploadViewModel(application, placeRepository) as T
- } else {
- throw java.lang.IllegalArgumentException()
- }
- }
-}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/upload/UploadStatus.kt b/android/app/src/main/java/com/now/naaga/presentation/upload/UploadStatus.kt
new file mode 100644
index 000000000..34f98afd9
--- /dev/null
+++ b/android/app/src/main/java/com/now/naaga/presentation/upload/UploadStatus.kt
@@ -0,0 +1,5 @@
+package com.now.naaga.presentation.upload
+
+enum class UploadStatus {
+ SUCCESS, PENDING, FAIL
+}
diff --git a/android/app/src/main/java/com/now/naaga/presentation/upload/UploadViewModel.kt b/android/app/src/main/java/com/now/naaga/presentation/upload/UploadViewModel.kt
index ae6e63ed6..5f7a460ac 100644
--- a/android/app/src/main/java/com/now/naaga/presentation/upload/UploadViewModel.kt
+++ b/android/app/src/main/java/com/now/naaga/presentation/upload/UploadViewModel.kt
@@ -1,81 +1,83 @@
package com.now.naaga.presentation.upload
-import android.app.Application
-import android.content.Context
-import android.net.Uri
-import android.provider.MediaStore
-import android.text.Editable
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
import com.now.domain.model.Coordinate
-import com.now.domain.model.Place
import com.now.domain.repository.PlaceRepository
+import com.now.naaga.data.throwable.DataThrowable
+import com.now.naaga.data.throwable.DataThrowable.PlaceThrowable
+import com.now.naaga.data.throwable.DataThrowable.UniversalThrowable
+import com.now.naaga.util.MutableSingleLiveData
+import com.now.naaga.util.SingleLiveData
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.launch
+import javax.inject.Inject
-class UploadViewModel(
- private val application: Application,
+@HiltViewModel
+class UploadViewModel @Inject constructor(
private val placeRepository: PlaceRepository,
) : ViewModel() {
- private var imageUri: Uri = Uri.EMPTY
- private var coordinate = DEFAULT_COORDINATE
+ private var imageUri: String = URI_EMPTY
- private val _name = MutableLiveData()
- val title: LiveData = _name
+ val name = MutableLiveData()
- private val _description = MutableLiveData()
- val description: LiveData = _description
+ private val _successUpload = MutableSingleLiveData()
+ val successUpload: SingleLiveData = _successUpload
- fun setTitle(editTitle: Editable) {
- _name.value = editTitle.toString()
- }
+ private val _throwable = MutableLiveData()
+ val throwable: LiveData = _throwable
- fun setDescription(editTitle: Editable) {
- _description.value = editTitle.toString()
- }
+ private val _coordinate = MutableLiveData()
+ val coordinate: LiveData = _coordinate
- fun setUri(uri: Uri) {
+ fun setUri(uri: String) {
imageUri = uri
}
fun setCoordinate(coordinate: Coordinate) {
- this.coordinate = coordinate
- }
-
- fun hasUri(): Boolean {
- return imageUri != Uri.EMPTY
+ _coordinate.value = coordinate
}
- fun hasCoordinate(): Boolean {
- return coordinate != DEFAULT_COORDINATE
+ fun isFormValid(): Boolean {
+ return (imageUri != URI_EMPTY) && (_coordinate.value != null) && (name.value != null)
}
fun postPlace() {
- placeRepository.postPlace(
- name = _name.value.toString(),
- description = _description.value.toString(),
- coordinate = coordinate,
- image = getAbsolutePathFromUri(application.applicationContext, imageUri) ?: "",
- callback = { result: Result ->
- result
- .onSuccess { }
- .onFailure { }
- },
- )
+ _coordinate.value?.let { coordinate ->
+ _successUpload.setValue(UploadStatus.PENDING)
+ viewModelScope.launch {
+ runCatching {
+ placeRepository.postPlace(
+ name = name.value.toString(),
+ description = "",
+ coordinate = coordinate,
+ image = imageUri,
+ )
+ }.onSuccess {
+ _successUpload.setValue(UploadStatus.SUCCESS)
+ }.onFailure {
+ _successUpload.setValue(UploadStatus.FAIL)
+ setError(it as DataThrowable)
+ }
+ }
+ }
}
- private fun getAbsolutePathFromUri(context: Context, uri: Uri): String? {
- val projection = arrayOf(MediaStore.Images.Media.DATA)
- val cursor = context.contentResolver.query(uri, projection, null, null, null)
- cursor?.use {
- val columnIndex = it.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
- if (it.moveToFirst()) {
- return it.getString(columnIndex)
- }
+ private fun setError(throwable: DataThrowable) {
+ when (throwable) {
+ is UniversalThrowable -> _throwable.value = throwable
+ is PlaceThrowable -> _throwable.value = throwable
+ else -> {}
}
- return null
}
companion object {
- val DEFAULT_COORDINATE = Coordinate(-1.0, -1.0)
+ const val URI_EMPTY = "EMPTY"
+
+ const val ALREADY_EXISTS_NEARBY = 505
+ const val ERROR_STORE_PHOTO = 215
+ const val ERROR_POST_BODY = 205
}
}
diff --git a/android/app/src/main/java/com/now/naaga/util/Event.kt b/android/app/src/main/java/com/now/naaga/util/Event.kt
new file mode 100644
index 000000000..66459f691
--- /dev/null
+++ b/android/app/src/main/java/com/now/naaga/util/Event.kt
@@ -0,0 +1,27 @@
+package com.now.naaga.util
+
+/**
+ * Used as a wrapper for data that is exposed via a LiveData that represents an event.
+ */
+open class Event(private val content: T) {
+
+ var hasBeenHandled = false
+ private set // Allow external read but not write
+
+ /**
+ * Returns the content and prevents its use again.
+ */
+ fun getContentIfNotHandled(): T? {
+ return if (hasBeenHandled) {
+ null
+ } else {
+ hasBeenHandled = true
+ content
+ }
+ }
+
+ /**
+ * Returns the content, even if it's already been handled.
+ */
+ fun peekContent(): T = content
+}
diff --git a/android/app/src/main/java/com/now/naaga/util/KakaoLoginUtil.kt b/android/app/src/main/java/com/now/naaga/util/KakaoLoginUtil.kt
index 20e78b956..e98de10fe 100644
--- a/android/app/src/main/java/com/now/naaga/util/KakaoLoginUtil.kt
+++ b/android/app/src/main/java/com/now/naaga/util/KakaoLoginUtil.kt
@@ -7,9 +7,11 @@ import com.kakao.sdk.common.model.ClientError
import com.kakao.sdk.common.model.ClientErrorCause
import com.kakao.sdk.user.UserApiClient
-private const val KAKAO_LOGIN_LOG_TAG = "kakao login"
+const val KAKAO_LOGIN_LOG_TAG = "kakao login"
private const val KAKAO_LOGIN_FAIL_MESSAGE = "카카오계정으로 로그인 실패"
private const val KAKAO_LOGIN_SUCCESS_MESSAGE = "카카오계정으로 로그인 성공"
+private const val KAKAO_UNLINK_FAIL_MESSAGE = "연결 끊기 실패"
+private const val KAKAO_UNLINK_SUCCESS_MESSAGE = "연결 끊기 성공. SDK에서 토큰 삭제 됨"
private fun getLoginCallback(doNextAction: (token: String) -> Unit): (OAuthToken?, Throwable?) -> Unit {
val callback: (OAuthToken?, Throwable?) -> Unit = { token, error ->
@@ -50,3 +52,13 @@ fun loginWithKakao(context: Context, doNextAction: (token: String) -> Unit) {
UserApiClient.instance.loginWithKakaoAccount(context, callback = callback)
}
}
+
+fun unlinkWithKakao() {
+ UserApiClient.instance.unlink { error ->
+ if (error != null) {
+ Log.e(KAKAO_LOGIN_LOG_TAG, KAKAO_UNLINK_FAIL_MESSAGE, error)
+ } else {
+ Log.i(KAKAO_LOGIN_LOG_TAG, KAKAO_UNLINK_SUCCESS_MESSAGE)
+ }
+ }
+}
diff --git a/android/app/src/main/java/com/now/naaga/util/MutableSingleLiveData.kt b/android/app/src/main/java/com/now/naaga/util/MutableSingleLiveData.kt
new file mode 100644
index 000000000..bb31a719d
--- /dev/null
+++ b/android/app/src/main/java/com/now/naaga/util/MutableSingleLiveData.kt
@@ -0,0 +1,16 @@
+package com.now.naaga.util
+
+class MutableSingleLiveData : SingleLiveData {
+
+ constructor() : super()
+
+ constructor(value: T) : super(value)
+
+ public override fun postValue(value: T) {
+ super.postValue(value)
+ }
+
+ public override fun setValue(value: T) {
+ super.setValue(value)
+ }
+}
diff --git a/android/app/src/main/java/com/now/naaga/util/ResponseUtil.kt b/android/app/src/main/java/com/now/naaga/util/ResponseUtil.kt
new file mode 100644
index 000000000..2d71bb421
--- /dev/null
+++ b/android/app/src/main/java/com/now/naaga/util/ResponseUtil.kt
@@ -0,0 +1,45 @@
+package com.now.naaga.util
+
+import com.now.naaga.data.throwable.DataThrowable
+import org.json.JSONObject
+import retrofit2.Response
+
+private fun Response.codeIn400s(): Boolean {
+ return this.code() in 400..499
+}
+
+private fun Response.codeIn500s(): Boolean {
+ return this.code() in 500..599
+}
+
+private fun Response.isDelete(): Boolean {
+ return this.raw().request.method == "DELETE"
+}
+
+@Suppress("UNCHECKED_CAST")
+fun Response.getValueOrThrow(): T {
+ if (this.isSuccessful) {
+ if (this.isDelete()) { return Unit as T }
+ return this.body() ?: throw DataThrowable.IllegalStateThrowable()
+ }
+
+ if (codeIn500s()) {
+ throw DataThrowable.IllegalStateThrowable()
+ }
+
+ if (codeIn400s()) {
+ val errorResponse = errorBody()?.string()
+ val jsonObject = errorResponse?.let { JSONObject(it) }
+ val code = jsonObject?.getInt("code") ?: 0
+ val message = jsonObject?.getString("message") ?: ""
+
+ when (code) {
+ in 100..199 -> { throw DataThrowable.AuthorizationThrowable(code, message) }
+ in 200..299 -> { throw DataThrowable.UniversalThrowable(code, message) }
+ in 300..399 -> { throw DataThrowable.PlayerThrowable(code, message) }
+ in 400..499 -> { throw DataThrowable.GameThrowable(code, message) }
+ in 500..599 -> { throw DataThrowable.PlaceThrowable(code, message) }
+ }
+ }
+ throw DataThrowable.IllegalStateThrowable()
+}
diff --git a/android/app/src/main/java/com/now/naaga/util/SingleLiveData.kt b/android/app/src/main/java/com/now/naaga/util/SingleLiveData.kt
new file mode 100644
index 000000000..30fb3b8a5
--- /dev/null
+++ b/android/app/src/main/java/com/now/naaga/util/SingleLiveData.kt
@@ -0,0 +1,33 @@
+package com.now.naaga.util
+
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.MutableLiveData
+
+abstract class SingleLiveData {
+
+ private val liveData = MutableLiveData>()
+
+ protected constructor()
+
+ protected constructor(value: T) {
+ liveData.value = Event(value)
+ }
+
+ protected open fun setValue(value: T) {
+ liveData.value = Event(value)
+ }
+
+ protected open fun postValue(value: T) {
+ liveData.postValue(Event(value))
+ }
+
+ fun getValue() = liveData.value?.peekContent()
+
+ fun observe(owner: LifecycleOwner, onResult: (T) -> Unit) {
+ liveData.observe(owner) { it.getContentIfNotHandled()?.let(onResult) }
+ }
+
+ fun observePeek(owner: LifecycleOwner, onResult: (T) -> Unit) {
+ liveData.observe(owner) { onResult(it.peekContent()) }
+ }
+}
diff --git a/android/app/src/main/java/com/now/naaga/util/IntentExt.kt b/android/app/src/main/java/com/now/naaga/util/extension/IntentExt.kt
similarity index 88%
rename from android/app/src/main/java/com/now/naaga/util/IntentExt.kt
rename to android/app/src/main/java/com/now/naaga/util/extension/IntentExt.kt
index 31ebe4025..3da311f84 100644
--- a/android/app/src/main/java/com/now/naaga/util/IntentExt.kt
+++ b/android/app/src/main/java/com/now/naaga/util/extension/IntentExt.kt
@@ -1,4 +1,4 @@
-package com.now.naaga.util
+package com.now.naaga.util.extension
import android.content.Intent
import android.os.Build
diff --git a/android/app/src/main/java/com/now/naaga/util/extension/PackageManagerExt.kt b/android/app/src/main/java/com/now/naaga/util/extension/PackageManagerExt.kt
new file mode 100644
index 000000000..6137b8bd8
--- /dev/null
+++ b/android/app/src/main/java/com/now/naaga/util/extension/PackageManagerExt.kt
@@ -0,0 +1,14 @@
+package com.now.naaga.util.extension
+
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
+import android.os.Build
+
+fun PackageManager.getPackageInfoCompat(packageName: String, flags: Int = 0): PackageInfo {
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(flags.toLong()))
+ } else {
+ @Suppress("DEPRECATION")
+ getPackageInfo(packageName, flags)
+ }
+}
diff --git a/android/app/src/main/naaga_icon-playstore.png b/android/app/src/main/naaga_icon-playstore.png
index 0447b6389..8af0b09a5 100644
Binary files a/android/app/src/main/naaga_icon-playstore.png and b/android/app/src/main/naaga_icon-playstore.png differ
diff --git a/android/app/src/main/res/drawable/bg_blue_button.xml b/android/app/src/main/res/drawable/bg_blue_button.xml
new file mode 100644
index 000000000..5b0c8246b
--- /dev/null
+++ b/android/app/src/main/res/drawable/bg_blue_button.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/bg_blue_gradient.xml b/android/app/src/main/res/drawable/bg_blue_gradient.xml
new file mode 100644
index 000000000..faa6e80d8
--- /dev/null
+++ b/android/app/src/main/res/drawable/bg_blue_gradient.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/bg_yellow_button.xml b/android/app/src/main/res/drawable/bg_yellow_button.xml
new file mode 100644
index 000000000..8b17b9fa9
--- /dev/null
+++ b/android/app/src/main/res/drawable/bg_yellow_button.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/bg_yellow_button_thick.xml b/android/app/src/main/res/drawable/bg_yellow_button_thick.xml
new file mode 100644
index 000000000..1c2fc7b43
--- /dev/null
+++ b/android/app/src/main/res/drawable/bg_yellow_button_thick.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_all_adventure.xml b/android/app/src/main/res/drawable/ic_all_adventure.xml
new file mode 100644
index 000000000..5566dd1a9
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_all_adventure.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_building.xml b/android/app/src/main/res/drawable/ic_building.xml
new file mode 100644
index 000000000..d8ded2560
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_building.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_building_full.xml b/android/app/src/main/res/drawable/ic_building_full.xml
new file mode 100644
index 000000000..976e5adda
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_building_full.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_camera_dialog.xml b/android/app/src/main/res/drawable/ic_camera_dialog.xml
index 1898378a7..3a97119e5 100644
--- a/android/app/src/main/res/drawable/ic_camera_dialog.xml
+++ b/android/app/src/main/res/drawable/ic_camera_dialog.xml
@@ -8,14 +8,14 @@
android:fillColor="#E3C6ED"/>
+ android:fillColor="#FACB10"/>
+ android:fillColor="#F2B70C"/>
+ android:fillColor="#F2B70C"/>
diff --git a/android/app/src/main/res/drawable/ic_compass.xml b/android/app/src/main/res/drawable/ic_compass.xml
new file mode 100644
index 000000000..f14824ac7
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_compass.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_drop.xml b/android/app/src/main/res/drawable/ic_drop.xml
deleted file mode 100644
index ef96c1ffb..000000000
--- a/android/app/src/main/res/drawable/ic_drop.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
diff --git a/android/app/src/main/res/drawable/ic_fail_adventure.xml b/android/app/src/main/res/drawable/ic_fail_adventure.xml
new file mode 100644
index 000000000..054e4be54
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_fail_adventure.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_fail.xml b/android/app/src/main/res/drawable/ic_fail_label.xml
similarity index 100%
rename from android/app/src/main/res/drawable/ic_fail.xml
rename to android/app/src/main/res/drawable/ic_fail_label.xml
diff --git a/android/app/src/main/res/drawable/ic_flag.xml b/android/app/src/main/res/drawable/ic_flag.xml
new file mode 100644
index 000000000..88383d664
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_flag.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_hint.xml b/android/app/src/main/res/drawable/ic_hint.xml
deleted file mode 100644
index 481db8bee..000000000
--- a/android/app/src/main/res/drawable/ic_hint.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
diff --git a/android/app/src/main/res/drawable/ic_launcher_foreground.xml b/android/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 000000000..356597e10
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,728 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_location_dialog.xml b/android/app/src/main/res/drawable/ic_location_dialog.xml
index b56ea58ea..ac4979756 100644
--- a/android/app/src/main/res/drawable/ic_location_dialog.xml
+++ b/android/app/src/main/res/drawable/ic_location_dialog.xml
@@ -1,15 +1,18 @@
-
-
-
+ android:width="144dp"
+ android:height="144dp"
+ android:viewportWidth="144"
+ android:viewportHeight="144">
+
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_logo_purple.xml b/android/app/src/main/res/drawable/ic_logo_purple.xml
deleted file mode 100644
index a7e2a6f47..000000000
--- a/android/app/src/main/res/drawable/ic_logo_purple.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/android/app/src/main/res/drawable/ic_marker_east.xml b/android/app/src/main/res/drawable/ic_marker_east.xml
index 06cbf22fb..7dc51c1cb 100644
--- a/android/app/src/main/res/drawable/ic_marker_east.xml
+++ b/android/app/src/main/res/drawable/ic_marker_east.xml
@@ -1,27 +1,18 @@
+ android:width="122dp"
+ android:height="85dp"
+ android:viewportWidth="122"
+ android:viewportHeight="85">
-
-
-
-
-
-
-
+ android:pathData="M16.15,0L105.3,0A16.15,16.15 0,0 1,121.45 16.15L121.45,16.15A16.15,16.15 0,0 1,105.3 32.3L16.15,32.3A16.15,16.15 0,0 1,0 16.15L0,16.15A16.15,16.15 0,0 1,16.15 0z"
+ android:fillColor="#0B0726"/>
+ android:pathData="M25.15,15.87H35.2V17.14H25.15V15.87ZM29.38,14.2H30.97V16.36H29.38V14.2ZM26.36,13.69H34.06V14.95H26.36V13.69ZM26.36,11.13H34V12.37H27.94V14.5H26.36V11.13ZM30.14,17.69C32.54,17.69 33.99,18.42 33.99,19.73C33.99,21.04 32.54,21.77 30.14,21.77C27.74,21.77 26.27,21.04 26.27,19.73C26.27,18.42 27.74,17.69 30.14,17.69ZM30.14,18.89C28.65,18.89 27.89,19.17 27.89,19.73C27.89,20.31 28.65,20.56 30.14,20.56C31.63,20.56 32.38,20.31 32.38,19.73C32.38,19.17 31.63,18.89 30.14,18.89ZM38.28,11.7H39.46V12.29C39.46,13.57 38.72,14.8 37.01,15.22L36.36,14.05C37.66,13.75 38.28,12.99 38.28,12.29V11.7ZM38.75,11.7H39.87V12.27C39.87,12.9 40.48,13.62 41.66,13.91L41.19,15.07C39.45,14.67 38.75,13.43 38.75,12.27V11.7ZM42.51,11.7H43.62V12.27C43.62,13.44 42.93,14.68 41.19,15.07L40.72,13.91C41.9,13.62 42.51,12.91 42.51,12.27V11.7ZM42.94,11.7H44.12V12.29C44.12,13.02 44.73,13.77 46.04,14.05L45.38,15.22C43.68,14.8 42.94,13.61 42.94,12.29V11.7ZM36.84,11.03H40.97V12.29H36.84V11.03ZM41.42,11.03H45.53V12.29H41.42V11.03ZM36.17,15.97H46.23V17.23H36.17V15.97ZM40.4,15.07H41.99V16.97H40.4V15.07ZM37.26,18.12H45.02V21.76H43.42V19.38H37.26V18.12ZM51.65,14.74H53.48V16.02H51.65V14.74ZM55.24,10.65H56.76V21.76H55.24V10.65ZM52.97,10.81H54.47V21.25H52.97V10.81ZM49.71,11.43C51.18,11.43 52.13,12.95 52.13,15.46C52.13,17.98 51.18,19.5 49.71,19.5C48.24,19.5 47.31,17.98 47.31,15.46C47.31,12.95 48.24,11.43 49.71,11.43ZM49.71,12.93C49.11,12.93 48.76,13.77 48.76,15.46C48.76,17.16 49.11,17.99 49.71,17.99C50.33,17.99 50.68,17.16 50.68,15.46C50.68,13.77 50.33,12.93 49.71,12.93ZM63.15,17.39H64.35V17.76C64.35,19.44 63.6,20.98 61.86,21.6L61.08,20.44C62.54,19.95 63.15,18.84 63.15,17.76V17.39ZM63.56,17.39H64.7V17.76C64.7,18.84 65.24,19.97 66.59,20.53L65.96,21.71C64.19,21.05 63.56,19.39 63.56,17.76V17.39ZM67.2,17.39H68.34V17.76C68.34,19.3 67.68,21.03 65.96,21.71L65.3,20.53C66.65,19.92 67.2,18.75 67.2,17.76V17.39ZM67.58,17.39H68.78V17.76C68.78,18.91 69.38,19.98 70.84,20.44L70.06,21.6C68.3,21 67.58,19.51 67.58,17.76V17.39ZM68.28,10.66H69.89V16.93H68.28V10.66ZM63.84,11.22C65.57,11.22 66.86,12.33 66.86,13.89C66.86,15.43 65.57,16.54 63.84,16.54C62.12,16.54 60.82,15.43 60.82,13.89C60.82,12.33 62.12,11.22 63.84,11.22ZM63.84,12.54C63,12.54 62.38,13.03 62.38,13.89C62.38,14.73 63,15.22 63.84,15.22C64.68,15.22 65.3,14.73 65.3,13.89C65.3,13.03 64.68,12.54 63.84,12.54ZM74.74,11.41C76.4,11.41 77.61,12.95 77.61,15.37C77.61,17.82 76.4,19.36 74.74,19.36C73.07,19.36 71.87,17.82 71.87,15.37C71.87,12.95 73.07,11.41 74.74,11.41ZM74.74,12.87C73.94,12.87 73.4,13.74 73.4,15.37C73.4,17.03 73.94,17.92 74.74,17.92C75.56,17.92 76.08,17.03 76.08,15.37C76.08,13.74 75.56,12.87 74.74,12.87ZM79.43,10.63H81.02V21.78H79.43V10.63ZM77.14,14.64H80.1V15.93H77.14V14.64ZM84.95,16.29H86.53V19.41H84.95V16.29ZM88.98,16.29H90.58V19.41H88.98V16.29ZM82.74,19.18H92.8V20.47H82.74V19.18ZM87.76,11.19C90.15,11.19 91.91,12.36 91.91,14.17C91.91,15.99 90.15,17.15 87.76,17.15C85.36,17.15 83.61,15.99 83.61,14.17C83.61,12.36 85.36,11.19 87.76,11.19ZM87.76,12.43C86.21,12.43 85.18,13.07 85.18,14.17C85.18,15.27 86.21,15.91 87.76,15.91C89.31,15.91 90.34,15.27 90.34,14.17C90.34,13.07 89.31,12.43 87.76,12.43ZM94.59,18.42L94.19,12.46H95.89L95.5,18.42H94.59ZM94.29,20.69V19.15H95.81V20.69H94.29Z"
+ android:fillColor="#ffffff"/>
+ android:pathData="M59.38,56.92L59.06,52.04C59.03,51.68 59.07,51.31 59.16,50.96L59.22,50.72C59.4,50.05 59.82,49.48 60.4,49.12C60.64,48.97 60.9,48.86 61.17,48.79L62.1,48.56C62.39,48.49 62.67,48.38 62.94,48.25L63.39,48.03C64.02,47.72 64.52,47.2 64.8,46.56C64.99,46.12 65.07,45.65 65.04,45.17L65,44.54C64.96,43.95 64.73,43.38 64.35,42.91C63.92,42.39 63.33,42.03 62.67,41.9L62.46,41.85C62.01,41.76 61.54,41.76 61.09,41.85L60.94,41.88C60.13,42.05 59.43,42.51 58.96,43.17C58.63,43.64 58.43,44.19 58.38,44.76L58.3,45.81C58.27,46.12 58.19,46.42 58.05,46.7C57.73,47.33 57.15,47.8 56.45,47.97L56.3,48.01C55.89,48.11 55.46,48.12 55.04,48.03L54.66,47.95C54.3,47.88 53.95,47.73 53.63,47.52C52.97,47.09 52.51,46.41 52.34,45.64L52.29,45.4C52.23,45.08 52.2,44.75 52.23,44.43L52.34,42.96L52.41,42.62C52.6,41.71 52.94,40.84 53.42,40.05C53.74,39.5 54.14,39 54.58,38.54L54.76,38.36C55.19,37.92 55.65,37.53 56.15,37.18L56.17,37.17C56.81,36.72 57.51,36.35 58.24,36.07C58.92,35.81 59.63,35.63 60.35,35.52L60.56,35.49C61.17,35.4 61.79,35.38 62.41,35.43L62.46,35.43C63.21,35.48 63.94,35.63 64.64,35.88C65.19,36.07 65.71,36.32 66.2,36.61L66.65,36.88C67.2,37.21 67.71,37.59 68.19,38.02C68.7,38.48 69.17,39 69.58,39.56L69.63,39.63C69.98,40.1 70.28,40.61 70.52,41.13C70.94,42.02 71.22,42.97 71.33,43.94L71.35,44.04C71.43,44.77 71.44,45.5 71.36,46.23L71.33,46.47C71.25,47.18 71.09,47.88 70.86,48.54C70.57,49.36 70.17,50.15 69.67,50.85C69.29,51.38 68.86,51.87 68.38,52.31L68.22,52.45C67.74,52.89 67.22,53.29 66.66,53.63L65.71,54.23C65.51,54.36 65.4,54.58 65.41,54.82L65.53,56.65C65.57,57.12 65.51,57.58 65.36,58.02L65.3,58.21C65.16,58.62 64.93,59 64.61,59.3C64.27,59.63 63.85,59.86 63.39,59.97L63.18,60.03C62.77,60.13 62.33,60.14 61.91,60.05L61.89,60.05C61.43,59.96 61.01,59.76 60.64,59.48C60.28,59.19 59.98,58.82 59.79,58.39L59.71,58.22C59.52,57.81 59.41,57.37 59.38,56.92Z"
+ android:fillColor="#0B0726"/>
+
diff --git a/android/app/src/main/res/drawable/ic_marker_north.xml b/android/app/src/main/res/drawable/ic_marker_north.xml
index deb9205a4..3382c191b 100644
--- a/android/app/src/main/res/drawable/ic_marker_north.xml
+++ b/android/app/src/main/res/drawable/ic_marker_north.xml
@@ -1,27 +1,18 @@
+ android:width="122dp"
+ android:height="85dp"
+ android:viewportWidth="122"
+ android:viewportHeight="85">
-
-
-
-
-
-
-
+ android:pathData="M16.15,0.16L105.3,0.16A16.15,16.15 0,0 1,121.45 16.31L121.45,16.31A16.15,16.15 0,0 1,105.3 32.46L16.15,32.46A16.15,16.15 0,0 1,0 16.31L0,16.31A16.15,16.15 0,0 1,16.15 0.16z"
+ android:fillColor="#0B0726"/>
+ android:pathData="M29.35,17.06H30.93V18.74H29.35V17.06ZM25.13,16.1H35.18V17.36H25.13V16.1ZM26.25,18.29H33.97V21.92H32.37V19.55H26.25V18.29ZM26.42,11.08H27.99V12.07H32.32V11.08H33.91V15.38H26.42V11.08ZM27.99,13.25V14.13H32.32V13.25H27.99ZM38.28,11.86H39.46V12.45C39.46,13.73 38.72,14.96 37.01,15.38L36.36,14.21C37.66,13.91 38.28,13.15 38.28,12.45V11.86ZM38.75,11.86H39.87V12.43C39.87,13.06 40.48,13.78 41.66,14.07L41.19,15.23C39.45,14.83 38.75,13.59 38.75,12.43V11.86ZM42.51,11.86H43.62V12.43C43.62,13.6 42.93,14.84 41.19,15.23L40.72,14.07C41.9,13.78 42.51,13.07 42.51,12.43V11.86ZM42.94,11.86H44.12V12.45C44.12,13.18 44.73,13.93 46.04,14.21L45.38,15.38C43.68,14.96 42.94,13.77 42.94,12.45V11.86ZM36.84,11.19H40.97V12.45H36.84V11.19ZM41.42,11.19H45.53V12.45H41.42V11.19ZM36.17,16.13H46.23V17.39H36.17V16.13ZM40.4,15.23H41.99V17.13H40.4V15.23ZM37.26,18.28H45.02V21.92H43.42V19.54H37.26V18.28ZM51.65,14.9H53.48V16.18H51.65V14.9ZM55.24,10.81H56.76V21.92H55.24V10.81ZM52.97,10.97H54.47V21.41H52.97V10.97ZM49.71,11.59C51.18,11.59 52.13,13.11 52.13,15.62C52.13,18.14 51.18,19.66 49.71,19.66C48.24,19.66 47.31,18.14 47.31,15.62C47.31,13.11 48.24,11.59 49.71,11.59ZM49.71,13.09C49.11,13.09 48.76,13.93 48.76,15.62C48.76,17.32 49.11,18.15 49.71,18.15C50.33,18.15 50.68,17.32 50.68,15.62C50.68,13.93 50.33,13.09 49.71,13.09ZM63.15,17.55H64.35V17.92C64.35,19.6 63.6,21.14 61.86,21.76L61.08,20.6C62.54,20.11 63.15,19 63.15,17.92V17.55ZM63.56,17.55H64.7V17.92C64.7,19 65.24,20.13 66.59,20.69L65.96,21.87C64.19,21.21 63.56,19.55 63.56,17.92V17.55ZM67.2,17.55H68.34V17.92C68.34,19.46 67.68,21.19 65.96,21.87L65.3,20.69C66.65,20.08 67.2,18.91 67.2,17.92V17.55ZM67.58,17.55H68.78V17.92C68.78,19.07 69.38,20.14 70.84,20.6L70.06,21.76C68.3,21.16 67.58,19.67 67.58,17.92V17.55ZM68.28,10.82H69.89V17.09H68.28V10.82ZM63.84,11.38C65.57,11.38 66.86,12.49 66.86,14.05C66.86,15.59 65.57,16.7 63.84,16.7C62.12,16.7 60.82,15.59 60.82,14.05C60.82,12.49 62.12,11.38 63.84,11.38ZM63.84,12.7C63,12.7 62.38,13.19 62.38,14.05C62.38,14.89 63,15.38 63.84,15.38C64.68,15.38 65.3,14.89 65.3,14.05C65.3,13.19 64.68,12.7 63.84,12.7ZM74.74,11.57C76.4,11.57 77.61,13.11 77.61,15.53C77.61,17.98 76.4,19.52 74.74,19.52C73.07,19.52 71.87,17.98 71.87,15.53C71.87,13.11 73.07,11.57 74.74,11.57ZM74.74,13.03C73.94,13.03 73.4,13.9 73.4,15.53C73.4,17.19 73.94,18.08 74.74,18.08C75.56,18.08 76.08,17.19 76.08,15.53C76.08,13.9 75.56,13.03 74.74,13.03ZM79.43,10.79H81.02V21.94H79.43V10.79ZM77.14,14.8H80.1V16.09H77.14V14.8ZM84.95,16.45H86.53V19.57H84.95V16.45ZM88.98,16.45H90.58V19.57H88.98V16.45ZM82.74,19.34H92.8V20.63H82.74V19.34ZM87.76,11.35C90.15,11.35 91.91,12.52 91.91,14.33C91.91,16.15 90.15,17.31 87.76,17.31C85.36,17.31 83.61,16.15 83.61,14.33C83.61,12.52 85.36,11.35 87.76,11.35ZM87.76,12.59C86.21,12.59 85.18,13.23 85.18,14.33C85.18,15.43 86.21,16.07 87.76,16.07C89.31,16.07 90.34,15.43 90.34,14.33C90.34,13.23 89.31,12.59 87.76,12.59ZM94.59,18.58L94.19,12.62H95.89L95.5,18.58H94.59ZM94.29,20.85V19.31H95.81V20.85H94.29Z"
+ android:fillColor="#ffffff"/>
+ android:pathData="M59.38,57.08L59.06,52.2C59.03,51.84 59.07,51.47 59.16,51.12L59.22,50.88C59.4,50.21 59.82,49.64 60.4,49.28C60.64,49.13 60.9,49.02 61.17,48.95L62.1,48.72C62.39,48.65 62.67,48.54 62.94,48.41L63.39,48.19C64.02,47.88 64.52,47.36 64.8,46.72C64.99,46.28 65.07,45.81 65.04,45.33L65,44.7C64.96,44.11 64.73,43.54 64.35,43.07C63.92,42.55 63.33,42.19 62.67,42.06L62.46,42.01C62.01,41.92 61.54,41.92 61.09,42.01L60.94,42.04C60.13,42.21 59.43,42.67 58.96,43.33C58.63,43.8 58.43,44.35 58.38,44.92L58.3,45.97C58.27,46.28 58.19,46.58 58.05,46.86C57.73,47.5 57.15,47.96 56.45,48.13L56.3,48.17C55.89,48.27 55.46,48.28 55.04,48.19L54.66,48.11C54.3,48.04 53.95,47.89 53.63,47.68C52.97,47.25 52.51,46.57 52.34,45.8L52.29,45.56C52.23,45.24 52.2,44.91 52.23,44.59L52.34,43.12L52.41,42.78C52.6,41.87 52.94,41 53.42,40.21C53.74,39.66 54.14,39.16 54.58,38.7L54.76,38.52C55.19,38.08 55.65,37.69 56.15,37.34L56.17,37.33C56.81,36.88 57.51,36.51 58.24,36.23C58.92,35.97 59.63,35.79 60.35,35.68L60.56,35.65C61.17,35.56 61.79,35.54 62.41,35.59L62.46,35.59C63.21,35.64 63.94,35.79 64.64,36.04C65.19,36.23 65.71,36.48 66.2,36.77L66.65,37.04C67.2,37.37 67.71,37.75 68.19,38.18C68.7,38.64 69.17,39.16 69.58,39.72L69.63,39.79C69.98,40.26 70.28,40.77 70.52,41.29C70.94,42.18 71.22,43.13 71.33,44.1L71.35,44.2C71.43,44.93 71.44,45.66 71.36,46.39L71.33,46.63C71.25,47.34 71.09,48.04 70.86,48.7C70.57,49.52 70.17,50.31 69.67,51.01C69.29,51.54 68.86,52.03 68.38,52.47L68.22,52.61C67.74,53.05 67.22,53.45 66.66,53.79L65.71,54.39C65.51,54.52 65.4,54.74 65.41,54.98L65.53,56.81C65.57,57.28 65.51,57.74 65.36,58.18L65.3,58.37C65.16,58.78 64.93,59.16 64.61,59.46C64.27,59.79 63.85,60.02 63.39,60.13L63.18,60.19C62.77,60.29 62.33,60.3 61.91,60.21L61.89,60.21C61.43,60.12 61.01,59.92 60.64,59.64C60.28,59.35 59.98,58.98 59.79,58.55L59.71,58.38C59.52,57.97 59.41,57.53 59.38,57.08Z"
+ android:fillColor="#0B0726"/>
+
diff --git a/android/app/src/main/res/drawable/ic_marker_south.xml b/android/app/src/main/res/drawable/ic_marker_south.xml
index 2ab524e65..3fc91584f 100644
--- a/android/app/src/main/res/drawable/ic_marker_south.xml
+++ b/android/app/src/main/res/drawable/ic_marker_south.xml
@@ -1,27 +1,18 @@
+ android:width="122dp"
+ android:height="85dp"
+ android:viewportWidth="122"
+ android:viewportHeight="85">
-
-
-
-
-
-
-
+ android:pathData="M16.15,0L105.3,0A16.15,16.15 0,0 1,121.45 16.15L121.45,16.15A16.15,16.15 0,0 1,105.3 32.3L16.15,32.3A16.15,16.15 0,0 1,0 16.15L0,16.15A16.15,16.15 0,0 1,16.15 0z"
+ android:fillColor="#0B0726"/>
+ android:pathData="M32.29,10.65H33.88V16.91H32.29V10.65ZM33.45,12.99H35.36V14.28H33.45V12.99ZM26.65,17.41H33.88V21.64H26.65V17.41ZM32.32,18.66H28.22V20.38H32.32V18.66ZM25.58,11.17H27.17V15.72H25.58V11.17ZM25.58,15.04H26.51C28.03,15.04 29.67,14.93 31.42,14.59L31.6,15.88C29.79,16.25 28.09,16.35 26.51,16.35H25.58V15.04ZM38.28,11.7H39.46V12.29C39.46,13.57 38.72,14.8 37.01,15.22L36.36,14.05C37.66,13.75 38.28,12.99 38.28,12.29V11.7ZM38.75,11.7H39.87V12.27C39.87,12.9 40.48,13.62 41.66,13.91L41.19,15.07C39.45,14.67 38.75,13.43 38.75,12.27V11.7ZM42.51,11.7H43.62V12.27C43.62,13.44 42.93,14.68 41.19,15.07L40.72,13.91C41.9,13.62 42.51,12.91 42.51,12.27V11.7ZM42.94,11.7H44.12V12.29C44.12,13.02 44.73,13.77 46.04,14.05L45.38,15.22C43.68,14.8 42.94,13.61 42.94,12.29V11.7ZM36.84,11.03H40.97V12.29H36.84V11.03ZM41.42,11.03H45.53V12.29H41.42V11.03ZM36.17,15.97H46.23V17.23H36.17V15.97ZM40.4,15.07H41.99V16.97H40.4V15.07ZM37.26,18.12H45.02V21.76H43.42V19.38H37.26V18.12ZM51.65,14.74H53.48V16.02H51.65V14.74ZM55.24,10.65H56.76V21.76H55.24V10.65ZM52.97,10.81H54.47V21.25H52.97V10.81ZM49.71,11.43C51.18,11.43 52.13,12.95 52.13,15.46C52.13,17.98 51.18,19.5 49.71,19.5C48.24,19.5 47.31,17.98 47.31,15.46C47.31,12.95 48.24,11.43 49.71,11.43ZM49.71,12.93C49.11,12.93 48.76,13.77 48.76,15.46C48.76,17.16 49.11,17.99 49.71,17.99C50.33,17.99 50.68,17.16 50.68,15.46C50.68,13.77 50.33,12.93 49.71,12.93ZM63.15,17.39H64.35V17.76C64.35,19.44 63.6,20.98 61.86,21.6L61.08,20.44C62.54,19.95 63.15,18.84 63.15,17.76V17.39ZM63.56,17.39H64.7V17.76C64.7,18.84 65.24,19.97 66.59,20.53L65.96,21.71C64.19,21.05 63.56,19.39 63.56,17.76V17.39ZM67.2,17.39H68.34V17.76C68.34,19.3 67.68,21.03 65.96,21.71L65.3,20.53C66.65,19.92 67.2,18.75 67.2,17.76V17.39ZM67.58,17.39H68.78V17.76C68.78,18.91 69.38,19.98 70.84,20.44L70.06,21.6C68.3,21 67.58,19.51 67.58,17.76V17.39ZM68.28,10.66H69.89V16.93H68.28V10.66ZM63.84,11.22C65.57,11.22 66.86,12.33 66.86,13.89C66.86,15.43 65.57,16.54 63.84,16.54C62.12,16.54 60.82,15.43 60.82,13.89C60.82,12.33 62.12,11.22 63.84,11.22ZM63.84,12.54C63,12.54 62.38,13.03 62.38,13.89C62.38,14.73 63,15.22 63.84,15.22C64.68,15.22 65.3,14.73 65.3,13.89C65.3,13.03 64.68,12.54 63.84,12.54ZM74.74,11.41C76.4,11.41 77.61,12.95 77.61,15.37C77.61,17.82 76.4,19.36 74.74,19.36C73.07,19.36 71.87,17.82 71.87,15.37C71.87,12.95 73.07,11.41 74.74,11.41ZM74.74,12.87C73.94,12.87 73.4,13.74 73.4,15.37C73.4,17.03 73.94,17.92 74.74,17.92C75.56,17.92 76.08,17.03 76.08,15.37C76.08,13.74 75.56,12.87 74.74,12.87ZM79.43,10.63H81.02V21.78H79.43V10.63ZM77.14,14.64H80.1V15.93H77.14V14.64ZM84.95,16.29H86.53V19.41H84.95V16.29ZM88.98,16.29H90.58V19.41H88.98V16.29ZM82.74,19.18H92.8V20.47H82.74V19.18ZM87.76,11.19C90.15,11.19 91.91,12.36 91.91,14.17C91.91,15.99 90.15,17.15 87.76,17.15C85.36,17.15 83.61,15.99 83.61,14.17C83.61,12.36 85.36,11.19 87.76,11.19ZM87.76,12.43C86.21,12.43 85.18,13.07 85.18,14.17C85.18,15.27 86.21,15.91 87.76,15.91C89.31,15.91 90.34,15.27 90.34,14.17C90.34,13.07 89.31,12.43 87.76,12.43ZM94.59,18.42L94.19,12.46H95.89L95.5,18.42H94.59ZM94.29,20.69V19.15H95.81V20.69H94.29Z"
+ android:fillColor="#ffffff"/>
+ android:pathData="M59.38,56.92L59.06,52.04C59.03,51.68 59.07,51.31 59.16,50.96L59.22,50.72C59.4,50.05 59.82,49.48 60.4,49.12C60.64,48.97 60.9,48.86 61.17,48.79L62.1,48.56C62.39,48.49 62.67,48.38 62.94,48.25L63.39,48.03C64.02,47.72 64.52,47.2 64.8,46.56C64.99,46.12 65.07,45.65 65.04,45.17L65,44.54C64.96,43.95 64.73,43.38 64.35,42.91C63.92,42.39 63.33,42.03 62.67,41.9L62.46,41.85C62.01,41.76 61.54,41.76 61.09,41.85L60.94,41.88C60.13,42.05 59.43,42.51 58.96,43.17C58.63,43.64 58.43,44.19 58.38,44.76L58.3,45.81C58.27,46.12 58.19,46.42 58.05,46.7C57.73,47.33 57.15,47.8 56.45,47.97L56.3,48.01C55.89,48.11 55.46,48.12 55.04,48.03L54.66,47.95C54.3,47.88 53.95,47.73 53.63,47.52C52.97,47.09 52.51,46.41 52.34,45.64L52.29,45.4C52.23,45.08 52.2,44.75 52.23,44.43L52.34,42.96L52.41,42.62C52.6,41.71 52.94,40.84 53.42,40.05C53.74,39.5 54.14,39 54.58,38.54L54.76,38.36C55.19,37.92 55.65,37.53 56.15,37.18L56.17,37.17C56.81,36.72 57.51,36.35 58.24,36.07C58.92,35.81 59.63,35.63 60.35,35.52L60.56,35.49C61.17,35.4 61.79,35.38 62.41,35.43L62.46,35.43C63.21,35.48 63.94,35.63 64.64,35.88C65.19,36.07 65.71,36.32 66.2,36.61L66.65,36.88C67.2,37.21 67.71,37.59 68.19,38.02C68.7,38.48 69.17,39 69.58,39.56L69.63,39.63C69.98,40.1 70.28,40.61 70.52,41.13C70.94,42.02 71.22,42.97 71.33,43.94L71.35,44.04C71.43,44.77 71.44,45.5 71.36,46.23L71.33,46.47C71.25,47.18 71.09,47.88 70.86,48.54C70.57,49.36 70.17,50.15 69.67,50.85C69.29,51.38 68.86,51.87 68.38,52.31L68.22,52.45C67.74,52.89 67.22,53.29 66.66,53.63L65.71,54.23C65.51,54.36 65.4,54.58 65.41,54.82L65.53,56.65C65.57,57.12 65.51,57.58 65.36,58.02L65.3,58.21C65.16,58.62 64.93,59 64.61,59.3C64.27,59.63 63.85,59.86 63.39,59.97L63.18,60.03C62.77,60.13 62.33,60.14 61.91,60.05L61.89,60.05C61.43,59.96 61.01,59.76 60.64,59.48C60.28,59.19 59.98,58.82 59.79,58.39L59.71,58.22C59.52,57.81 59.41,57.37 59.38,56.92Z"
+ android:fillColor="#0B0726"/>
+
diff --git a/android/app/src/main/res/drawable/ic_marker_west.xml b/android/app/src/main/res/drawable/ic_marker_west.xml
index ba35acdc1..e7f9e0364 100644
--- a/android/app/src/main/res/drawable/ic_marker_west.xml
+++ b/android/app/src/main/res/drawable/ic_marker_west.xml
@@ -1,27 +1,18 @@
+ android:width="122dp"
+ android:height="85dp"
+ android:viewportWidth="122"
+ android:viewportHeight="85">
-
-
-
-
-
-
-
+ android:pathData="M16.15,0.16L105.3,0.16A16.15,16.15 0,0 1,121.45 16.31L121.45,16.31A16.15,16.15 0,0 1,105.3 32.46L16.15,32.46A16.15,16.15 0,0 1,0 16.31L0,16.31A16.15,16.15 0,0 1,16.15 0.16z"
+ android:fillColor="#0B0726"/>
+ android:pathData="M30.74,14.27H33.67V15.56H30.74V14.27ZM27.71,11.66H29.01V13.27C29.01,16.06 28.06,18.64 25.97,19.69L24.98,18.4C26.87,17.53 27.71,15.38 27.71,13.27V11.66ZM28.05,11.66H29.32V13.27C29.32,15.38 30.13,17.41 31.96,18.22L31.01,19.49C28.93,18.49 28.05,16.04 28.05,13.27V11.66ZM32.87,10.79H34.46V21.94H32.87V10.79ZM38.28,11.86H39.46V12.45C39.46,13.73 38.72,14.96 37.01,15.38L36.36,14.21C37.66,13.91 38.28,13.15 38.28,12.45V11.86ZM38.75,11.86H39.87V12.43C39.87,13.06 40.48,13.78 41.66,14.07L41.19,15.23C39.45,14.83 38.75,13.59 38.75,12.43V11.86ZM42.51,11.86H43.62V12.43C43.62,13.6 42.93,14.84 41.19,15.23L40.72,14.07C41.9,13.78 42.51,13.07 42.51,12.43V11.86ZM42.94,11.86H44.12V12.45C44.12,13.18 44.73,13.93 46.04,14.21L45.38,15.38C43.68,14.96 42.94,13.77 42.94,12.45V11.86ZM36.84,11.19H40.97V12.45H36.84V11.19ZM41.42,11.19H45.53V12.45H41.42V11.19ZM36.17,16.13H46.23V17.39H36.17V16.13ZM40.4,15.23H41.99V17.13H40.4V15.23ZM37.26,18.28H45.02V21.92H43.42V19.54H37.26V18.28ZM51.65,14.9H53.48V16.18H51.65V14.9ZM55.24,10.81H56.76V21.92H55.24V10.81ZM52.97,10.97H54.47V21.41H52.97V10.97ZM49.71,11.59C51.18,11.59 52.13,13.11 52.13,15.62C52.13,18.14 51.18,19.66 49.71,19.66C48.24,19.66 47.31,18.14 47.31,15.62C47.31,13.11 48.24,11.59 49.71,11.59ZM49.71,13.09C49.11,13.09 48.76,13.93 48.76,15.62C48.76,17.32 49.11,18.15 49.71,18.15C50.33,18.15 50.68,17.32 50.68,15.62C50.68,13.93 50.33,13.09 49.71,13.09ZM63.15,17.55H64.35V17.92C64.35,19.6 63.6,21.14 61.86,21.76L61.08,20.6C62.54,20.11 63.15,19 63.15,17.92V17.55ZM63.56,17.55H64.7V17.92C64.7,19 65.24,20.13 66.59,20.69L65.96,21.87C64.19,21.21 63.56,19.55 63.56,17.92V17.55ZM67.2,17.55H68.34V17.92C68.34,19.46 67.68,21.19 65.96,21.87L65.3,20.69C66.65,20.08 67.2,18.91 67.2,17.92V17.55ZM67.58,17.55H68.78V17.92C68.78,19.07 69.38,20.14 70.84,20.6L70.06,21.76C68.3,21.16 67.58,19.67 67.58,17.92V17.55ZM68.28,10.82H69.89V17.09H68.28V10.82ZM63.84,11.38C65.57,11.38 66.86,12.49 66.86,14.05C66.86,15.59 65.57,16.7 63.84,16.7C62.12,16.7 60.82,15.59 60.82,14.05C60.82,12.49 62.12,11.38 63.84,11.38ZM63.84,12.7C63,12.7 62.38,13.19 62.38,14.05C62.38,14.89 63,15.38 63.84,15.38C64.68,15.38 65.3,14.89 65.3,14.05C65.3,13.19 64.68,12.7 63.84,12.7ZM74.74,11.57C76.4,11.57 77.61,13.11 77.61,15.53C77.61,17.98 76.4,19.52 74.74,19.52C73.07,19.52 71.87,17.98 71.87,15.53C71.87,13.11 73.07,11.57 74.74,11.57ZM74.74,13.03C73.94,13.03 73.4,13.9 73.4,15.53C73.4,17.19 73.94,18.08 74.74,18.08C75.56,18.08 76.08,17.19 76.08,15.53C76.08,13.9 75.56,13.03 74.74,13.03ZM79.43,10.79H81.02V21.94H79.43V10.79ZM77.14,14.8H80.1V16.09H77.14V14.8ZM84.95,16.45H86.53V19.57H84.95V16.45ZM88.98,16.45H90.58V19.57H88.98V16.45ZM82.74,19.34H92.8V20.63H82.74V19.34ZM87.76,11.35C90.15,11.35 91.91,12.52 91.91,14.33C91.91,16.15 90.15,17.31 87.76,17.31C85.36,17.31 83.61,16.15 83.61,14.33C83.61,12.52 85.36,11.35 87.76,11.35ZM87.76,12.59C86.21,12.59 85.18,13.23 85.18,14.33C85.18,15.43 86.21,16.07 87.76,16.07C89.31,16.07 90.34,15.43 90.34,14.33C90.34,13.23 89.31,12.59 87.76,12.59ZM94.59,18.58L94.19,12.62H95.89L95.5,18.58H94.59ZM94.29,20.85V19.31H95.81V20.85H94.29Z"
+ android:fillColor="#ffffff"/>
+ android:pathData="M59.38,57.08L59.06,52.2C59.03,51.84 59.07,51.47 59.16,51.12L59.22,50.88C59.4,50.21 59.82,49.64 60.4,49.28C60.64,49.13 60.9,49.02 61.17,48.95L62.1,48.72C62.39,48.65 62.67,48.54 62.94,48.41L63.39,48.19C64.02,47.88 64.52,47.36 64.8,46.72C64.99,46.28 65.07,45.81 65.04,45.33L65,44.7C64.96,44.11 64.73,43.54 64.35,43.07C63.92,42.55 63.33,42.19 62.67,42.06L62.46,42.01C62.01,41.92 61.54,41.92 61.09,42.01L60.94,42.04C60.13,42.21 59.43,42.67 58.96,43.33C58.63,43.8 58.43,44.35 58.38,44.92L58.3,45.97C58.27,46.28 58.19,46.58 58.05,46.86C57.73,47.5 57.15,47.96 56.45,48.13L56.3,48.17C55.89,48.27 55.46,48.28 55.04,48.19L54.66,48.11C54.3,48.04 53.95,47.89 53.63,47.68C52.97,47.25 52.51,46.57 52.34,45.8L52.29,45.56C52.23,45.24 52.2,44.91 52.23,44.59L52.34,43.12L52.41,42.78C52.6,41.87 52.94,41 53.42,40.21C53.74,39.66 54.14,39.16 54.58,38.7L54.76,38.52C55.19,38.08 55.65,37.69 56.15,37.34L56.17,37.33C56.81,36.88 57.51,36.51 58.24,36.23C58.92,35.97 59.63,35.79 60.35,35.68L60.56,35.65C61.17,35.56 61.79,35.54 62.41,35.59L62.46,35.59C63.21,35.64 63.94,35.79 64.64,36.04C65.19,36.23 65.71,36.48 66.2,36.77L66.65,37.04C67.2,37.37 67.71,37.75 68.19,38.18C68.7,38.64 69.17,39.16 69.58,39.72L69.63,39.79C69.98,40.26 70.28,40.77 70.52,41.29C70.94,42.18 71.22,43.13 71.33,44.1L71.35,44.2C71.43,44.93 71.44,45.66 71.36,46.39L71.33,46.63C71.25,47.34 71.09,48.04 70.86,48.7C70.57,49.52 70.17,50.31 69.67,51.01C69.29,51.54 68.86,52.03 68.38,52.47L68.22,52.61C67.74,53.05 67.22,53.45 66.66,53.79L65.71,54.39C65.51,54.52 65.4,54.74 65.41,54.98L65.53,56.81C65.57,57.28 65.51,57.74 65.36,58.18L65.3,58.37C65.16,58.78 64.93,59.16 64.61,59.46C64.27,59.79 63.85,60.02 63.39,60.13L63.18,60.19C62.77,60.29 62.33,60.3 61.91,60.21L61.89,60.21C61.43,60.12 61.01,59.92 60.64,59.64C60.28,59.35 59.98,58.98 59.79,58.55L59.71,58.38C59.52,57.97 59.41,57.53 59.38,57.08Z"
+ android:fillColor="#0B0726"/>
+
diff --git a/android/app/src/main/res/drawable/ic_message_received.xml b/android/app/src/main/res/drawable/ic_message_received.xml
new file mode 100644
index 000000000..712fe8b99
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_message_received.xml
@@ -0,0 +1,14 @@
+
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_message_send.xml b/android/app/src/main/res/drawable/ic_message_send.xml
new file mode 100644
index 000000000..34cbfc6c7
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_message_send.xml
@@ -0,0 +1,14 @@
+
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_mypage.xml b/android/app/src/main/res/drawable/ic_mypage.xml
deleted file mode 100644
index 04771b46a..000000000
--- a/android/app/src/main/res/drawable/ic_mypage.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
diff --git a/android/app/src/main/res/drawable/ic_naaga_logo.xml b/android/app/src/main/res/drawable/ic_naaga_logo.xml
new file mode 100644
index 000000000..e2548c1e3
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_naaga_logo.xml
@@ -0,0 +1,728 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_setting.xml b/android/app/src/main/res/drawable/ic_setting.xml
new file mode 100644
index 000000000..691f41129
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_setting.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_star.xml b/android/app/src/main/res/drawable/ic_star.xml
new file mode 100644
index 000000000..b68ee9a6b
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_star.xml
@@ -0,0 +1,910 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_stop.xml b/android/app/src/main/res/drawable/ic_stop.xml
new file mode 100644
index 000000000..d2745ce60
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_stop.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_success.xml b/android/app/src/main/res/drawable/ic_success.xml
deleted file mode 100644
index c606aee66..000000000
--- a/android/app/src/main/res/drawable/ic_success.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
diff --git a/android/app/src/main/res/drawable/ic_success_adventure.xml b/android/app/src/main/res/drawable/ic_success_adventure.xml
new file mode 100644
index 000000000..c16f083ae
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_success_adventure.xml
@@ -0,0 +1,14 @@
+
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_success_label.xml b/android/app/src/main/res/drawable/ic_success_label.xml
new file mode 100644
index 000000000..a9eb30db8
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_success_label.xml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_upload.xml b/android/app/src/main/res/drawable/ic_upload.xml
deleted file mode 100644
index fefac2583..000000000
--- a/android/app/src/main/res/drawable/ic_upload.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/android/app/src/main/res/drawable/naaga_icon_background.xml b/android/app/src/main/res/drawable/naaga_icon_background.xml
new file mode 100644
index 000000000..e009ebe7e
--- /dev/null
+++ b/android/app/src/main/res/drawable/naaga_icon_background.xml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/naaga_icon_foreground.xml b/android/app/src/main/res/drawable/naaga_icon_foreground.xml
new file mode 100644
index 000000000..d1cc549ee
--- /dev/null
+++ b/android/app/src/main/res/drawable/naaga_icon_foreground.xml
@@ -0,0 +1,733 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/oval_blue_gradient.xml b/android/app/src/main/res/drawable/oval_blue_gradient.xml
new file mode 100644
index 000000000..ff3354c75
--- /dev/null
+++ b/android/app/src/main/res/drawable/oval_blue_gradient.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/android/app/src/main/res/drawable/oval_orange_gradient.xml b/android/app/src/main/res/drawable/oval_orange_gradient.xml
new file mode 100644
index 000000000..120a3e511
--- /dev/null
+++ b/android/app/src/main/res/drawable/oval_orange_gradient.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/android/app/src/main/res/drawable/oval_yellow_gradient.xml b/android/app/src/main/res/drawable/oval_yellow_gradient.xml
new file mode 100644
index 000000000..fa763a897
--- /dev/null
+++ b/android/app/src/main/res/drawable/oval_yellow_gradient.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/android/app/src/main/res/drawable/rect_main_gradient.xml b/android/app/src/main/res/drawable/rect_main_gradient.xml
deleted file mode 100644
index 8d1b47b1c..000000000
--- a/android/app/src/main/res/drawable/rect_main_gradient.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
diff --git a/android/app/src/main/res/drawable/rect_radius_small_white.xml b/android/app/src/main/res/drawable/rect_radius_small_white.xml
deleted file mode 100644
index ed6ce6653..000000000
--- a/android/app/src/main/res/drawable/rect_radius_small_white.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/android/app/src/main/res/font/main_font.otf b/android/app/src/main/res/font/main_font.otf
new file mode 100644
index 000000000..7bf60ffe2
Binary files /dev/null and b/android/app/src/main/res/font/main_font.otf differ
diff --git a/android/app/src/main/res/layout/activity_adventure_history.xml b/android/app/src/main/res/layout/activity_adventure_history.xml
index dcc468b50..8d5d45c38 100644
--- a/android/app/src/main/res/layout/activity_adventure_history.xml
+++ b/android/app/src/main/res/layout/activity_adventure_history.xml
@@ -13,33 +13,30 @@
android:id="@+id/iv_adventureHistory_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginStart="24dp"
- android:layout_marginTop="24dp"
+ android:layout_marginStart="8dp"
+ android:layout_marginTop="16dp"
android:paddingHorizontal="16dp"
android:paddingVertical="12dp"
+ android:src="@drawable/ic_arrow"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- android:src="@drawable/ic_arrow" />
+ app:layout_constraintTop_toTopOf="parent" />
+ app:layout_constraintTop_toTopOf="@+id/iv_adventureHistory_back" />
+ app:layout_constraintTop_toTopOf="parent" />
+ tools:src="@drawable/ic_success_label" />
@@ -87,9 +84,8 @@
android:id="@+id/tv_adventureResult_timeTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:fontFamily="@font/pretendard_bold"
android:text="@string/adventureResult_time_description"
- android:textColor="@color/black"
+ android:textColor="@color/main_gray"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -99,16 +95,14 @@
android:id="@+id/tv_adventureResult_playTime"
android:layout_width="0dp"
android:layout_height="wrap_content"
- android:text="@{@string/adventureResult_play_time_value(viewModel.adventureResult.playTime)}"
- android:fontFamily="@font/pretendard_bold"
- android:textColor="@color/white"
- android:textSize="20sp"
+ android:layout_marginStart="100dp"
+ android:background="@drawable/bg_blue_gradient"
android:gravity="end"
- android:background="@drawable/rect_radius_small"
- android:backgroundTint="@color/main_navy"
- android:layout_marginStart="40dp"
android:paddingVertical="4dp"
android:paddingEnd="@dimen/space_default_medium"
+ android:text="@{@string/adventureResult_play_time_value(viewModel.adventureResult.playTime)}"
+ android:textColor="@color/white"
+ android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/tv_adventureResult_timeTitle"
@@ -132,9 +126,8 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ app:layout_constraintTop_toTopOf="parent" />
-
-
-
-
-
-
-
+ app:layout_constraintStart_toStartOf="parent" />
-
+
-
+
+
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent" />
diff --git a/android/app/src/main/res/layout/activity_login.xml b/android/app/src/main/res/layout/activity_login.xml
index 22ae6cd47..88d162362 100644
--- a/android/app/src/main/res/layout/activity_login.xml
+++ b/android/app/src/main/res/layout/activity_login.xml
@@ -6,7 +6,6 @@
-
+ app:layout_constraintTop_toTopOf="parent" />
+
+
+
+
diff --git a/android/app/src/main/res/layout/activity_my_page.xml b/android/app/src/main/res/layout/activity_my_page.xml
index e33d66e3c..ad4f2fd62 100644
--- a/android/app/src/main/res/layout/activity_my_page.xml
+++ b/android/app/src/main/res/layout/activity_my_page.xml
@@ -36,84 +36,69 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/space_default_large"
- android:fontFamily="@font/pretendard_bold"
+ android:layout_marginBottom="@dimen/space_default_medium"
+ android:ellipsize="end"
android:includeFontPadding="false"
+ android:maxLines="1"
android:text="@{viewModel.rank.player.nickname}"
android:textColor="@color/white"
android:textSize="52sp"
- android:maxLines="1"
- android:ellipsize="end"
- app:layout_constraintEnd_toStartOf="@id/btn_mypage_adventure_results"
+ app:layout_constraintBottom_toTopOf="@id/rv_mypage_statistics"
+ app:layout_constraintEnd_toEndOf="@id/g_mypage"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_mypage_back"
tools:text="krrrrrrrrrrrrrrrrrong" />
-
-
-
-
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="@id/g_mypage"
+ app:layout_constraintTop_toTopOf="@id/tv_mypage_nickname" />
-
+ android:layout_marginTop="@dimen/space_default_large"
+ android:orientation="vertical"
+ app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
+ app:layout_constraintBottom_toTopOf="@id/customGrid_mypage_places"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/tv_mypage_nickname"
+ tools:itemCount="3"
+ tools:listitem="@layout/rv_mypage_item_adventure" />
-
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/rv_mypage_statistics" />
+
+
diff --git a/android/app/src/main/res/layout/activity_on_adventure.xml b/android/app/src/main/res/layout/activity_on_adventure.xml
index 67f06b23d..40dca9a64 100644
--- a/android/app/src/main/res/layout/activity_on_adventure.xml
+++ b/android/app/src/main/res/layout/activity_on_adventure.xml
@@ -5,6 +5,8 @@
+
+
@@ -30,20 +32,78 @@
app:lottie_loop="true"
app:lottie_autoPlay="true" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ app:layout_constraintTop_toBottomOf="@id/cl_onAdventure_top" />
+ app:layout_constraintTop_toTopOf="@id/fcv_onAdventure_map"
+ app:layout_constraintStart_toStartOf="@id/fcv_onAdventure_map" />
-
-
-
-
-
-
-
-
-
-
-
-
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent">
-
+
+
+
+
+
+
+
+
+
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent">
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/layout/activity_rank.xml b/android/app/src/main/res/layout/activity_rank.xml
index 645d2c17e..adb0e540f 100644
--- a/android/app/src/main/res/layout/activity_rank.xml
+++ b/android/app/src/main/res/layout/activity_rank.xml
@@ -12,7 +12,6 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/layout/activity_splash.xml b/android/app/src/main/res/layout/activity_splash.xml
index d8cc8e456..80463b889 100644
--- a/android/app/src/main/res/layout/activity_splash.xml
+++ b/android/app/src/main/res/layout/activity_splash.xml
@@ -1,8 +1,21 @@
-
\ No newline at end of file
+
+
+
diff --git a/android/app/src/main/res/layout/activity_upload.xml b/android/app/src/main/res/layout/activity_upload.xml
index 53d696cb8..ab5a7ada0 100644
--- a/android/app/src/main/res/layout/activity_upload.xml
+++ b/android/app/src/main/res/layout/activity_upload.xml
@@ -18,17 +18,55 @@
android:layout_marginTop="@dimen/space_default_large"
tools:context=".presentation.upload.UploadActivity">
+
+
+
+
+
+
+ app:layout_constraintTop_toBottomOf="@id/tv_upload_activity_title" />
-
-
-
-
-
+ app:layout_constraintTop_toBottomOf="@id/iv_upload_photo" />
+ android:layout_marginHorizontal="@dimen/space_default_large"
+ android:layout_marginTop="@dimen/space_default_medium"
+ android:background="@color/white"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/tv_upload_title" />
-
-
-
-
-
-
-
+
+
-
-
+ app:layout_constraintStart_toStartOf="parent" />
diff --git a/android/app/src/main/res/layout/custom_mypage_grid.xml b/android/app/src/main/res/layout/custom_mypage_grid.xml
index b672d8c4a..c6367edb2 100644
--- a/android/app/src/main/res/layout/custom_mypage_grid.xml
+++ b/android/app/src/main/res/layout/custom_mypage_grid.xml
@@ -13,26 +13,25 @@
android:layout_height="wrap_content"
android:layout_marginStart="18dp"
android:layout_marginTop="10dp"
- android:fontFamily="@font/pretendard_bold"
- android:textColor="@color/main_purple"
- android:textSize="30sp"
+ android:text="@string/mypage_place_title"
+ android:textColor="@color/black"
+ android:textSize="20sp"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- tools:text="Statistics" />
+ app:layout_constraintTop_toTopOf="parent" />
+ tools:listitem="@layout/rv_mypage_item_place" />
diff --git a/android/app/src/main/res/layout/custom_mypage_grid_empty.xml b/android/app/src/main/res/layout/custom_mypage_grid_empty.xml
index 04b4e77e9..3c01f51f6 100644
--- a/android/app/src/main/res/layout/custom_mypage_grid_empty.xml
+++ b/android/app/src/main/res/layout/custom_mypage_grid_empty.xml
@@ -17,25 +17,23 @@
android:layout_height="wrap_content"
android:layout_marginStart="18dp"
android:layout_marginTop="10dp"
- android:fontFamily="@font/pretendard_bold"
- android:textColor="@color/main_purple"
- android:textSize="30sp"
+ android:text="@string/mypage_place_title"
+ android:textColor="@color/black"
+ android:textSize="20sp"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- tools:text="Statistics" />
+ app:layout_constraintTop_toTopOf="parent" />
+ android:textSize="16sp"
+ app:layout_constraintTop_toBottomOf="@id/tv_mypage_empty_item_title" />
diff --git a/android/app/src/main/res/layout/dialog_location_permission.xml b/android/app/src/main/res/layout/dialog_location_permission.xml
deleted file mode 100644
index b75d9d559..000000000
--- a/android/app/src/main/res/layout/dialog_location_permission.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/android/app/src/main/res/layout/dialog_naaga_alert.xml b/android/app/src/main/res/layout/dialog_naaga_alert.xml
index 2381c2b51..6049f2131 100644
--- a/android/app/src/main/res/layout/dialog_naaga_alert.xml
+++ b/android/app/src/main/res/layout/dialog_naaga_alert.xml
@@ -36,39 +36,39 @@
app:layout_constraintEnd_toEndOf="parent" />
+ app:layout_constraintBottom_toBottomOf="@+id/tv_alert_dialog_negative"
+ app:layout_constraintTop_toTopOf="@+id/tv_alert_dialog_negative" />
diff --git a/android/app/src/main/res/layout/dialog_camera_permission.xml b/android/app/src/main/res/layout/dialog_permission.xml
similarity index 72%
rename from android/app/src/main/res/layout/dialog_camera_permission.xml
rename to android/app/src/main/res/layout/dialog_permission.xml
index baa4f9564..d9de24a3c 100644
--- a/android/app/src/main/res/layout/dialog_camera_permission.xml
+++ b/android/app/src/main/res/layout/dialog_permission.xml
@@ -1,6 +1,7 @@
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools">
+ tools:srcCompat="@drawable/ic_camera_dialog" />
+ app:layout_constraintTop_toBottomOf="@id/iv_dialog_permission_icon"
+ tools:text="@string/permissionDialog_camera_description" />
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools">
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/rect_radius_large"
+ android:backgroundTint="@color/red_white">
+ android:id="@+id/iv_image_dialog_close"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="12dp"
+ android:layout_marginEnd="12dp"
+ android:padding="16dp"
+ app:tint="@color/main_dark_blue"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:srcCompat="@drawable/ic_close" />
+ android:id="@+id/iv_image_dialog_destination_image"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:background="@drawable/rect_radius_large"
+ android:backgroundTint="@color/white"
+ android:scaleType="fitCenter"
+ app:layout_constraintDimensionRatio="3:4"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/iv_image_dialog_close"
+ app:layout_constraintWidth_percent="0.8"
+ tools:srcCompat="@tools:sample/avatars" />
+ android:id="@+id/tv_image_dialog_description"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginVertical="20dp"
+ android:fontFamily="@font/pretendard_semibold"
+ android:text="@string/polaroid_description"
+ android:textColor="@color/black"
+ android:textSize="28sp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/iv_image_dialog_destination_image" />
-
\ No newline at end of file
+
diff --git a/android/app/src/main/res/layout/item_history.xml b/android/app/src/main/res/layout/item_history.xml
index 9da0caaaa..b9d844cbe 100644
--- a/android/app/src/main/res/layout/item_history.xml
+++ b/android/app/src/main/res/layout/item_history.xml
@@ -13,8 +13,8 @@
@@ -28,7 +28,6 @@
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:scaleType="centerCrop"
- android:src="@{adventureResult.destination.image}"
android:background="@drawable/rect_radius_small"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -40,7 +39,6 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
- android:fontFamily="@font/pretendard_bold"
android:textColor="@color/black"
android:textSize="16sp"
android:ellipsize="end"
@@ -58,7 +56,7 @@
android:layout_marginTop="12dp"
android:adjustViewBounds="true"
android:layout_marginBottom="12dp"
- android:src="@drawable/ic_success"
+ android:src="@drawable/ic_success_label"
app:layout_constraintWidth_percent="0.4"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_adventureHistory_name"
@@ -70,9 +68,8 @@
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginBottom="12dp"
- android:fontFamily="@font/pretendard_medium"
- android:textColor="@color/black"
- android:textSize="16sp"
+ android:textColor="@color/main_gray"
+ android:textSize="12sp"
android:text="@{adventureResult.beginTime.toLocalDate().toString()}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
diff --git a/android/app/src/main/res/layout/item_rank.xml b/android/app/src/main/res/layout/item_rank.xml
index 58754537f..a2c3a51f1 100644
--- a/android/app/src/main/res/layout/item_rank.xml
+++ b/android/app/src/main/res/layout/item_rank.xml
@@ -18,7 +18,7 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/layout/rv_mypage_item_place.xml b/android/app/src/main/res/layout/rv_mypage_item_place.xml
index 80ddb351c..2e129843b 100644
--- a/android/app/src/main/res/layout/rv_mypage_item_place.xml
+++ b/android/app/src/main/res/layout/rv_mypage_item_place.xml
@@ -4,43 +4,48 @@
xmlns:tools="http://schemas.android.com/tools">
+
+ type="com.now.naaga.presentation.uimodel.model.MyPagePlaceUiModel" />
-
-
+ app:layout_constraintVertical_chainStyle="packed"
+ tools:srcCompat="@tools:sample/avatars" />
+
+
+
diff --git a/android/app/src/main/res/layout/rv_mypage_item_statistics.xml b/android/app/src/main/res/layout/rv_mypage_item_statistics.xml
deleted file mode 100644
index 72b4b6ac2..000000000
--- a/android/app/src/main/res/layout/rv_mypage_item_statistics.xml
+++ /dev/null
@@ -1,75 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/android/app/src/main/res/layout/skeleton_begin_adventure.xml b/android/app/src/main/res/layout/skeleton_begin_adventure.xml
deleted file mode 100644
index cc9e10f8b..000000000
--- a/android/app/src/main/res/layout/skeleton_begin_adventure.xml
+++ /dev/null
@@ -1,97 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/android/app/src/main/res/mipmap-anydpi-v26/naaga_icon.xml b/android/app/src/main/res/mipmap-anydpi-v26/naaga_icon.xml
index 5bf1dfa7f..fc5e68636 100644
--- a/android/app/src/main/res/mipmap-anydpi-v26/naaga_icon.xml
+++ b/android/app/src/main/res/mipmap-anydpi-v26/naaga_icon.xml
@@ -1,5 +1,5 @@
-
-
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/mipmap-anydpi-v26/naaga_icon_round.xml b/android/app/src/main/res/mipmap-anydpi-v26/naaga_icon_round.xml
index 5bf1dfa7f..fc5e68636 100644
--- a/android/app/src/main/res/mipmap-anydpi-v26/naaga_icon_round.xml
+++ b/android/app/src/main/res/mipmap-anydpi-v26/naaga_icon_round.xml
@@ -1,5 +1,5 @@
-
-
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/mipmap-hdpi/naaga_icon.png b/android/app/src/main/res/mipmap-hdpi/naaga_icon.png
deleted file mode 100644
index 38f3f4edf..000000000
Binary files a/android/app/src/main/res/mipmap-hdpi/naaga_icon.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-hdpi/naaga_icon.webp b/android/app/src/main/res/mipmap-hdpi/naaga_icon.webp
new file mode 100644
index 000000000..4bcce6674
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/naaga_icon.webp differ
diff --git a/android/app/src/main/res/mipmap-hdpi/naaga_icon_foreground.png b/android/app/src/main/res/mipmap-hdpi/naaga_icon_foreground.png
deleted file mode 100644
index 8509af29e..000000000
Binary files a/android/app/src/main/res/mipmap-hdpi/naaga_icon_foreground.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-hdpi/naaga_icon_round.png b/android/app/src/main/res/mipmap-hdpi/naaga_icon_round.png
deleted file mode 100644
index 5386310ba..000000000
Binary files a/android/app/src/main/res/mipmap-hdpi/naaga_icon_round.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-hdpi/naaga_icon_round.webp b/android/app/src/main/res/mipmap-hdpi/naaga_icon_round.webp
new file mode 100644
index 000000000..b353c08ac
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/naaga_icon_round.webp differ
diff --git a/android/app/src/main/res/mipmap-mdpi/naaga_icon.png b/android/app/src/main/res/mipmap-mdpi/naaga_icon.png
deleted file mode 100644
index d4f0148cf..000000000
Binary files a/android/app/src/main/res/mipmap-mdpi/naaga_icon.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-mdpi/naaga_icon.webp b/android/app/src/main/res/mipmap-mdpi/naaga_icon.webp
new file mode 100644
index 000000000..de16c95f3
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/naaga_icon.webp differ
diff --git a/android/app/src/main/res/mipmap-mdpi/naaga_icon_foreground.png b/android/app/src/main/res/mipmap-mdpi/naaga_icon_foreground.png
deleted file mode 100644
index 2c56923ac..000000000
Binary files a/android/app/src/main/res/mipmap-mdpi/naaga_icon_foreground.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-mdpi/naaga_icon_round.png b/android/app/src/main/res/mipmap-mdpi/naaga_icon_round.png
deleted file mode 100644
index ebc8611f0..000000000
Binary files a/android/app/src/main/res/mipmap-mdpi/naaga_icon_round.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-mdpi/naaga_icon_round.webp b/android/app/src/main/res/mipmap-mdpi/naaga_icon_round.webp
new file mode 100644
index 000000000..36e10dc66
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/naaga_icon_round.webp differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/naaga_icon.png b/android/app/src/main/res/mipmap-xhdpi/naaga_icon.png
deleted file mode 100644
index 41adc0408..000000000
Binary files a/android/app/src/main/res/mipmap-xhdpi/naaga_icon.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/naaga_icon.webp b/android/app/src/main/res/mipmap-xhdpi/naaga_icon.webp
new file mode 100644
index 000000000..306cde28b
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/naaga_icon.webp differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/naaga_icon_foreground.png b/android/app/src/main/res/mipmap-xhdpi/naaga_icon_foreground.png
deleted file mode 100644
index 61ffae168..000000000
Binary files a/android/app/src/main/res/mipmap-xhdpi/naaga_icon_foreground.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/naaga_icon_round.png b/android/app/src/main/res/mipmap-xhdpi/naaga_icon_round.png
deleted file mode 100644
index 09cd128ff..000000000
Binary files a/android/app/src/main/res/mipmap-xhdpi/naaga_icon_round.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/naaga_icon_round.webp b/android/app/src/main/res/mipmap-xhdpi/naaga_icon_round.webp
new file mode 100644
index 000000000..bd4a04079
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/naaga_icon_round.webp differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/naaga_icon.png b/android/app/src/main/res/mipmap-xxhdpi/naaga_icon.png
deleted file mode 100644
index 429172df2..000000000
Binary files a/android/app/src/main/res/mipmap-xxhdpi/naaga_icon.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/naaga_icon.webp b/android/app/src/main/res/mipmap-xxhdpi/naaga_icon.webp
new file mode 100644
index 000000000..bbd2a7766
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/naaga_icon.webp differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/naaga_icon_foreground.png b/android/app/src/main/res/mipmap-xxhdpi/naaga_icon_foreground.png
deleted file mode 100644
index ba3f75d89..000000000
Binary files a/android/app/src/main/res/mipmap-xxhdpi/naaga_icon_foreground.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/naaga_icon_round.png b/android/app/src/main/res/mipmap-xxhdpi/naaga_icon_round.png
deleted file mode 100644
index dca9699b0..000000000
Binary files a/android/app/src/main/res/mipmap-xxhdpi/naaga_icon_round.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/naaga_icon_round.webp b/android/app/src/main/res/mipmap-xxhdpi/naaga_icon_round.webp
new file mode 100644
index 000000000..a7f6484c3
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/naaga_icon_round.webp differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/naaga_icon.png b/android/app/src/main/res/mipmap-xxxhdpi/naaga_icon.png
deleted file mode 100644
index c20aa55b1..000000000
Binary files a/android/app/src/main/res/mipmap-xxxhdpi/naaga_icon.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/naaga_icon.webp b/android/app/src/main/res/mipmap-xxxhdpi/naaga_icon.webp
new file mode 100644
index 000000000..95e17744c
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/naaga_icon.webp differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/naaga_icon_foreground.png b/android/app/src/main/res/mipmap-xxxhdpi/naaga_icon_foreground.png
deleted file mode 100644
index 191f1f144..000000000
Binary files a/android/app/src/main/res/mipmap-xxxhdpi/naaga_icon_foreground.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/naaga_icon_round.png b/android/app/src/main/res/mipmap-xxxhdpi/naaga_icon_round.png
deleted file mode 100644
index d72a9e604..000000000
Binary files a/android/app/src/main/res/mipmap-xxxhdpi/naaga_icon_round.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/naaga_icon_round.webp b/android/app/src/main/res/mipmap-xxxhdpi/naaga_icon_round.webp
new file mode 100644
index 000000000..65d69fcf7
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/naaga_icon_round.webp differ
diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml
index fdcb32770..4d0ba9299 100644
--- a/android/app/src/main/res/values/colors.xml
+++ b/android/app/src/main/res/values/colors.xml
@@ -8,9 +8,18 @@
#F6EBF9
#FFA5E4DB
#BDBDBD
- #989898
+ #616161
#80989898
#E4A5A5
- #DBB2E8
+
#0D0C4A
+ #E9D1F1
+
+ #EAE0E0
+
+ #0B0726
+ #F6BF0C
+ #10C1FD
+ #E93394
+
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index 9778eacf8..8fede51db 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -2,29 +2,33 @@
Naaga
- 지금 나아가 볼까요?
위치 권한 요청이 허용되었습니다
대략적인 위치 권한만 허용되었습니다
위치 권한 요청이 거절되었습니다
- 아직 준비되지 않은 기능입니다.
- 모험 하기
+ 모험 시작
+ 장소 등록
+ 나의 모험
이어 하기
-
- 설정으로 이동해요
- 나아가 서비스를 이용하기 위해서는\n위치에 엑세스할 권한을\n부여해주셔야 해요
- 대략적인 위치 권한만 허용되었어요\n나아가 서비스를 이용하기 위해서는\n정확한 위치 권한을\n부여해주셔야 해요
+
+ 설정으로 이동해요
+ 나아가 서비스를 이용하기 위해서는\n위치 권한을 부여해주셔야 해요
+ 대략적인 위치 권한만 허용되었어요\n나아가 서비스를 이용하기 위해서는\n정확한 위치 권한을\n부여해주셔야 해요
+ 장소를 올리기 위해서는\n카메라 접근 권한이 필요해요!
사진을 로딩 중이에요
목적지를 받아오는데 실패했어요!
모험 중
- 목적지까지
- %dm
+ 목적지까지
+ m
+ 사진보기
+ 방향찾기
+ 그만하기
도착!
모험 중
- 정답 도전
- 목적지 인근이에요
+ 목적지\n도착
+ 목적지 근처예요!
이곳은 어디일까요?
목적지가 아니에요. %d회 남았어요!
시도 횟수를 초과했어요. 게임이 종료돼요
@@ -39,10 +43,10 @@
오늘의 목적지
\????
- TIME
- DISTANCE
- HINT USES
- TRY COUNT
+ 시간
+ 거리
+ 힌트 사용 횟수
+ 정답 도전 횟수
Score
%d분
%dm
@@ -53,22 +57,25 @@
%d등
- Description
- 장소에 대한 설명을 입력해주세요! \n\n자세할수록 좋습니다!
- 등록할게요!
- 장소명을 입력해주세요!
-
-
- 장소를 올리기 위해서는\n카메라 접근 권한이 필요해요!
+ 장소 등록
+ 장소이름
+ 장소이름을 적어주세요!
+ 장소 등록
+ 장소등록에 성공했어요!\n반영되기까지 약간의 시간이 필요해요!
+ 장소등록에 실패했어요!
+ 사진을 저장하는데 문제가 생겼어요! 다시 시도해주세요!
+ 근방에 다른 미션장소가 존재해서 추가할 수 없어요!
+ 전송에 실패했어요! 다시 시도해주세요!
+ 모든 정보를 입력해주세요.
- History
+ 모험 기록
Leader Board
%d등
-
+
모험 기록
%d등
(상위%d%%)
@@ -82,21 +89,52 @@
총 모험 시간
총 직경 거리
사용 힌트 개수
- My Places
+ 내가 추가한 장소
추가한 장소가 없어요!\n장소를 추가해 주세요!
+ 회
-
+
게임을 포기하시겠어요?
지금 떠나면 점수가 하락해요
이어 하기
포기 하기
-
+
힌트를 사용하시겠어요?
힌트가 %d개 남았어요
힌트 받기
취소 하기
- NAAGA
+ 나 아 가
+
+
+ "성공적으로 회원 탈퇴 되었습니다."
+ "인증 정보가 잘 못 되었어요!"
+ "인증 정보가 만료 되었어요!"
+ 로그아웃 되었습니다.
+ 설정
+ 로그아웃
+ 회원탈퇴
+ 문의하기
+ naaganow@gmail.com
+ [나아가에게 문의하기]
+
+
+ 정말 회원 탈퇴를 하시겠습니까?
+ 나아가와 함께 즐거운 모험을 계속해보세요!
+ 네
+ 아니요
+
+
+ 정말 로그아웃을 하시겠습니까?
+ 나아가와 함께 즐거운 모험을 계속해보세요!
+ 아니오
+ 예
+
+
+ 업데이트 확인
+ 새로운 버전이 출시되었어요!\n업데이트가 필요해요!
+ 업데이트
+ 취소
diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml
index 9c48f80ce..351a936a2 100644
--- a/android/app/src/main/res/values/styles.xml
+++ b/android/app/src/main/res/values/styles.xml
@@ -4,15 +4,9 @@
-
-
-
diff --git a/android/app/src/main/res/values/themes.xml b/android/app/src/main/res/values/themes.xml
index b7a2ec091..f36421ca7 100644
--- a/android/app/src/main/res/values/themes.xml
+++ b/android/app/src/main/res/values/themes.xml
@@ -1,21 +1,17 @@
-
+
-
-
diff --git a/android/app/src/test/java/com/now/naaga/AdventureHistoryViewModelTest.kt b/android/app/src/test/java/com/now/naaga/AdventureHistoryViewModelTest.kt
new file mode 100644
index 000000000..4e4ea001d
--- /dev/null
+++ b/android/app/src/test/java/com/now/naaga/AdventureHistoryViewModelTest.kt
@@ -0,0 +1,119 @@
+package com.now.naaga
+
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import com.now.domain.model.AdventureResult
+import com.now.domain.model.AdventureResultType
+import com.now.domain.model.Coordinate
+import com.now.domain.model.OrderType
+import com.now.domain.model.Place
+import com.now.domain.model.SortType
+import com.now.domain.repository.AdventureRepository
+import com.now.naaga.presentation.adventurehistory.AdventureHistoryViewModel
+import io.mockk.coEvery
+import io.mockk.mockk
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import java.time.LocalDate
+import java.time.LocalDateTime
+import java.time.LocalTime
+
+class AdventureHistoryViewModelTest {
+ private lateinit var vm: AdventureHistoryViewModel
+ private lateinit var adventureRepository: AdventureRepository
+
+ @get:Rule
+ val instantExecutorRule = InstantTaskExecutorRule()
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Before
+ fun setup() {
+ Dispatchers.setMain(UnconfinedTestDispatcher())
+ adventureRepository = mockk()
+ vm = AdventureHistoryViewModel(adventureRepository)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @After
+ fun tearDown() {
+ Dispatchers.resetMain()
+ }
+
+ @Test
+ fun `내 게임 결과 리스트 불러오기`() {
+ // given
+ coEvery {
+ adventureRepository.fetchMyAdventureResults(SortType.TIME, OrderType.DESCENDING)
+ } coAnswers {
+ fakeAdventureResults
+ }
+
+ // when
+ vm.fetchHistories()
+
+ // then
+ assertEquals(vm.adventureResults.getOrAwaitValue(), fakeAdventureResults)
+ }
+
+ private val fakeAdventureResults = listOf(
+ AdventureResult(
+ id = 1,
+ gameId = 1,
+ destination = Place(
+ id = 1,
+ name = "파이브 가이즈",
+ coordinate = Coordinate(37.1234, 125.1234),
+ image = "https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn" +
+ ".net%2Fdn%2FcPs9Im%2Fbtrb2k1feQq%2FBW34tbqjtHscUYkgCPyjcK%2Fimg.jpg,",
+ description = "룰루랄라",
+ ),
+ resultType = AdventureResultType.SUCCESS,
+ score = 1000,
+ playTime = 30,
+ distance = 200,
+ hintUses = 3,
+ tryCount = 2,
+ beginTime = LocalDateTime.of(
+ LocalDate.of(2023, 9, 16),
+ LocalTime.of(13, 30),
+ ),
+ endTime = LocalDateTime.of(
+ LocalDate.of(2023, 9, 16),
+ LocalTime.of(14, 0),
+ ),
+ ),
+ AdventureResult(
+ id = 2,
+ gameId = 1,
+ destination = Place(
+ id = 1,
+ name = "파이브 가이즈",
+ coordinate = Coordinate(37.1234, 125.1234),
+ image = "https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fb" +
+ "log.kakaocdn.net%2Fdn%2FcPs9Im%2Fbtrb2k1feQq%2FBW34tbqjtHscUYkgCPyjcK%2Fimg.jpg,",
+ description = "룰루랄라",
+ ),
+ resultType = AdventureResultType.FAIL,
+ score = 1000,
+ playTime = 30,
+ distance = 200,
+ hintUses = 3,
+ tryCount = 2,
+ beginTime = LocalDateTime.of(
+ LocalDate.of(2023, 9, 16),
+ LocalTime.of(13, 30),
+ ),
+ endTime = LocalDateTime.of(
+ LocalDate.of(2023, 9, 16),
+ LocalTime.of(14, 0),
+ ),
+ ),
+ )
+}
diff --git a/android/app/src/test/java/com/now/naaga/AdventureResultViewModelTest.kt b/android/app/src/test/java/com/now/naaga/AdventureResultViewModelTest.kt
new file mode 100644
index 000000000..84335e10e
--- /dev/null
+++ b/android/app/src/test/java/com/now/naaga/AdventureResultViewModelTest.kt
@@ -0,0 +1,165 @@
+package com.now.naaga
+
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import com.now.domain.model.AdventureResult
+import com.now.domain.model.AdventureResultType
+import com.now.domain.model.Coordinate
+import com.now.domain.model.Place
+import com.now.domain.model.Player
+import com.now.domain.model.Rank
+import com.now.domain.repository.AdventureRepository
+import com.now.domain.repository.RankRepository
+import com.now.naaga.presentation.adventureresult.AdventureResultViewModel
+import io.mockk.coEvery
+import io.mockk.mockk
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import java.time.LocalDate
+import java.time.LocalDateTime
+import java.time.LocalTime
+
+class AdventureResultViewModelTest {
+ private lateinit var vm: AdventureResultViewModel
+ private lateinit var adventureRepository: AdventureRepository
+ private lateinit var rankRepository: RankRepository
+
+ @get:Rule
+ val instantExecutorRule = InstantTaskExecutorRule()
+
+ private val fakeAdventureResultOnSuccess = AdventureResult(
+ id = 1,
+ gameId = 1,
+ destination = Place(
+ id = 1,
+ name = "파이브 가이즈",
+ coordinate = Coordinate(37.1234, 125.1234),
+ image = "https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn" +
+ ".net%2Fdn%2FcPs9Im%2Fbtrb2k1feQq%2FBW34tbqjtHscUYkgCPyjcK%2Fimg.jpg,",
+ description = "룰루랄라",
+ ),
+ resultType = AdventureResultType.SUCCESS,
+ score = 1000,
+ playTime = 30,
+ distance = 200,
+ hintUses = 3,
+ tryCount = 2,
+ beginTime = LocalDateTime.of(
+ LocalDate.of(2023, 9, 16),
+ LocalTime.of(13, 30),
+ ),
+ endTime = LocalDateTime.of(
+ LocalDate.of(2023, 9, 16),
+ LocalTime.of(14, 0),
+ ),
+ )
+
+ private val fakeAdventureResultOnFailure = AdventureResult(
+ id = 2,
+ gameId = 1,
+ destination = Place(
+ id = 1,
+ name = "파이브 가이즈",
+ coordinate = Coordinate(37.1234, 125.1234),
+ image = "https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net" +
+ "%2Fdn%2FcPs9Im%2Fbtrb2k1feQq%2FBW34tbqjtHscUYkgCPyjcK%2Fimg.jpg,",
+ description = "룰루랄라",
+ ),
+ resultType = AdventureResultType.FAIL,
+ score = 1000,
+ playTime = 30,
+ distance = 200,
+ hintUses = 3,
+ tryCount = 2,
+ beginTime = LocalDateTime.of(
+ LocalDate.of(2023, 9, 16),
+ LocalTime.of(13, 30),
+ ),
+ endTime = LocalDateTime.of(
+ LocalDate.of(2023, 9, 16),
+ LocalTime.of(14, 0),
+ ),
+ )
+
+ private val fakeMyRank = Rank(
+ player = Player(
+ id = 1,
+ nickname = "뽀또",
+ score = 1000,
+ ),
+ rank = 1,
+ percent = 1,
+ )
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Before
+ fun setup() {
+ Dispatchers.setMain(UnconfinedTestDispatcher())
+ adventureRepository = mockk()
+ rankRepository = mockk()
+ vm = AdventureResultViewModel(adventureRepository, rankRepository)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @After
+ fun tearDown() {
+ Dispatchers.resetMain()
+ }
+
+ @Test
+ fun `성공한 게임 결과 불러오기`() {
+ // given
+ coEvery {
+ adventureRepository.fetchAdventureResult(1)
+ } coAnswers {
+ fakeAdventureResultOnSuccess
+ }
+
+ // when
+ vm.fetchGameResult(1)
+
+ // then
+ assertEquals(vm.adventureResult.getOrAwaitValue(), fakeAdventureResultOnSuccess)
+ assertEquals(vm.adventureResult.getOrAwaitValue().resultType, AdventureResultType.SUCCESS)
+ }
+
+ @Test
+ fun `실패한 게임 결과 불러오기`() {
+ // given
+ coEvery {
+ adventureRepository.fetchAdventureResult(2)
+ } coAnswers {
+ fakeAdventureResultOnFailure
+ }
+
+ // when
+ vm.fetchGameResult(2)
+
+ // then
+ assertEquals(vm.adventureResult.getOrAwaitValue(), fakeAdventureResultOnFailure)
+ assertEquals(vm.adventureResult.getOrAwaitValue().resultType, AdventureResultType.FAIL)
+ }
+
+ @Test
+ fun `내 랭킹 결과 불러오기`() {
+ // given
+ coEvery {
+ rankRepository.getMyRank()
+ } coAnswers {
+ fakeMyRank
+ }
+
+ // when
+ vm.fetchMyRank()
+
+ // then
+ assertEquals(vm.myRank.getOrAwaitValue(), fakeMyRank.rank)
+ }
+}
diff --git a/android/app/src/test/java/com/now/naaga/ExampleUnitTest.kt b/android/app/src/test/java/com/now/naaga/ExampleUnitTest.kt
deleted file mode 100644
index 4b1c874b7..000000000
--- a/android/app/src/test/java/com/now/naaga/ExampleUnitTest.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.now.naaga
-
-import org.junit.Assert.assertEquals
-import org.junit.Test
-
-/**
- * Example local unit test, which will execute on the development machine (host).
- *
- * See [testing documentation](http://d.android.com/tools/testing).
- */
-class ExampleUnitTest {
- @Test
- fun addition_isCorrect() {
- assertEquals(4, 2 + 2)
- }
-}
diff --git a/android/app/src/test/java/com/now/naaga/HiltTestRunner.kt b/android/app/src/test/java/com/now/naaga/HiltTestRunner.kt
new file mode 100644
index 000000000..c213b6282
--- /dev/null
+++ b/android/app/src/test/java/com/now/naaga/HiltTestRunner.kt
@@ -0,0 +1,16 @@
+package com.now.naaga
+
+import android.app.Application
+import android.content.Context
+import androidx.test.runner.AndroidJUnitRunner
+import dagger.hilt.android.testing.HiltTestApplication
+
+class HiltTestRunner : AndroidJUnitRunner() {
+ override fun newApplication(
+ cl: ClassLoader?,
+ className: String?,
+ context: Context?,
+ ): Application {
+ return super.newApplication(cl, HiltTestApplication::class.java.name, context)
+ }
+}
diff --git a/android/app/src/test/java/com/now/naaga/RankViewModelTest.kt b/android/app/src/test/java/com/now/naaga/RankViewModelTest.kt
new file mode 100644
index 000000000..26b9dc935
--- /dev/null
+++ b/android/app/src/test/java/com/now/naaga/RankViewModelTest.kt
@@ -0,0 +1,117 @@
+package com.now.naaga
+
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import com.now.domain.model.OrderType
+import com.now.domain.model.Player
+import com.now.domain.model.Rank
+import com.now.domain.model.SortType
+import com.now.domain.repository.RankRepository
+import com.now.naaga.presentation.rank.RankViewModel
+import io.mockk.coEvery
+import io.mockk.mockk
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+class RankViewModelTest {
+ private lateinit var vm: RankViewModel
+ private lateinit var rankRepository: RankRepository
+
+ @get:Rule
+ val instantExecutorRule = InstantTaskExecutorRule()
+
+ private val fakeMyLank = Rank(
+ Player(
+ id = 1,
+ nickname = "뽀또",
+ score = 1000,
+ ),
+ rank = 1,
+ percent = 1,
+ )
+
+ private val fakeRanksList = listOf(
+ Rank(
+ player = Player(
+ id = 1,
+ nickname = "뽀또",
+ score = 1000,
+ ),
+ rank = 1,
+ percent = 1,
+ ),
+ Rank(
+ player = Player(
+ id = 2,
+ nickname = "뽀또2",
+ score = 1100,
+ ),
+ rank = 2,
+ percent = 2,
+ ),
+ Rank(
+ player = Player(
+ id = 3,
+ nickname = "뽀또3",
+ score = 1200,
+ ),
+ rank = 3,
+ percent = 3,
+ ),
+ )
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Before
+ fun setup() {
+ Dispatchers.setMain(UnconfinedTestDispatcher())
+ rankRepository = mockk()
+ vm = RankViewModel(rankRepository)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @After
+ fun tearDown() {
+ Dispatchers.resetMain()
+ }
+
+ @Test
+ fun `내 랭킹 조회`() {
+ // given
+ coEvery {
+ rankRepository.getMyRank()
+ } coAnswers {
+ fakeMyLank
+ }
+
+ // when
+ vm.fetchMyRank()
+
+ // then
+ assertEquals(vm.myRank.getOrAwaitValue(), fakeMyLank.rank)
+ assertEquals(vm.myName.getOrAwaitValue(), fakeMyLank.player.nickname)
+ assertEquals(vm.myScore.getOrAwaitValue(), fakeMyLank.player.score)
+ }
+
+ @Test
+ fun `전체 랭킹 조회`() {
+ // given
+ coEvery {
+ rankRepository.getAllRanks(SortType.RANK.name, OrderType.ASCENDING.name)
+ } coAnswers {
+ fakeRanksList
+ }
+
+ // when
+ vm.fetchRanks()
+
+ // then
+ assertEquals(vm.ranks.getOrAwaitValue(), fakeRanksList)
+ }
+}
diff --git a/android/app/src/test/java/com/now/naaga/UploadViewModelTest.kt b/android/app/src/test/java/com/now/naaga/UploadViewModelTest.kt
new file mode 100644
index 000000000..c2caee1c8
--- /dev/null
+++ b/android/app/src/test/java/com/now/naaga/UploadViewModelTest.kt
@@ -0,0 +1,110 @@
+package com.now.naaga
+
+import android.text.Editable
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import com.now.domain.model.Coordinate
+import com.now.domain.model.Place
+import com.now.domain.repository.PlaceRepository
+import com.now.naaga.data.throwable.DataThrowable
+import com.now.naaga.presentation.upload.UploadStatus
+import com.now.naaga.presentation.upload.UploadViewModel
+import io.mockk.coEvery
+import io.mockk.coVerify
+import io.mockk.mockk
+import junit.framework.TestCase.assertEquals
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+class UploadViewModelTest {
+ private lateinit var viewModel: UploadViewModel
+ private lateinit var placeRepository: PlaceRepository
+
+ // 라이브데이터가 메인 스레드에서 동작하도록 함
+ @get:Rule
+ val instantExecutorRule = InstantTaskExecutorRule()
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Before
+ fun setup() {
+ Dispatchers.setMain(UnconfinedTestDispatcher())
+ placeRepository = mockk()
+ viewModel = UploadViewModel(placeRepository)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @After
+ fun tearDown() {
+ Dispatchers.resetMain()
+ }
+
+ private fun String.toEditable(): Editable {
+ return Editable.Factory.getInstance().newEditable(this)
+ }
+
+ @Test
+ fun `Coordinate이 입력되면 뷰모델의 Coordinate도 바뀐다`() {
+ // given
+ val coordinate = Coordinate(123.4567, 37.890)
+
+ // when
+ viewModel.setCoordinate(coordinate)
+
+ // then
+ assertEquals(Coordinate(123.4567, 37.890), viewModel.coordinate.value)
+ }
+
+ @Test
+ fun `데이터 전송 성공시 successUpload가 UploadStatus SUCCESS다`() {
+ // given
+ val coordinate = Coordinate(123.4567, 37.890)
+ viewModel.setCoordinate(coordinate)
+
+ coEvery {
+ placeRepository.postPlace(any(), any(), any(), any())
+ } returns (
+ Place(
+ id = 1,
+ name = "krrong",
+ coordinate = Coordinate(37.1234, 127.1234),
+ image = "https://img.segye.com/content/image/2021/07/29/20210729513138.jpg",
+ description = "android",
+ )
+ )
+
+ // when
+ viewModel.postPlace()
+
+ // Assert: 결과 확인
+ assertEquals(UploadStatus.SUCCESS, viewModel.successUpload.getValue())
+ }
+
+ @Test
+ fun `데이터 전송 실패시 successUpload가 UploadStatus Fail이고 반환된 throwable이 저장된다`() {
+ // given
+ val coordinate = Coordinate(123.4567, 37.890)
+ viewModel.setCoordinate(coordinate)
+ val placeThrowable = DataThrowable.PlaceThrowable(505, "Test Failure")
+ coEvery { placeRepository.postPlace(any(), any(), any(), any()) } throws placeThrowable
+
+ // when
+ runBlocking { viewModel.postPlace() }
+
+ // then
+ // placeRepository.postPlace가 실행되었는지 확인
+ coVerify { placeRepository.postPlace(any(), any(), any(), any()) }
+
+ // successUpload가 UploadStatus.Fail 인지 확인
+ assertEquals(UploadStatus.FAIL, viewModel.successUpload.getValue())
+
+ // 의도한 throwable이 저장되었는지 확인
+ assertEquals(placeThrowable, viewModel.throwable.value)
+ }
+}
diff --git a/android/app/src/test/java/com/now/naaga/UploadViewModelTestWithRobolectric.kt b/android/app/src/test/java/com/now/naaga/UploadViewModelTestWithRobolectric.kt
new file mode 100644
index 000000000..29d8cadee
--- /dev/null
+++ b/android/app/src/test/java/com/now/naaga/UploadViewModelTestWithRobolectric.kt
@@ -0,0 +1,64 @@
+package com.now.naaga
+
+import android.widget.EditText
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import androidx.lifecycle.ViewModelProvider
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import com.google.firebase.FirebaseApp
+import com.now.naaga.presentation.upload.UploadActivity
+import com.now.naaga.presentation.upload.UploadViewModel
+import dagger.hilt.android.testing.HiltAndroidRule
+import dagger.hilt.android.testing.HiltAndroidTest
+import dagger.hilt.android.testing.HiltTestApplication
+import junit.framework.TestCase.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.Robolectric
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+
+@RunWith(RobolectricTestRunner::class)
+@HiltAndroidTest
+@Config(application = HiltTestApplication::class)
+class UploadViewModelTestWithRobolectric {
+
+ @get:Rule(order = 0)
+ var hiltRule = HiltAndroidRule(this)
+
+ @get:Rule(order = 1)
+ var instantTaskExecutorRule = InstantTaskExecutorRule()
+
+ @Before
+ fun setup() {
+ FirebaseApp.initializeApp(getApplicationContext())
+ }
+
+ @Test
+ fun `editText 값이 변경되면 뷰모델의 name 프로퍼티도 변경된다`() {
+ // given
+ val activity = Robolectric
+ .buildActivity(UploadActivity::class.java)
+ .create()
+ .visible()
+ .get()
+
+ // EditText 찾기
+ val editTextTitle = activity.findViewById(R.id.et_upload_place_name)
+
+ // when : EditText 값 변경
+ val testInput = "Test Input"
+ editTextTitle.setText(testInput)
+
+ // ViewModelProvider에서 뷰모델 가져오기
+ val viewModel = ViewModelProvider(activity)[UploadViewModel::class.java]
+
+ // LiveData 값 확인
+ val nameLiveData = viewModel.name
+ val observedValue = nameLiveData.getOrAwaitValue()
+
+ // then : EditText 값과 LiveData 값이 일치하는지 확인
+ assertEquals(testInput, observedValue)
+ }
+}
diff --git a/android/app/src/test/java/com/now/naaga/getOrAwaitValue.kt b/android/app/src/test/java/com/now/naaga/getOrAwaitValue.kt
new file mode 100644
index 000000000..3732667ea
--- /dev/null
+++ b/android/app/src/test/java/com/now/naaga/getOrAwaitValue.kt
@@ -0,0 +1,32 @@
+package com.now.naaga // ktlint-disable filename
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.Observer
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeoutException
+
+fun LiveData.getOrAwaitValue(
+ time: Long = 2,
+ timeUnit: TimeUnit = TimeUnit.SECONDS,
+): T {
+ var data: T? = null
+ val latch = CountDownLatch(1)
+ val observer = object : Observer {
+ override fun onChanged(o: T) {
+ data = o
+ latch.countDown()
+ this@getOrAwaitValue.removeObserver(this)
+ }
+ }
+
+ this.observeForever(observer)
+
+ // Don't wait indefinitely if the LiveData is not set.
+ if (!latch.await(time, timeUnit)) {
+ throw TimeoutException("LiveData value was never set.")
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ return data as T
+}
diff --git a/android/build.gradle b/android/build.gradle
index 77c7dffca..2baf6f4b9 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -7,6 +7,7 @@ plugins {
id 'org.jlleitschuh.gradle.ktlint' version "10.3.0" apply false
id 'com.google.gms.google-services' version "4.3.15" apply false
id 'com.google.firebase.crashlytics' version '2.9.7' apply false
+ id 'com.google.dagger.hilt.android' version '2.44' apply false
}
allprojects {
diff --git a/android/domain/src/main/java/com/now/domain/repository/AdventureRepository.kt b/android/domain/src/main/java/com/now/domain/repository/AdventureRepository.kt
index c7c38f1f9..9ce2746c1 100644
--- a/android/domain/src/main/java/com/now/domain/repository/AdventureRepository.kt
+++ b/android/domain/src/main/java/com/now/domain/repository/AdventureRepository.kt
@@ -10,38 +10,31 @@ import com.now.domain.model.OrderType
import com.now.domain.model.SortType
interface AdventureRepository {
- fun fetchMyAdventures(callback: (Result>) -> Unit)
+ suspend fun fetchMyAdventures(): List
+ suspend fun fetchAdventure(adventureId: Long): Adventure
+ suspend fun fetchAdventureByStatus(status: AdventureStatus): List
+ suspend fun beginAdventure(coordinate: Coordinate): Adventure
- fun fetchAdventure(adventureId: Long, callback: (Result) -> Unit)
-
- fun fetchAdventureByStatus(status: AdventureStatus, callback: (Result>) -> Unit)
-
- fun beginAdventure(coordinate: Coordinate, callback: (Result) -> Unit)
-
- fun endGame(
+ suspend fun endGame(
adventureId: Long,
endType: AdventureEndType,
coordinate: Coordinate,
- callback: (Result) -> Unit,
- )
+ ): AdventureStatus
- fun fetchAdventureResult(adventureId: Long, callback: (Result) -> Unit)
+ suspend fun fetchAdventureResult(adventureId: Long): AdventureResult
- fun fetchMyAdventureResults(
+ suspend fun fetchMyAdventureResults(
sortBy: SortType,
order: OrderType,
- callback: (Result>) -> Unit,
- )
+ ): List
- fun fetchHint(
+ suspend fun fetchHint(
adventureId: Long,
hintId: Long,
- callback: (Result) -> Unit,
- )
+ ): Hint
- fun makeHint(
+ suspend fun makeHint(
adventureId: Long,
coordinate: Coordinate,
- callback: (Result) -> Unit,
- )
+ ): Hint
}
diff --git a/android/domain/src/main/java/com/now/domain/repository/AuthRepository.kt b/android/domain/src/main/java/com/now/domain/repository/AuthRepository.kt
index 2b3fb8d97..2177c221a 100644
--- a/android/domain/src/main/java/com/now/domain/repository/AuthRepository.kt
+++ b/android/domain/src/main/java/com/now/domain/repository/AuthRepository.kt
@@ -3,8 +3,7 @@ package com.now.domain.repository
import com.now.domain.model.PlatformAuth
interface AuthRepository {
- fun getToken(
- platformAuth: PlatformAuth,
- callback: (Result) -> Unit,
- )
+ suspend fun getToken(platformAuth: PlatformAuth): Boolean
+ suspend fun withdrawalMember()
+ suspend fun logout()
}
diff --git a/android/domain/src/main/java/com/now/domain/repository/PlaceRepository.kt b/android/domain/src/main/java/com/now/domain/repository/PlaceRepository.kt
index ade0233a6..46763c496 100644
--- a/android/domain/src/main/java/com/now/domain/repository/PlaceRepository.kt
+++ b/android/domain/src/main/java/com/now/domain/repository/PlaceRepository.kt
@@ -4,22 +4,19 @@ import com.now.domain.model.Coordinate
import com.now.domain.model.Place
interface PlaceRepository {
- fun fetchMyPlaces(
+ suspend fun fetchMyPlaces(
sortBy: String,
order: String,
- callback: (Result>) -> Unit,
- )
+ ): List
- fun fetchPlace(
+ suspend fun fetchPlace(
placeId: Long,
- callback: (Result) -> Unit,
- )
+ ): Place
- fun postPlace(
+ suspend fun postPlace(
name: String,
description: String,
coordinate: Coordinate,
image: String,
- callback: (Result) -> Unit,
- )
+ ): Place
}
diff --git a/android/domain/src/main/java/com/now/domain/repository/RankRepository.kt b/android/domain/src/main/java/com/now/domain/repository/RankRepository.kt
index d5c504251..aa4c2dbc0 100644
--- a/android/domain/src/main/java/com/now/domain/repository/RankRepository.kt
+++ b/android/domain/src/main/java/com/now/domain/repository/RankRepository.kt
@@ -3,11 +3,10 @@ package com.now.domain.repository
import com.now.domain.model.Rank
interface RankRepository {
- fun getAllRanks(
+ suspend fun getAllRanks(
sortBy: String,
order: String,
- callback: (Result>) -> Unit,
- )
+ ): List
- fun getMyRank(callback: (Result) -> Unit)
+ suspend fun getMyRank(): Rank
}
diff --git a/android/domain/src/main/java/com/now/domain/repository/StatisticsRepository.kt b/android/domain/src/main/java/com/now/domain/repository/StatisticsRepository.kt
index 8f1bf6a55..a3a789831 100644
--- a/android/domain/src/main/java/com/now/domain/repository/StatisticsRepository.kt
+++ b/android/domain/src/main/java/com/now/domain/repository/StatisticsRepository.kt
@@ -3,5 +3,5 @@ package com.now.domain.repository
import com.now.domain.model.Statistics
interface StatisticsRepository {
- fun getMyStatistics(callback: (Result) -> Unit)
+ suspend fun getMyStatistics(): Statistics
}
diff --git a/android/naaga b/android/naaga
new file mode 100644
index 000000000..ff31e00da
Binary files /dev/null and b/android/naaga differ
diff --git a/backend/build.gradle b/backend/build.gradle
index bc5038e47..11045f858 100644
--- a/backend/build.gradle
+++ b/backend/build.gradle
@@ -26,6 +26,10 @@ dependencies {
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.5'
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'
+
+ implementation 'ch.qos.logback.contrib:logback-jackson:0.1.5'
+ implementation 'ch.qos.logback.contrib:logback-json-classic:0.1.5'
+ implementation 'net.logstash.logback:logstash-logback-encoder:6.1'
}
tasks.named('test') {
diff --git a/backend/src/main/java/com/now/naaga/auth/application/AuthService.java b/backend/src/main/java/com/now/naaga/auth/application/AuthService.java
index fb611fd63..4fd9c15f7 100644
--- a/backend/src/main/java/com/now/naaga/auth/application/AuthService.java
+++ b/backend/src/main/java/com/now/naaga/auth/application/AuthService.java
@@ -9,8 +9,8 @@
import com.now.naaga.auth.infrastructure.dto.MemberAuth;
import com.now.naaga.auth.infrastructure.jwt.AuthTokenGenerator;
import com.now.naaga.auth.persistence.AuthRepository;
-import com.now.naaga.member.application.CreateMemberCommand;
-import com.now.naaga.member.application.DeleteMemberCommand;
+import com.now.naaga.member.application.dto.CreateMemberCommand;
+import com.now.naaga.member.application.dto.DeleteMemberCommand;
import com.now.naaga.member.application.MemberService;
import com.now.naaga.member.domain.Member;
import com.now.naaga.player.application.PlayerService;
diff --git a/backend/src/main/java/com/now/naaga/auth/exception/AuthExceptionType.java b/backend/src/main/java/com/now/naaga/auth/exception/AuthExceptionType.java
index 54d73a751..1ec3140d1 100644
--- a/backend/src/main/java/com/now/naaga/auth/exception/AuthExceptionType.java
+++ b/backend/src/main/java/com/now/naaga/auth/exception/AuthExceptionType.java
@@ -44,7 +44,12 @@ public enum AuthExceptionType implements BaseExceptionType {
//todo : 고쳐줘
INVALID_KAKAO_DELETE(101,
HttpStatus.UNAUTHORIZED,
- "카카오에서 정보를 삭제할 수 없습니다.");
+ "카카오에서 정보를 삭제할 수 없습니다."),
+
+ INVALID_MANAGER(103,
+ HttpStatus.UNAUTHORIZED,
+ "관리자 정보가 옳지 않습니다.")
+ ;
private final int errorCode;
private final HttpStatus httpStatus;
diff --git a/backend/src/main/java/com/now/naaga/auth/presentation/interceptor/ManagerAuthInterceptor.java b/backend/src/main/java/com/now/naaga/auth/presentation/interceptor/ManagerAuthInterceptor.java
new file mode 100644
index 000000000..62ffb63c4
--- /dev/null
+++ b/backend/src/main/java/com/now/naaga/auth/presentation/interceptor/ManagerAuthInterceptor.java
@@ -0,0 +1,64 @@
+package com.now.naaga.auth.presentation.interceptor;
+
+import com.now.naaga.auth.exception.AuthException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpHeaders;
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.List;
+import java.util.Objects;
+
+import static com.now.naaga.auth.exception.AuthExceptionType.*;
+
+@Component
+public class ManagerAuthInterceptor implements HandlerInterceptor {
+
+ public static final int AUTH_HEADER_INFO_SIZE = 2;
+
+ public static final String AUTH_HEADER_TYPE = "Basic";
+
+ @Value("${manager.id}")
+ private String id;
+
+ @Value("${manager.password}")
+ private String password;
+
+ @Override
+ public boolean preHandle(final HttpServletRequest request,
+ final HttpServletResponse response,
+ final Object handler) throws Exception {
+ final List idAndPassword = extractHeaderInfo(request);
+ return loginForManager(idAndPassword);
+ }
+
+ private List extractHeaderInfo(final HttpServletRequest request) {
+ final String header = request.getHeader(HttpHeaders.AUTHORIZATION);
+ if (header == null) {
+ throw new AuthException(NOT_EXIST_HEADER);
+ }
+ final String[] authHeader = header.split(" ");
+ if (!AUTH_HEADER_TYPE.equalsIgnoreCase(authHeader[0])) {
+ throw new AuthException(INVALID_HEADER);
+ }
+ final String decodedHeader = new String(Base64.getDecoder().decode(authHeader[1]));
+ final String[] idAndPassword = decodedHeader.split(":");
+ if (idAndPassword.length != AUTH_HEADER_INFO_SIZE) {
+ throw new AuthException(INVALID_HEADER);
+ }
+ return Arrays.asList(idAndPassword);
+ }
+
+ private boolean loginForManager(final List idAndPassword) {
+ final String inputId = idAndPassword.get(0).strip();
+ final String inputPassword = idAndPassword.get(1).strip();
+ if (Objects.equals(id, inputId) && Objects.equals(password, inputPassword)) {
+ return true;
+ }
+ throw new AuthException(INVALID_MANAGER);
+ }
+}
diff --git a/backend/src/main/java/com/now/naaga/common/config/BeanConfig.java b/backend/src/main/java/com/now/naaga/common/config/BeanConfig.java
index 458c01d39..d6ad7c4a4 100644
--- a/backend/src/main/java/com/now/naaga/common/config/BeanConfig.java
+++ b/backend/src/main/java/com/now/naaga/common/config/BeanConfig.java
@@ -1,10 +1,10 @@
package com.now.naaga.common.config;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.now.naaga.game.domain.gamescore.FailGameScorePolicy;
-import com.now.naaga.game.domain.gamescore.GameScoreCalculator;
-import com.now.naaga.game.domain.gamescore.GameScorePolicy;
-import com.now.naaga.game.domain.gamescore.SuccessGameScorePolicy;
+import com.now.naaga.gameresult.domain.gamescore.FailResultScorePolicy;
+import com.now.naaga.gameresult.domain.gamescore.ResultScoreCalculator;
+import com.now.naaga.gameresult.domain.gamescore.ResultScorePolicy;
+import com.now.naaga.gameresult.domain.gamescore.SuccessResultScorePolicy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@@ -25,21 +25,21 @@ public ObjectMapper objectMapper() {
}
@Bean
- public SuccessGameScorePolicy successGameScorePolicy() {
- return new SuccessGameScorePolicy();
+ public SuccessResultScorePolicy successGameScorePolicy() {
+ return new SuccessResultScorePolicy();
}
@Bean
- public FailGameScorePolicy failGameScorePolicy() {
- return new FailGameScorePolicy();
+ public FailResultScorePolicy failGameScorePolicy() {
+ return new FailResultScorePolicy();
}
@Bean
- public GameScoreCalculator gameScoreCalculator() {
- final List gameScorePolicies = List.of(
+ public ResultScoreCalculator gameScoreCalculator() {
+ final List gameScorePolicies = List.of(
successGameScorePolicy(),
failGameScorePolicy()
);
- return new GameScoreCalculator(gameScorePolicies);
+ return new ResultScoreCalculator(gameScorePolicies);
}
}
diff --git a/backend/src/main/java/com/now/naaga/common/config/FilterConfig.java b/backend/src/main/java/com/now/naaga/common/config/FilterConfig.java
new file mode 100644
index 000000000..2ea82795d
--- /dev/null
+++ b/backend/src/main/java/com/now/naaga/common/config/FilterConfig.java
@@ -0,0 +1,23 @@
+package com.now.naaga.common.config;
+
+import com.now.naaga.common.presentation.LogFilter;
+import com.now.naaga.common.presentation.QueryCounter;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class FilterConfig {
+
+ private final QueryCounter queryCounter;
+
+ public FilterConfig(final QueryCounter queryCounter) {
+ this.queryCounter = queryCounter;
+ }
+
+ @Bean
+ public FilterRegistrationBean logFilter() {
+ final LogFilter logFilter = new LogFilter(queryCounter);
+ return new FilterRegistrationBean<>(logFilter);
+ }
+}
diff --git a/backend/src/main/java/com/now/naaga/common/config/HibernateConfig.java b/backend/src/main/java/com/now/naaga/common/config/HibernateConfig.java
new file mode 100644
index 000000000..f84ef50e0
--- /dev/null
+++ b/backend/src/main/java/com/now/naaga/common/config/HibernateConfig.java
@@ -0,0 +1,23 @@
+package com.now.naaga.common.config;
+
+import com.now.naaga.common.presentation.QueryInspector;
+import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import static org.hibernate.cfg.AvailableSettings.STATEMENT_INSPECTOR;
+
+@Configuration
+public class HibernateConfig {
+
+ private final QueryInspector queryInspector;
+
+ public HibernateConfig(final QueryInspector queryInspector) {
+ this.queryInspector = queryInspector;
+ }
+
+ @Bean
+ public HibernatePropertiesCustomizer hibernatePropertiesCustomizer() {
+ return hibernateProperties -> hibernateProperties.put(STATEMENT_INSPECTOR, queryInspector);
+ }
+}
diff --git a/backend/src/main/java/com/now/naaga/common/config/WebConfig.java b/backend/src/main/java/com/now/naaga/common/config/WebConfig.java
index 11f04e3ee..5014a2bcf 100644
--- a/backend/src/main/java/com/now/naaga/common/config/WebConfig.java
+++ b/backend/src/main/java/com/now/naaga/common/config/WebConfig.java
@@ -1,13 +1,17 @@
package com.now.naaga.common.config;
-import com.now.naaga.auth.presentation.interceptor.AuthInterceptor;
+import com.now.naaga.auth.presentation.argumentresolver.MemberAuthArgumentResolver;
import com.now.naaga.auth.presentation.argumentresolver.PlayerArgumentResolver;
-
+import com.now.naaga.auth.presentation.interceptor.AuthInterceptor;
+import com.now.naaga.auth.presentation.interceptor.ManagerAuthInterceptor;
+import com.now.naaga.common.presentation.interceptor.RequestMatcherInterceptor;
import java.util.List;
-
-import com.now.naaga.auth.presentation.argumentresolver.MemberAuthArgumentResolver;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpMethod;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@@ -15,25 +19,54 @@
public class WebConfig implements WebMvcConfigurer {
private final PlayerArgumentResolver playerArgumentResolver;
+
private final MemberAuthArgumentResolver memberAuthArgumentResolver;
+
private final AuthInterceptor authInterceptor;
+ private final ManagerAuthInterceptor managerAuthInterceptor;
+
+ @Value("${manager.origin-url}")
+ private String managerUriPath;
+
public WebConfig(final PlayerArgumentResolver playerArgumentResolver,
final MemberAuthArgumentResolver memberAuthArgumentResolver,
- final AuthInterceptor authInterceptor) {
+ final AuthInterceptor authInterceptor,
+ final ManagerAuthInterceptor managerAuthInterceptor) {
this.playerArgumentResolver = playerArgumentResolver;
this.memberAuthArgumentResolver = memberAuthArgumentResolver;
this.authInterceptor = authInterceptor;
+ this.managerAuthInterceptor = managerAuthInterceptor;
}
@Override
public void addInterceptors(final InterceptorRegistry registry) {
- registry.addInterceptor(authInterceptor)
- .addPathPatterns("/**")
- .excludePathPatterns("/h2-console/**")
- .excludePathPatterns("/auth/**")
- .excludePathPatterns("/**/*.png", "/**/*.jpg", "/**/*.jpeg", "/**/*.gif", "/**/*.ico")
- .excludePathPatterns("/ranks");
+ registry.addInterceptor(mapAuthInterceptor());
+ registry.addInterceptor(mapManagerAuthInterceptor());
+ }
+
+ private HandlerInterceptor mapAuthInterceptor() {
+ return new RequestMatcherInterceptor(authInterceptor)
+ .includeRequestPattern("/**")
+ .excludeRequestPattern("/h2-console/**")
+ .excludeRequestPattern("/auth/**")
+ .excludeRequestPattern("/**/*.png")
+ .excludeRequestPattern("/**/*.jpg")
+ .excludeRequestPattern("/**/*.jpeg")
+ .excludeRequestPattern("/**/*.gif")
+ .excludeRequestPattern("/**/*.ico")
+ .excludeRequestPattern("/ranks")
+ .excludeRequestPattern("/**", HttpMethod.OPTIONS)
+ .excludeRequestPattern("/temporary-places", HttpMethod.GET)
+ .excludeRequestPattern("/places", HttpMethod.POST)
+ .excludeRequestPattern("/temporary-places/**", HttpMethod.DELETE);
+ }
+
+ private HandlerInterceptor mapManagerAuthInterceptor() {
+ return new RequestMatcherInterceptor(managerAuthInterceptor)
+ .includeRequestPattern("/temporary-places", HttpMethod.GET)
+ .includeRequestPattern("/places", HttpMethod.POST)
+ .includeRequestPattern("/temporary-places/**", HttpMethod.DELETE);
}
@Override
@@ -41,4 +74,14 @@ public void addArgumentResolvers(final List resol
resolvers.add(playerArgumentResolver);
resolvers.add(memberAuthArgumentResolver);
}
+
+ @Override
+ public void addCorsMappings(final CorsRegistry registry) {
+ registry.addMapping("/**")
+ .allowedOrigins(managerUriPath)
+ .allowedMethods("*")
+ .allowedHeaders("*")
+ .allowCredentials(true)
+ .maxAge(3000);
+ }
}
diff --git a/backend/src/main/java/com/now/naaga/common/exception/ControllerExceptionHandler.java b/backend/src/main/java/com/now/naaga/common/exception/ControllerExceptionHandler.java
index c191696aa..fd1f00863 100644
--- a/backend/src/main/java/com/now/naaga/common/exception/ControllerExceptionHandler.java
+++ b/backend/src/main/java/com/now/naaga/common/exception/ControllerExceptionHandler.java
@@ -1,6 +1,5 @@
package com.now.naaga.common.exception;
-import jakarta.persistence.criteria.CriteriaBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
@@ -20,34 +19,30 @@ public class ControllerExceptionHandler {
public ResponseEntity handleBaseException(final BaseException e) {
final BaseExceptionType baseExceptionType = e.exceptionType();
final ExceptionResponse exceptionResponse = new ExceptionResponse(baseExceptionType.errorCode(), baseExceptionType.errorMessage());
- log.warn("error = {}", exceptionResponse, e);
+
+ log.info("error = {}", exceptionResponse);
+
return ResponseEntity.status(baseExceptionType.httpStatus()).body(exceptionResponse);
}
@ExceptionHandler({
HttpMessageNotReadableException.class,
MethodArgumentNotValidException.class,
- HttpMediaTypeNotSupportedException.class
- })
+ HttpMediaTypeNotSupportedException.class})
public ResponseEntity handleTypeMismatchException(final Exception e) {
final CommonExceptionType commonExceptionType = CommonExceptionType.INVALID_REQUEST_BODY;
final ExceptionResponse exceptionResponse = new ExceptionResponse(commonExceptionType.errorCode(), commonExceptionType.errorMessage());
- log.warn("error = {}", exceptionResponse, e);
+
+ log.info("error = {}", exceptionResponse);
+
return ResponseEntity.status(commonExceptionType.httpStatus()).body(exceptionResponse);
}
@ExceptionHandler(InternalException.class)
- public ResponseEntity handleInternalException(final InternalException e){
+ public ResponseEntity handleInternalException(final InternalException e) {
final BaseExceptionType internalExceptionType = e.exceptionType();
-// log.error("errorCode = {} \n message = {}",
-// internalExceptionType.errorCode(),
-// internalExceptionType.errorMessage());
-
- log.error("errorCode = {} \n message = {} \n error = {}",
- internalExceptionType.errorCode(),
- internalExceptionType.errorMessage(),
- e.getMessage() , e);
+ log.error(e.getMessage(), e);
final ExceptionResponse exceptionResponse = new ExceptionResponse(10000, "예기치 못한 오류입니다");
return ResponseEntity.status(internalExceptionType.httpStatus())
@@ -55,11 +50,10 @@ public ResponseEntity handleInternalException(final InternalE
}
@ExceptionHandler(Exception.class)
- public ResponseEntity handleException(final Exception e){
- log.error("error = {}"+ e.getMessage() , e);
+ public ResponseEntity handleException(final Exception e) {
+ log.error(e.getMessage(), e);
final ExceptionResponse exceptionResponse = new ExceptionResponse(10000, "예기치 못한 오류입니다");
- e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(exceptionResponse);
}
diff --git a/backend/src/main/java/com/now/naaga/common/presentation/LogFilter.java b/backend/src/main/java/com/now/naaga/common/presentation/LogFilter.java
new file mode 100644
index 000000000..cad62bbd7
--- /dev/null
+++ b/backend/src/main/java/com/now/naaga/common/presentation/LogFilter.java
@@ -0,0 +1,51 @@
+package com.now.naaga.common.presentation;
+
+import jakarta.servlet.*;
+import jakarta.servlet.http.HttpServletRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.MDC;
+
+import java.io.IOException;
+import java.util.UUID;
+
+import static com.now.naaga.common.presentation.MdcToken.*;
+
+public class LogFilter implements Filter {
+
+ private static final String LOG_FORMAT = "uri: {}, method: {}, time: {}ms, queryCount: {}";
+
+ private final Logger log = LoggerFactory.getLogger(this.getClass().getSimpleName());
+ private final QueryCounter queryCounter;
+
+ public LogFilter(final QueryCounter queryCounter) {
+ this.queryCounter = queryCounter;
+ }
+
+ @Override
+ public void doFilter(final ServletRequest request,
+ final ServletResponse response,
+ final FilterChain chain)
+ throws IOException, ServletException {
+ queryCounter.init();
+
+ final long start = System.currentTimeMillis();
+ final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
+ MDC.put(REQUEST_ID.getKey(), UUID.randomUUID().toString());
+
+ chain.doFilter(request, response);
+
+ final int queryCount = queryCounter.count();
+ log(start, queryCount, httpServletRequest);
+ queryCounter.close();
+ MDC.clear();
+ }
+
+ private void log(final long start,
+ final int queryCount,
+ final HttpServletRequest httpServletRequest) {
+ final long end = System.currentTimeMillis();
+ final long time = end - start;
+ log.info(LOG_FORMAT, httpServletRequest.getRequestURI(), httpServletRequest.getMethod(), time, queryCount);
+ }
+}
diff --git a/backend/src/main/java/com/now/naaga/common/presentation/MdcToken.java b/backend/src/main/java/com/now/naaga/common/presentation/MdcToken.java
new file mode 100644
index 000000000..0364f50df
--- /dev/null
+++ b/backend/src/main/java/com/now/naaga/common/presentation/MdcToken.java
@@ -0,0 +1,17 @@
+package com.now.naaga.common.presentation;
+
+public enum MdcToken {
+
+ REQUEST_ID("request_id")
+ ;
+
+ private final String key;
+
+ MdcToken(final String key) {
+ this.key = key;
+ }
+
+ public String getKey() {
+ return key;
+ }
+}
diff --git a/backend/src/main/java/com/now/naaga/common/presentation/QueryCounter.java b/backend/src/main/java/com/now/naaga/common/presentation/QueryCounter.java
new file mode 100644
index 000000000..34d8c1c5d
--- /dev/null
+++ b/backend/src/main/java/com/now/naaga/common/presentation/QueryCounter.java
@@ -0,0 +1,30 @@
+package com.now.naaga.common.presentation;
+
+import org.springframework.stereotype.Component;
+
+@Component
+public class QueryCounter {
+
+ private static final int INITIAL_VALUE = 0;
+
+ private final ThreadLocal count = new ThreadLocal<>();
+
+ public void increase() {
+ if (count.get() == null) {
+ init();
+ }
+ count.set(count.get() + 1);
+ }
+
+ public void init() {
+ count.set(INITIAL_VALUE);
+ }
+
+ public int count() {
+ return count.get();
+ }
+
+ public void close() {
+ count.remove();
+ }
+}
diff --git a/backend/src/main/java/com/now/naaga/common/presentation/QueryInspector.java b/backend/src/main/java/com/now/naaga/common/presentation/QueryInspector.java
new file mode 100644
index 000000000..51e67bba3
--- /dev/null
+++ b/backend/src/main/java/com/now/naaga/common/presentation/QueryInspector.java
@@ -0,0 +1,31 @@
+package com.now.naaga.common.presentation;
+
+import org.hibernate.resource.jdbc.spi.StatementInspector;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestContextHolder;
+
+import java.util.Objects;
+
+@Component
+public class QueryInspector implements StatementInspector {
+
+ private final QueryCounter queryCounter;
+
+ public QueryInspector(final QueryCounter queryCounter) {
+ this.queryCounter = queryCounter;
+ }
+
+ @Override
+ public String inspect(final String sql) {
+ if (isInRequestScope()) {
+ queryCounter.increase();
+ }
+ return sql;
+ }
+
+ private boolean isInRequestScope() {
+ return Objects.nonNull(RequestContextHolder.getRequestAttributes());
+ }
+}
diff --git a/backend/src/main/java/com/now/naaga/common/presentation/interceptor/RequestMatcher.java b/backend/src/main/java/com/now/naaga/common/presentation/interceptor/RequestMatcher.java
new file mode 100644
index 000000000..9764ad02f
--- /dev/null
+++ b/backend/src/main/java/com/now/naaga/common/presentation/interceptor/RequestMatcher.java
@@ -0,0 +1,18 @@
+package com.now.naaga.common.presentation.interceptor;
+
+import org.springframework.util.AntPathMatcher;
+import org.springframework.util.PathMatcher;
+
+
+public class RequestMatcher {
+
+ private static final PathMatcher pathMatcher = new AntPathMatcher();
+
+ private RequestMatcher() {}
+
+ public static boolean match(final RequestPattern requestPattern,
+ final RequestPattern inputRequestPattern) {
+ return pathMatcher.match(requestPattern.path(), inputRequestPattern.path())
+ && requestPattern.method() == inputRequestPattern.method();
+ }
+}
diff --git a/backend/src/main/java/com/now/naaga/common/presentation/interceptor/RequestMatcherInterceptor.java b/backend/src/main/java/com/now/naaga/common/presentation/interceptor/RequestMatcherInterceptor.java
new file mode 100644
index 000000000..f13c7f3da
--- /dev/null
+++ b/backend/src/main/java/com/now/naaga/common/presentation/interceptor/RequestMatcherInterceptor.java
@@ -0,0 +1,80 @@
+package com.now.naaga.common.presentation.interceptor;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.http.HttpMethod;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class RequestMatcherInterceptor implements HandlerInterceptor {
+
+ private static final List ALL_HTTP_METHODS = new ArrayList<>(List.of(HttpMethod.values()));
+
+ private final HandlerInterceptor handlerInterceptor;
+
+ private final List includedRequestPatterns = new ArrayList<>();
+
+ private final List excludedRequestPatterns = new ArrayList<>();
+
+
+ public RequestMatcherInterceptor(final HandlerInterceptor handlerInterceptor) {
+ this.handlerInterceptor = handlerInterceptor;
+ }
+
+ @Override
+ public boolean preHandle(final HttpServletRequest request,
+ final HttpServletResponse response,
+ final Object handler) throws Exception {
+ if (isIncludedRequestPattern(request)) {
+ return handlerInterceptor.preHandle(request, response, handler);
+ }
+ return true;
+ }
+
+ private boolean isIncludedRequestPattern(final HttpServletRequest request) {
+ final String requestPath = request.getServletPath();
+ final String requestMethod = request.getMethod();
+
+ final boolean isIncludedPattern = includedRequestPatterns.stream()
+ .anyMatch(requestPattern -> requestPattern.match(requestPath, requestMethod));
+
+ final boolean isNotExcludedPattern = excludedRequestPatterns.stream()
+ .noneMatch(requestPattern -> requestPattern.match(requestPath, requestMethod));
+
+ return isIncludedPattern && isNotExcludedPattern;
+ }
+
+ public RequestMatcherInterceptor includeRequestPattern(final String requestPathPattern,
+ final HttpMethod... requestMethods) {
+ final List mappingRequestMethods = decideRequestMethods(requestMethods);
+
+ for (HttpMethod httpMethod : mappingRequestMethods) {
+ this.includedRequestPatterns.add(new RequestPattern(requestPathPattern, httpMethod));
+ }
+
+ return this;
+ }
+
+ public RequestMatcherInterceptor excludeRequestPattern(final String requestPathPattern,
+ final HttpMethod... requestMethods) {
+ final List mappingRequestMethods = decideRequestMethods(requestMethods);
+
+ for (HttpMethod httpMethod : mappingRequestMethods) {
+ this.excludedRequestPatterns.add(new RequestPattern(requestPathPattern, httpMethod));
+ }
+
+ return this;
+ }
+
+ private List decideRequestMethods(final HttpMethod[] requestMethods) {
+ final List httpMethods = new ArrayList<>(List.of(requestMethods));
+
+ if (httpMethods.isEmpty()) {
+ httpMethods.addAll(ALL_HTTP_METHODS);
+ }
+
+ return httpMethods;
+ }
+}
diff --git a/backend/src/main/java/com/now/naaga/common/presentation/interceptor/RequestPattern.java b/backend/src/main/java/com/now/naaga/common/presentation/interceptor/RequestPattern.java
new file mode 100644
index 000000000..21ad98239
--- /dev/null
+++ b/backend/src/main/java/com/now/naaga/common/presentation/interceptor/RequestPattern.java
@@ -0,0 +1,15 @@
+package com.now.naaga.common.presentation.interceptor;
+
+import org.springframework.http.HttpMethod;
+
+public record RequestPattern(String path,
+ HttpMethod method) {
+
+ public boolean match(final String inputPath,
+ final String inputMethodAsString) {
+ final HttpMethod inputMethod = HttpMethod.valueOf(inputMethodAsString.toUpperCase());
+ final RequestPattern inputRequestPattern = new RequestPattern(inputPath, inputMethod);
+
+ return RequestMatcher.match(this, inputRequestPattern);
+ }
+}
diff --git a/backend/src/main/java/com/now/naaga/game/application/GameFinishService.java b/backend/src/main/java/com/now/naaga/game/application/GameFinishService.java
new file mode 100644
index 000000000..78738c797
--- /dev/null
+++ b/backend/src/main/java/com/now/naaga/game/application/GameFinishService.java
@@ -0,0 +1,8 @@
+package com.now.naaga.game.application;
+
+import com.now.naaga.game.application.dto.CreateGameResultCommand;
+
+public interface GameFinishService {
+
+ void createGameResult(final CreateGameResultCommand createGameResultCommand);
+}
diff --git a/backend/src/main/java/com/now/naaga/game/application/GameService.java b/backend/src/main/java/com/now/naaga/game/application/GameService.java
index 6493288d6..ccfde8e65 100644
--- a/backend/src/main/java/com/now/naaga/game/application/GameService.java
+++ b/backend/src/main/java/com/now/naaga/game/application/GameService.java
@@ -1,16 +1,16 @@
package com.now.naaga.game.application;
-import com.now.naaga.game.application.dto.CreateGameCommand;
-import com.now.naaga.game.application.dto.EndGameCommand;
-import com.now.naaga.game.application.dto.FindAllGamesCommand;
-import com.now.naaga.game.application.dto.FindGameByIdCommand;
-import com.now.naaga.game.application.dto.FindGameByStatusCommand;
+import com.now.naaga.game.application.dto.*;
import com.now.naaga.game.domain.*;
-import com.now.naaga.game.domain.gamescore.GameScoreCalculator;
import com.now.naaga.game.exception.GameException;
-import com.now.naaga.game.exception.GameNotArrivalException;
+import com.now.naaga.game.exception.GameNotFinishedException;
import com.now.naaga.game.repository.GameRepository;
-import com.now.naaga.game.repository.GameResultRepository;
+
+// TODO: 8/31/23 제거할 대상 - 이슈 범위를 벗어나서 일단은 제거하지 않음
+import com.now.naaga.gameresult.domain.GameResult;
+import com.now.naaga.gameresult.exception.GameResultException;
+import com.now.naaga.gameresult.repository.GameResultRepository;
+
import com.now.naaga.place.application.PlaceService;
import com.now.naaga.place.application.dto.RecommendPlaceCommand;
import com.now.naaga.place.domain.Place;
@@ -19,7 +19,6 @@
import com.now.naaga.player.application.PlayerService;
import com.now.naaga.player.domain.Player;
import com.now.naaga.player.presentation.dto.PlayerRequest;
-import com.now.naaga.score.domain.Score;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -28,30 +27,34 @@
import static com.now.naaga.game.exception.GameExceptionType.*;
+// TODO: 8/31/23 제거할 대상 - 이슈 범위를 벗어나서 일단은 제거하지 않음
+import static com.now.naaga.gameresult.exception.GameResultExceptionType.GAME_RESULT_NOT_EXIST;
+
@Transactional
@Service
public class GameService {
private final GameRepository gameRepository;
+ // TODO: 8/31/23 제거할 대상 - 이슈 범위를 벗어나서 일단은 제거하지 않음
private final GameResultRepository gameResultRepository;
private final PlayerService playerService;
private final PlaceService placeService;
- private final GameScoreCalculator gameScoreCalculator;
+ private final GameFinishService gameFinishService;
public GameService(final GameRepository gameRepository,
- final GameResultRepository gameResultRepository,
- final PlayerService playerService,
- final PlaceService placeService,
- final GameScoreCalculator gameScoreCalculator) {
+ final GameResultRepository gameResultRepository,
+ final PlayerService playerService,
+ final PlaceService placeService,
+ final GameFinishService gameFinishService) {
this.gameRepository = gameRepository;
this.gameResultRepository = gameResultRepository;
this.playerService = playerService;
this.placeService = placeService;
- this.gameScoreCalculator = gameScoreCalculator;
+ this.gameFinishService = gameFinishService;
}
public Game createGame(final CreateGameCommand createGameCommand) {
@@ -72,16 +75,19 @@ public Game createGame(final CreateGameCommand createGameCommand) {
}
}
- @Transactional(noRollbackFor = {GameNotArrivalException.class})
+ @Transactional(noRollbackFor = {GameNotFinishedException.class})
public void endGame(final EndGameCommand endGameCommand) {
- final Game game = gameRepository.findById(endGameCommand.gameId())
- .orElseThrow(() -> new GameException(NOT_EXIST));
+ final Game game = gameRepository.findById(endGameCommand.gameId()).orElseThrow(() -> new GameException(NOT_EXIST));
final Player player = playerService.findPlayerById(endGameCommand.playerId());
game.validateOwner(player);
- final ResultType resultType = game.endGame(endGameCommand.endType(), endGameCommand.position());
- final Score score = gameScoreCalculator.calculate(game, resultType);
- player.addScore(score);
- gameResultRepository.save(new GameResult(resultType, score, game));
+
+ final EndType endType = endGameCommand.endType();
+ final Position position = endGameCommand.position();
+
+ game.endGame(position, endType);
+
+ final CreateGameResultCommand createGameResultCommand = new CreateGameResultCommand(player, game, position, endType);
+ gameFinishService.createGameResult(createGameResultCommand);
}
@Transactional(readOnly = true)
@@ -104,7 +110,7 @@ public GameResult findGameResultByGameId(final Long gameId) {
final List gameResultsByGameId = gameResultRepository.findByGameId(gameId);
if (gameResultsByGameId.isEmpty()) {
- throw new GameException(GAME_RESULT_NOT_EXIST);
+ throw new GameResultException(GAME_RESULT_NOT_EXIST);
}
return gameResultsByGameId.get(0);
@@ -118,24 +124,21 @@ public GameRecord findGameResult(final Long gameId) {
@Transactional(readOnly = true)
public List findAllGameResult(final PlayerRequest playerRequest) {
- final List gamesByPlayerId = gameRepository.findByPlayerId(playerRequest.playerId());
- final List gameResults = gamesByPlayerId.stream()
- .map(game -> findGameResultByGameId(game.getId()))
+ final Long playerId = playerRequest.playerId();
+ final List gameResults = gameResultRepository.findByPlayerId(playerId);
+
+ final List sortedGameResults = gameResults.stream()
.sorted((gr1, gr2) -> gr2.getCreatedAt().compareTo(gr1.getCreatedAt()))
.toList();
- return gameResults.stream()
+ return sortedGameResults.stream()
.map(GameRecord::from)
- .collect(Collectors.toList());
+ .toList();
}
@Transactional(readOnly = true)
public Statistic findStatistic(final PlayerRequest playerRequest) {
- final List gamesByPlayerId = gameRepository.findByPlayerIdAndGameStatus(playerRequest.playerId(),
- GameStatus.DONE);
- final List gameResults = gamesByPlayerId.stream()
- .map(game -> findGameResultByGameId(game.getId()))
- .toList();
+ final List gameResults = gameResultRepository.findByPlayerId(playerRequest.playerId());
final List gameRecords = gameResults.stream()
.map(GameRecord::from).toList();
diff --git a/backend/src/main/java/com/now/naaga/game/application/dto/CreateGameResultCommand.java b/backend/src/main/java/com/now/naaga/game/application/dto/CreateGameResultCommand.java
new file mode 100644
index 000000000..f6aff4c18
--- /dev/null
+++ b/backend/src/main/java/com/now/naaga/game/application/dto/CreateGameResultCommand.java
@@ -0,0 +1,13 @@
+package com.now.naaga.game.application.dto;
+
+import com.now.naaga.game.domain.EndType;
+import com.now.naaga.game.domain.Game;
+import com.now.naaga.place.domain.Position;
+import com.now.naaga.player.domain.Player;
+
+// TODO: 8/31/23 player -> ID / game -> game -> ID
+public record CreateGameResultCommand(Player player,
+ Game game,
+ Position position,
+ EndType endType) {
+}
diff --git a/backend/src/main/java/com/now/naaga/game/domain/Game.java b/backend/src/main/java/com/now/naaga/game/domain/Game.java
index 6babb5835..2ddb4b9ae 100644
--- a/backend/src/main/java/com/now/naaga/game/domain/Game.java
+++ b/backend/src/main/java/com/now/naaga/game/domain/Game.java
@@ -1,39 +1,24 @@
package com.now.naaga.game.domain;
-import static com.now.naaga.game.domain.EndType.GIVE_UP;
-import static com.now.naaga.game.domain.GameStatus.DONE;
-import static com.now.naaga.game.domain.GameStatus.IN_PROGRESS;
-import static com.now.naaga.game.domain.ResultType.FAIL;
-import static com.now.naaga.game.domain.ResultType.SUCCESS;
-import static com.now.naaga.game.exception.GameExceptionType.ALREADY_DONE;
-import static com.now.naaga.game.exception.GameExceptionType.INACCESSIBLE_AUTHENTICATION;
-import static com.now.naaga.game.exception.GameExceptionType.NOT_ARRIVED;
-
import com.now.naaga.common.domain.BaseEntity;
import com.now.naaga.game.exception.GameException;
-import com.now.naaga.game.exception.GameNotArrivalException;
+import com.now.naaga.game.exception.GameExceptionType;
+import com.now.naaga.game.exception.GameNotFinishedException;
import com.now.naaga.place.domain.Place;
import com.now.naaga.place.domain.Position;
import com.now.naaga.player.domain.Player;
-import jakarta.persistence.AttributeOverride;
-import jakarta.persistence.AttributeOverrides;
-import jakarta.persistence.Column;
-import jakarta.persistence.Embedded;
-import jakarta.persistence.Entity;
-import jakarta.persistence.EnumType;
-import jakarta.persistence.Enumerated;
-import jakarta.persistence.GeneratedValue;
-import jakarta.persistence.GenerationType;
-import jakarta.persistence.Id;
-import jakarta.persistence.JoinColumn;
-import jakarta.persistence.ManyToOne;
-import jakarta.persistence.OneToMany;
-import java.math.BigDecimal;
+import jakarta.persistence.*;
+
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
-import org.springframework.data.geo.Distance;
+
+import static com.now.naaga.game.domain.EndType.ARRIVED;
+import static com.now.naaga.game.domain.EndType.GIVE_UP;
+import static com.now.naaga.game.domain.GameStatus.DONE;
+import static com.now.naaga.game.domain.GameStatus.IN_PROGRESS;
+import static com.now.naaga.game.exception.GameExceptionType.*;
@Entity
public class Game extends BaseEntity {
@@ -76,7 +61,9 @@ public class Game extends BaseEntity {
protected Game() {
}
- public Game(final Player player, final Place place, final Position startPosition) {
+ public Game(final Player player,
+ final Place place,
+ final Position startPosition) {
this(null, IN_PROGRESS, player, place, startPosition, MAX_ATTEMPT_COUNT, new ArrayList<>(), LocalDateTime.now(), null);
}
@@ -121,56 +108,62 @@ public boolean canUseMoreHint() {
return hints.size() < MAX_HINT_COUNT;
}
- public ResultType endGame(final EndType endType, final Position position) {
- if (isDone()) {
- throw new GameException(ALREADY_DONE);
- }
- if (endType == GIVE_UP) {
- return giveUpGame();
- }
- return endGameByArrival(position);
+ public double findDistance() {
+ final Position destinationPosition = place.getPosition();
+ return startPosition.calculateDistance(destinationPosition);
}
- private boolean isDone() {
- return gameStatus == DONE;
+ public void endGame(final Position position,
+ final EndType endType) {
+ validateInProgressing();
+
+ if (endType == ARRIVED) {
+ subtractAttempts();
+ }
+
+ setGameStatusDone(position, endType);
}
- private ResultType giveUpGame() {
- gameStatus = DONE;
- endTime = LocalDateTime.now();
- return FAIL;
+ private void validateInProgressing() {
+ if (gameStatus == DONE) {
+ throw new GameException(ALREADY_DONE);
+ }
}
- private ResultType endGameByArrival(final Position position) {
+ private void subtractAttempts() {
+ validateAvailableAttempts();
remainingAttempts--;
- if (isPlayerArrived(position)) {
- return endGameWithSuccess();
- }
- return endGameWithFailure();
}
- private boolean isPlayerArrived(final Position position) {
- return place.isCoordinateInsideBounds(position);
+ private void validateAvailableAttempts() {
+ if (remainingAttempts <= 0) {
+ throw new GameException(GameExceptionType.NOT_REMAIN_ATTEMPTS);
+ }
}
- private ResultType endGameWithSuccess() {
- gameStatus = DONE;
- endTime = LocalDateTime.now();
- return SUCCESS;
+ private void setGameStatusDone(final Position position,
+ final EndType endType) {
+ validateForEnd(position, endType);
+
+ this.endTime = LocalDateTime.now();
+ this.gameStatus = DONE;
}
- private ResultType endGameWithFailure() {
- if (remainingAttempts == 0) {
- gameStatus = DONE;
- endTime = LocalDateTime.now();
- return FAIL;
+ private void validateForEnd(final Position position,
+ final EndType endType) {
+ final boolean isUnfinishedCondition = remainingAttempts > 0
+ && !place.isCoordinateInsideBounds(position)
+ && endType != GIVE_UP;
+
+ if (isUnfinishedCondition) {
+ throw new GameNotFinishedException(NOT_ARRIVED);
}
- throw new GameNotArrivalException(NOT_ARRIVED);
}
-
- public double findDistance() {
- final Position destinationPosition = place.getPosition();
- return startPosition.calculateDistance(destinationPosition);
+
+ public void validateDoneGame() {
+ if(gameStatus == IN_PROGRESS) {
+ throw new GameException(NOT_DONE);
+ }
}
public Long getId() {
diff --git a/backend/src/main/java/com/now/naaga/game/domain/GameRecord.java b/backend/src/main/java/com/now/naaga/game/domain/GameRecord.java
index d1c807d56..80a49ebfe 100644
--- a/backend/src/main/java/com/now/naaga/game/domain/GameRecord.java
+++ b/backend/src/main/java/com/now/naaga/game/domain/GameRecord.java
@@ -2,41 +2,23 @@
import static com.now.naaga.game.domain.Game.MAX_ATTEMPT_COUNT;
+import com.now.naaga.gameresult.domain.GameResult;
+import com.now.naaga.gameresult.domain.ResultType;
import com.now.naaga.place.domain.Position;
import java.time.Duration;
import java.time.LocalDateTime;
-import java.time.LocalTime;
-import java.time.temporal.ChronoUnit;
-public class GameRecord {
-
- private GameResult gameResult;
- private Duration totalPlayTime;
- private int distance;
- private int hintUses;
- private int tryCount;
- private LocalDateTime startTime;
- private LocalDateTime finishTime;
-
- public GameRecord(final GameResult gameResult,
- final Duration totalPlayTime,
- final int distance,
- final int hintUses,
- final int tryCount,
- final LocalDateTime startTime,
- final LocalDateTime finishTime) {
- this.gameResult = gameResult;
- this.totalPlayTime = totalPlayTime;
- this.distance = distance;
- this.hintUses = hintUses;
- this.tryCount = tryCount;
- this.startTime = startTime;
- this.finishTime = finishTime;
- }
+public record GameRecord(GameResult gameResult,
+ Duration totalPlayTime,
+ int distance,
+ int hintUses,
+ int tryCount,
+ LocalDateTime startTime,
+ LocalDateTime finishTime) {
public static GameRecord from(final GameResult gameResult) {
- final Duration totalPlayTime = calculateTotalPlayTime(gameResult.getGame().getStartTime(), gameResult.getGame().getEndTime());
- final int distance = calculateDistance(gameResult.getGame().getStartPosition(), gameResult.getGame().getPlace().getPosition());
+ final Duration totalPlayTime = calculateTotalPlayTime(gameResult);
+ final int distance = calculateDistance(gameResult);
final int hintUses = gameResult.getGame().getHints().size();
final int tryCount = MAX_ATTEMPT_COUNT - gameResult.getGame().getRemainingAttempts();
final LocalDateTime startTime = gameResult.getGame().getStartTime();
@@ -44,45 +26,22 @@ public static GameRecord from(final GameResult gameResult) {
return new GameRecord(gameResult, totalPlayTime, distance, hintUses, tryCount, startTime, finishTime);
}
- private static Duration calculateTotalPlayTime(final LocalDateTime startDateTime,
- final LocalDateTime endDateTime) {
+ private static Duration calculateTotalPlayTime(final GameResult gameResult) {
+ final LocalDateTime startDateTime = gameResult.getGame().getStartTime();
+ final LocalDateTime endDateTime = gameResult.getGame().getEndTime();
return Duration.between(startDateTime, endDateTime);
}
- private static int calculateDistance(final Position startPosition,
- final Position destinationPosition) {
+ private static int calculateDistance(final GameResult gameResult) {
+ if (gameResult.getResultType() == ResultType.FAIL) {
+ return 0;
+ }
+ final Position startPosition = gameResult.getGame().getStartPosition();
+ final Position destinationPosition = gameResult.getGame().getPlace().getPosition();
return (int) startPosition.calculateDistance(destinationPosition);
}
- public int durationToInteger(Duration duration){
+ public int durationToInteger(Duration duration) {
return (int) duration.toMinutes();
}
-
- public GameResult getGameResult() {
- return gameResult;
- }
-
- public Duration getTotalPlayTime() {
- return totalPlayTime;
- }
-
- public int getDistance() {
- return distance;
- }
-
- public int getHintUses() {
- return hintUses;
- }
-
- public int getTryCount() {
- return tryCount;
- }
-
- public LocalDateTime getStartTime() {
- return startTime;
- }
-
- public LocalDateTime getFinishTime() {
- return finishTime;
- }
}
diff --git a/backend/src/main/java/com/now/naaga/game/domain/ResultType.java b/backend/src/main/java/com/now/naaga/game/domain/ResultType.java
deleted file mode 100644
index 83ca772f1..000000000
--- a/backend/src/main/java/com/now/naaga/game/domain/ResultType.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.now.naaga.game.domain;
-
-public enum ResultType {
-
- SUCCESS,
- FAIL,
-}
diff --git a/backend/src/main/java/com/now/naaga/game/domain/Statistic.java b/backend/src/main/java/com/now/naaga/game/domain/Statistic.java
index eded755b2..214bba3b9 100644
--- a/backend/src/main/java/com/now/naaga/game/domain/Statistic.java
+++ b/backend/src/main/java/com/now/naaga/game/domain/Statistic.java
@@ -3,8 +3,8 @@
import java.time.Duration;
import java.util.List;
-import static com.now.naaga.game.domain.ResultType.FAIL;
-import static com.now.naaga.game.domain.ResultType.SUCCESS;
+import static com.now.naaga.gameresult.domain.ResultType.FAIL;
+import static com.now.naaga.gameresult.domain.ResultType.SUCCESS;
public class Statistic {
@@ -42,33 +42,33 @@ public static Statistic of(final List gameRecords) {
private static int countGameResultsSuccess(final List gameRecords) {
return (int) gameRecords.stream()
- .filter(gameRecord -> gameRecord.getGameResult().getResultType() == SUCCESS)
+ .filter(gameRecord -> gameRecord.gameResult().getResultType() == SUCCESS)
.count();
}
private static int countGameResultsFail(final List gameRecords) {
return (int) gameRecords.stream()
- .filter(gameRecord -> gameRecord.getGameResult().getResultType() == FAIL)
+ .filter(gameRecord -> gameRecord.gameResult().getResultType() == FAIL)
.count();
}
private static int sumTotalDistance(final List gameRecords) {
return gameRecords.stream()
- .mapToInt(GameRecord::getDistance)
+ .mapToInt(GameRecord::distance)
.sum();
}
public static Duration sumTotalPlayTime(List gameRecords) {
Duration playTime = Duration.ZERO;
for (GameRecord gameRecord : gameRecords) {
- playTime = playTime.plus(gameRecord.getTotalPlayTime());
+ playTime = playTime.plus(gameRecord.totalPlayTime());
}
return playTime;
}
private static int countUsedHits(final List gameRecords) {
return gameRecords.stream()
- .mapToInt(GameRecord::getHintUses)
+ .mapToInt(GameRecord::hintUses)
.sum();
}
diff --git a/backend/src/main/java/com/now/naaga/game/exception/GameExceptionType.java b/backend/src/main/java/com/now/naaga/game/exception/GameExceptionType.java
index ebcbb0e4e..87df21fa2 100644
--- a/backend/src/main/java/com/now/naaga/game/exception/GameExceptionType.java
+++ b/backend/src/main/java/com/now/naaga/game/exception/GameExceptionType.java
@@ -41,10 +41,10 @@ public enum GameExceptionType implements BaseExceptionType {
"이미 종료된 게임입니다."
),
- GAME_RESULT_NOT_EXIST(
- 434,
- HttpStatus.NOT_FOUND,
- "해당게임의 게임결과가 존재하지 않습니다."
+ NOT_DONE(
+ 417,
+ HttpStatus.BAD_REQUEST,
+ "아직 종료되지 않은 게임입니다."
),
HINT_NOT_EXIST_IN_GAME(
@@ -58,6 +58,12 @@ public enum GameExceptionType implements BaseExceptionType {
HttpStatus.BAD_REQUEST,
"사용할 수 있는 힌트를 모두 소진했습니다."
),
+
+ NOT_REMAIN_ATTEMPTS(
+ 418,
+ HttpStatus.BAD_REQUEST,
+ "시도 횟수를 이미 다 사용한 게임입니다"
+ ),
;
private final int errorCode;
diff --git a/backend/src/main/java/com/now/naaga/game/exception/GameNotArrivalException.java b/backend/src/main/java/com/now/naaga/game/exception/GameNotArrivalException.java
deleted file mode 100644
index 7ce3e9208..000000000
--- a/backend/src/main/java/com/now/naaga/game/exception/GameNotArrivalException.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.now.naaga.game.exception;
-
-public class GameNotArrivalException extends GameException{
-
- public GameNotArrivalException(GameExceptionType gameExceptionType) {
- super(gameExceptionType);
- }
-}
diff --git a/backend/src/main/java/com/now/naaga/game/exception/GameNotFinishedException.java b/backend/src/main/java/com/now/naaga/game/exception/GameNotFinishedException.java
new file mode 100644
index 000000000..06a19641d
--- /dev/null
+++ b/backend/src/main/java/com/now/naaga/game/exception/GameNotFinishedException.java
@@ -0,0 +1,8 @@
+package com.now.naaga.game.exception;
+
+public class GameNotFinishedException extends GameException{
+
+ public GameNotFinishedException(GameExceptionType gameExceptionType) {
+ super(gameExceptionType);
+ }
+}
diff --git a/backend/src/main/java/com/now/naaga/game/presentation/dto/GameResultResponse.java b/backend/src/main/java/com/now/naaga/game/presentation/dto/GameResultResponse.java
index b564b4db4..b2dd3a40a 100644
--- a/backend/src/main/java/com/now/naaga/game/presentation/dto/GameResultResponse.java
+++ b/backend/src/main/java/com/now/naaga/game/presentation/dto/GameResultResponse.java
@@ -1,9 +1,7 @@
package com.now.naaga.game.presentation.dto;
import com.now.naaga.game.domain.GameRecord;
-import com.now.naaga.game.domain.ResultType;
-
-import java.time.LocalDateTime;
+import com.now.naaga.gameresult.domain.ResultType;
public record GameResultResponse(Long id,
Long gameId,
@@ -19,16 +17,16 @@ public record GameResultResponse(Long id,
public static GameResultResponse from(final GameRecord gameRecord) {
return new GameResultResponse(
- gameRecord.getGameResult().getId(),
- gameRecord.getGameResult().getGame().getId(),
- GameDestinationResponse.from(gameRecord.getGameResult().getGame().getPlace()),
- gameRecord.getGameResult().getResultType(),
- gameRecord.getGameResult().getScore().getValue(),
- gameRecord.durationToInteger(gameRecord.getTotalPlayTime()),
- gameRecord.getDistance(),
- gameRecord.getHintUses(),
- gameRecord.getTryCount(),
- gameRecord.getStartTime().toString(),
- gameRecord.getFinishTime().toString());
+ gameRecord.gameResult().getId(),
+ gameRecord.gameResult().getGame().getId(),
+ GameDestinationResponse.from(gameRecord.gameResult().getGame().getPlace()),
+ gameRecord.gameResult().getResultType(),
+ gameRecord.gameResult().getScore().getValue(),
+ gameRecord.durationToInteger(gameRecord.totalPlayTime()),
+ gameRecord.distance(),
+ gameRecord.hintUses(),
+ gameRecord.tryCount(),
+ gameRecord.startTime().toString(),
+ gameRecord.finishTime().toString());
}
}
diff --git a/backend/src/main/java/com/now/naaga/game/repository/GameResultRepository.java b/backend/src/main/java/com/now/naaga/game/repository/GameResultRepository.java
deleted file mode 100644
index 03dc25d44..000000000
--- a/backend/src/main/java/com/now/naaga/game/repository/GameResultRepository.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.now.naaga.game.repository;
-
-import com.now.naaga.game.domain.GameResult;
-import java.util.List;
-import org.springframework.data.jpa.repository.JpaRepository;
-
-public interface GameResultRepository extends JpaRepository {
-
- List findByGameId(final Long gameId);
-}
diff --git a/backend/src/main/java/com/now/naaga/gameresult/application/GameResultService.java b/backend/src/main/java/com/now/naaga/gameresult/application/GameResultService.java
new file mode 100644
index 000000000..010b295ad
--- /dev/null
+++ b/backend/src/main/java/com/now/naaga/gameresult/application/GameResultService.java
@@ -0,0 +1,55 @@
+package com.now.naaga.gameresult.application;
+
+import com.now.naaga.game.application.dto.CreateGameResultCommand;
+import com.now.naaga.game.domain.EndType;
+import com.now.naaga.game.domain.Game;
+import com.now.naaga.game.application.GameFinishService;
+import com.now.naaga.gameresult.domain.GameResult;
+import com.now.naaga.gameresult.domain.ResultType;
+import com.now.naaga.gameresult.domain.gamescore.ResultScoreCalculator;
+import com.now.naaga.gameresult.repository.GameResultRepository;
+import com.now.naaga.place.domain.Position;
+import com.now.naaga.player.application.PlayerService;
+import com.now.naaga.player.application.dto.AddScoreCommand;
+import com.now.naaga.player.domain.Player;
+import com.now.naaga.score.domain.Score;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Transactional
+@Service
+public class GameResultService implements GameFinishService {
+
+ private final GameResultRepository gameResultRepository;
+
+ private final PlayerService playerService;
+
+ private final ResultScoreCalculator resultScoreCalculator;
+
+ public GameResultService(final GameResultRepository gameResultRepository,
+ final PlayerService playerService,
+ final ResultScoreCalculator resultScoreCalculator) {
+ this.gameResultRepository = gameResultRepository;
+ this.playerService = playerService;
+ this.resultScoreCalculator = resultScoreCalculator;
+ }
+
+ @Override
+ public void createGameResult(final CreateGameResultCommand createGameResultCommand) {
+ final Player player = createGameResultCommand.player();
+ final Game game = createGameResultCommand.game();
+ game.validateOwner(player);
+
+ final EndType endType = createGameResultCommand.endType();
+ final Position position = createGameResultCommand.position();
+
+ final ResultType resultType = ResultType.decide(game, endType, position);
+ final Score score = resultScoreCalculator.calculate(game, resultType);
+
+ final AddScoreCommand addScoreCommand = new AddScoreCommand(player.getId(), score);
+ playerService.addScore(addScoreCommand);
+
+ final GameResult gameResult = new GameResult(resultType, score, game);
+ gameResultRepository.save(gameResult);
+ }
+}
diff --git a/backend/src/main/java/com/now/naaga/game/domain/GameResult.java b/backend/src/main/java/com/now/naaga/gameresult/domain/GameResult.java
similarity index 82%
rename from backend/src/main/java/com/now/naaga/game/domain/GameResult.java
rename to backend/src/main/java/com/now/naaga/gameresult/domain/GameResult.java
index 3ed81d94e..12b8a9c01 100644
--- a/backend/src/main/java/com/now/naaga/game/domain/GameResult.java
+++ b/backend/src/main/java/com/now/naaga/gameresult/domain/GameResult.java
@@ -1,16 +1,10 @@
-package com.now.naaga.game.domain;
+package com.now.naaga.gameresult.domain;
import com.now.naaga.common.domain.BaseEntity;
+import com.now.naaga.game.domain.Game;
import com.now.naaga.score.domain.Score;
-import jakarta.persistence.Embedded;
-import jakarta.persistence.Entity;
-import jakarta.persistence.EnumType;
-import jakarta.persistence.Enumerated;
-import jakarta.persistence.GeneratedValue;
-import jakarta.persistence.GenerationType;
-import jakarta.persistence.Id;
-import jakarta.persistence.JoinColumn;
-import jakarta.persistence.OneToOne;
+import jakarta.persistence.*;
+
import java.util.Objects;
@Entity
@@ -26,7 +20,7 @@ public class GameResult extends BaseEntity {
@Embedded
private Score score;
- @OneToOne
+ @OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "game_id")
private Game game;
diff --git a/backend/src/main/java/com/now/naaga/gameresult/domain/ResultType.java b/backend/src/main/java/com/now/naaga/gameresult/domain/ResultType.java
new file mode 100644
index 000000000..8b5d86c33
--- /dev/null
+++ b/backend/src/main/java/com/now/naaga/gameresult/domain/ResultType.java
@@ -0,0 +1,23 @@
+package com.now.naaga.gameresult.domain;
+
+import com.now.naaga.game.domain.EndType;
+import com.now.naaga.game.domain.Game;
+import com.now.naaga.place.domain.Position;
+
+public enum ResultType {
+
+ SUCCESS,
+ FAIL,
+ ;
+
+ public static ResultType decide(final Game game,
+ final EndType endType,
+ final Position position) {
+ game.validateDoneGame();
+
+ if (endType == EndType.ARRIVED && game.getPlace().isCoordinateInsideBounds(position)) {
+ return ResultType.SUCCESS;
+ }
+ return ResultType.FAIL;
+ }
+}
diff --git a/backend/src/main/java/com/now/naaga/game/domain/gamescore/FailGameScorePolicy.java b/backend/src/main/java/com/now/naaga/gameresult/domain/gamescore/FailResultScorePolicy.java
similarity index 57%
rename from backend/src/main/java/com/now/naaga/game/domain/gamescore/FailGameScorePolicy.java
rename to backend/src/main/java/com/now/naaga/gameresult/domain/gamescore/FailResultScorePolicy.java
index 66c3a6049..90a0457ca 100644
--- a/backend/src/main/java/com/now/naaga/game/domain/gamescore/FailGameScorePolicy.java
+++ b/backend/src/main/java/com/now/naaga/gameresult/domain/gamescore/FailResultScorePolicy.java
@@ -1,12 +1,12 @@
-package com.now.naaga.game.domain.gamescore;
+package com.now.naaga.gameresult.domain.gamescore;
import com.now.naaga.game.domain.Game;
-import com.now.naaga.game.domain.ResultType;
+import com.now.naaga.gameresult.domain.ResultType;
import com.now.naaga.score.domain.Score;
-import static com.now.naaga.game.domain.ResultType.FAIL;
+import static com.now.naaga.gameresult.domain.ResultType.FAIL;
-public class FailGameScorePolicy implements GameScorePolicy {
+public class FailResultScorePolicy implements ResultScorePolicy {
@Override
public Score calculate(final Game game) {
diff --git a/backend/src/main/java/com/now/naaga/game/domain/gamescore/GameScoreCalculator.java b/backend/src/main/java/com/now/naaga/gameresult/domain/gamescore/ResultScoreCalculator.java
similarity index 60%
rename from backend/src/main/java/com/now/naaga/game/domain/gamescore/GameScoreCalculator.java
rename to backend/src/main/java/com/now/naaga/gameresult/domain/gamescore/ResultScoreCalculator.java
index 6e909e05a..d1db814ef 100644
--- a/backend/src/main/java/com/now/naaga/game/domain/gamescore/GameScoreCalculator.java
+++ b/backend/src/main/java/com/now/naaga/gameresult/domain/gamescore/ResultScoreCalculator.java
@@ -1,28 +1,28 @@
-package com.now.naaga.game.domain.gamescore;
+package com.now.naaga.gameresult.domain.gamescore;
import com.now.naaga.common.exception.InternalException;
import com.now.naaga.game.domain.Game;
-import com.now.naaga.game.domain.ResultType;
+import com.now.naaga.gameresult.domain.ResultType;
import com.now.naaga.score.domain.Score;
import java.util.List;
import static com.now.naaga.common.exception.InternalExceptionType.FAIL_ESTABLISH_GAME_SCORE_POLICY;
-public class GameScoreCalculator {
+public class ResultScoreCalculator {
- private final List scorePolicies;
+ private final List scorePolicies;
- public GameScoreCalculator(final List scorePolicies) {
+ public ResultScoreCalculator(final List scorePolicies) {
this.scorePolicies = scorePolicies;
}
public Score calculate(final Game game,
final ResultType resultType) {
- final GameScorePolicy gameScorePolicy = scorePolicies.stream()
+ final ResultScorePolicy resultScorePolicy = scorePolicies.stream()
.filter(policy -> policy.hasSameResultType(resultType))
.findAny()
.orElseThrow(() -> new InternalException(FAIL_ESTABLISH_GAME_SCORE_POLICY));
- return gameScorePolicy.calculate(game);
+ return resultScorePolicy.calculate(game);
}
}
diff --git a/backend/src/main/java/com/now/naaga/game/domain/gamescore/GameScorePolicy.java b/backend/src/main/java/com/now/naaga/gameresult/domain/gamescore/ResultScorePolicy.java
similarity index 57%
rename from backend/src/main/java/com/now/naaga/game/domain/gamescore/GameScorePolicy.java
rename to backend/src/main/java/com/now/naaga/gameresult/domain/gamescore/ResultScorePolicy.java
index fd669845a..411857437 100644
--- a/backend/src/main/java/com/now/naaga/game/domain/gamescore/GameScorePolicy.java
+++ b/backend/src/main/java/com/now/naaga/gameresult/domain/gamescore/ResultScorePolicy.java
@@ -1,10 +1,10 @@
-package com.now.naaga.game.domain.gamescore;
+package com.now.naaga.gameresult.domain.gamescore;
import com.now.naaga.game.domain.Game;
-import com.now.naaga.game.domain.ResultType;
+import com.now.naaga.gameresult.domain.ResultType;
import com.now.naaga.score.domain.Score;
-public interface GameScorePolicy {
+public interface ResultScorePolicy {
Score calculate(final Game game);
diff --git a/backend/src/main/java/com/now/naaga/game/domain/gamescore/SuccessGameScorePolicy.java b/backend/src/main/java/com/now/naaga/gameresult/domain/gamescore/SuccessResultScorePolicy.java
similarity index 91%
rename from backend/src/main/java/com/now/naaga/game/domain/gamescore/SuccessGameScorePolicy.java
rename to backend/src/main/java/com/now/naaga/gameresult/domain/gamescore/SuccessResultScorePolicy.java
index 2eb1e2d54..3827c3d1e 100644
--- a/backend/src/main/java/com/now/naaga/game/domain/gamescore/SuccessGameScorePolicy.java
+++ b/backend/src/main/java/com/now/naaga/gameresult/domain/gamescore/SuccessResultScorePolicy.java
@@ -1,7 +1,7 @@
-package com.now.naaga.game.domain.gamescore;
+package com.now.naaga.gameresult.domain.gamescore;
import com.now.naaga.game.domain.Game;
-import com.now.naaga.game.domain.ResultType;
+import com.now.naaga.gameresult.domain.ResultType;
import com.now.naaga.score.domain.Score;
import java.time.Duration;
@@ -9,9 +9,9 @@
import static com.now.naaga.game.domain.Game.MAX_ATTEMPT_COUNT;
import static com.now.naaga.game.domain.Game.MAX_HINT_COUNT;
-import static com.now.naaga.game.domain.ResultType.SUCCESS;
+import static com.now.naaga.gameresult.domain.ResultType.SUCCESS;
-public class SuccessGameScorePolicy implements GameScorePolicy {
+public class SuccessResultScorePolicy implements ResultScorePolicy {
private static final Score BASE_SCORE = new Score(50);
private static final double HINT_SCORE_RATIO = 0.3;
diff --git a/backend/src/main/java/com/now/naaga/gameresult/exception/GameResultException.java b/backend/src/main/java/com/now/naaga/gameresult/exception/GameResultException.java
new file mode 100644
index 000000000..2200ac3ad
--- /dev/null
+++ b/backend/src/main/java/com/now/naaga/gameresult/exception/GameResultException.java
@@ -0,0 +1,18 @@
+package com.now.naaga.gameresult.exception;
+
+import com.now.naaga.common.exception.BaseException;
+import com.now.naaga.common.exception.BaseExceptionType;
+
+public class GameResultException extends BaseException {
+
+ private final GameResultExceptionType gameResultExceptionType;
+
+ public GameResultException(final GameResultExceptionType gameResultExceptionType) {
+ this.gameResultExceptionType = gameResultExceptionType;
+ }
+
+ @Override
+ public BaseExceptionType exceptionType() {
+ return gameResultExceptionType;
+ }
+}
diff --git a/backend/src/main/java/com/now/naaga/gameresult/exception/GameResultExceptionType.java b/backend/src/main/java/com/now/naaga/gameresult/exception/GameResultExceptionType.java
new file mode 100644
index 000000000..454c2d8d3
--- /dev/null
+++ b/backend/src/main/java/com/now/naaga/gameresult/exception/GameResultExceptionType.java
@@ -0,0 +1,41 @@
+package com.now.naaga.gameresult.exception;
+
+import com.now.naaga.common.exception.BaseExceptionType;
+import org.springframework.http.HttpStatus;
+
+public enum GameResultExceptionType implements BaseExceptionType {
+
+ GAME_RESULT_NOT_EXIST(
+ 434,
+ HttpStatus.NOT_FOUND,
+ "해당게임의 게임결과가 존재하지 않습니다."
+ ),
+ ;
+
+ private final int errorCode;
+ private final HttpStatus httpStatus;
+ private final String errorMessage;
+
+ GameResultExceptionType(final int errorCode,
+ final HttpStatus httpStatus,
+ final String errorMessage) {
+ this.errorCode = errorCode;
+ this.httpStatus = httpStatus;
+ this.errorMessage = errorMessage;
+ }
+
+ @Override
+ public int errorCode() {
+ return errorCode;
+ }
+
+ @Override
+ public HttpStatus httpStatus() {
+ return httpStatus;
+ }
+
+ @Override
+ public String errorMessage() {
+ return errorMessage;
+ }
+}
diff --git a/backend/src/main/java/com/now/naaga/gameresult/repository/GameResultRepository.java b/backend/src/main/java/com/now/naaga/gameresult/repository/GameResultRepository.java
new file mode 100644
index 000000000..33409563a
--- /dev/null
+++ b/backend/src/main/java/com/now/naaga/gameresult/repository/GameResultRepository.java
@@ -0,0 +1,16 @@
+package com.now.naaga.gameresult.repository;
+
+import com.now.naaga.gameresult.domain.GameResult;
+import java.util.List;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+
+public interface GameResultRepository extends JpaRepository {
+
+ List findByGameId(final Long gameId);
+
+ @Query("SELECT r FROM GameResult r JOIN FETCH r.game g LEFT JOIN FETCH g.hints JOIN FETCH g.place Where g.player.id = :playerId")
+ List findByPlayerId(@Param("playerId") Long playerId);
+
+}
diff --git a/backend/src/main/java/com/now/naaga/like/domain/PlaceLike.java b/backend/src/main/java/com/now/naaga/like/domain/PlaceLike.java
new file mode 100644
index 000000000..744ed121b
--- /dev/null
+++ b/backend/src/main/java/com/now/naaga/like/domain/PlaceLike.java
@@ -0,0 +1,96 @@
+package com.now.naaga.like.domain;
+
+import com.now.naaga.common.domain.BaseEntity;
+import com.now.naaga.place.domain.Place;
+import com.now.naaga.player.domain.Player;
+import jakarta.persistence.Entity;
+import jakarta.persistence.EnumType;
+import jakarta.persistence.Enumerated;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import java.util.Objects;
+
+// 아직 미구역 영역입니다. 사실 백엔드 디렉토리 변경을 위한 변경사항입니다.
+@Entity
+public class PlaceLike extends BaseEntity {
+
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Id
+ private Long id;
+
+ @ManyToOne
+ @JoinColumn(name = "place_id")
+ private Place place;
+
+ @ManyToOne
+ @JoinColumn(name = "player_id")
+ private Player player;
+
+ @Enumerated(EnumType.STRING)
+ private PlaceLikeType placeLikeType;
+
+ protected PlaceLike() {
+ }
+
+ public PlaceLike(final Place place,
+ final Player player,
+ final PlaceLikeType placeLikeType) {
+ this(null, place, player, placeLikeType);
+ }
+
+ public PlaceLike(final Long id,
+ final Place place,
+ final Player player,
+ final PlaceLikeType placeLikeType) {
+ this.id = id;
+ this.place = place;
+ this.player = player;
+ this.placeLikeType = placeLikeType;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public Place getPlace() {
+ return place;
+ }
+
+ public Player getPlayer() {
+ return player;
+ }
+
+ public PlaceLikeType getType() {
+ return placeLikeType;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final PlaceLike placeLike = (PlaceLike) o;
+ return Objects.equals(id, placeLike.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id);
+ }
+
+ @Override
+ public String toString() {
+ return "PlaceLike{" +
+ "id=" + id +
+ ", placeId=" + place.getId() +
+ ", playerId=" + player.getId() +
+ ", placeLikeType=" + placeLikeType +
+ '}';
+ }
+}
diff --git a/backend/src/main/java/com/now/naaga/like/domain/PlaceLikeType.java b/backend/src/main/java/com/now/naaga/like/domain/PlaceLikeType.java
new file mode 100644
index 000000000..e18438bbc
--- /dev/null
+++ b/backend/src/main/java/com/now/naaga/like/domain/PlaceLikeType.java
@@ -0,0 +1,8 @@
+package com.now.naaga.like.domain;
+
+public enum PlaceLikeType {
+
+ LIKE,
+ DISLIKE,
+ ;
+}
diff --git a/backend/src/main/java/com/now/naaga/like/repository/PlaceLikeRepository.java b/backend/src/main/java/com/now/naaga/like/repository/PlaceLikeRepository.java
new file mode 100644
index 000000000..920c754dd
--- /dev/null
+++ b/backend/src/main/java/com/now/naaga/like/repository/PlaceLikeRepository.java
@@ -0,0 +1,8 @@
+package com.now.naaga.like.repository;
+
+import com.now.naaga.like.domain.PlaceLike;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface PlaceLikeRepository extends JpaRepository {
+
+}
diff --git a/backend/src/main/java/com/now/naaga/member/application/MemberService.java b/backend/src/main/java/com/now/naaga/member/application/MemberService.java
index c3b649119..eb1996554 100644
--- a/backend/src/main/java/com/now/naaga/member/application/MemberService.java
+++ b/backend/src/main/java/com/now/naaga/member/application/MemberService.java
@@ -2,7 +2,8 @@
import static com.now.naaga.member.exception.MemberExceptionType.NOT_EXIST_MEMBER;
-import com.now.naaga.auth.infrastructure.dto.MemberAuth;
+import com.now.naaga.member.application.dto.CreateMemberCommand;
+import com.now.naaga.member.application.dto.DeleteMemberCommand;
import com.now.naaga.member.domain.Member;
import com.now.naaga.member.exception.MemberException;
import com.now.naaga.member.persistence.repository.MemberRepository;
@@ -39,8 +40,6 @@ public Member create(final CreateMemberCommand createMemberCommand) {
public void deleteByMemberId(final DeleteMemberCommand deleteMemberCommand) {
final Long memberId = deleteMemberCommand.memberId();
- System.out.println("=---------------------------");
- System.out.println(memberId);
final Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new MemberException(NOT_EXIST_MEMBER));
memberRepository.delete(member);
diff --git a/backend/src/main/java/com/now/naaga/member/application/CreateMemberCommand.java b/backend/src/main/java/com/now/naaga/member/application/dto/CreateMemberCommand.java
similarity index 53%
rename from backend/src/main/java/com/now/naaga/member/application/CreateMemberCommand.java
rename to backend/src/main/java/com/now/naaga/member/application/dto/CreateMemberCommand.java
index a9b305085..b37c43dc4 100644
--- a/backend/src/main/java/com/now/naaga/member/application/CreateMemberCommand.java
+++ b/backend/src/main/java/com/now/naaga/member/application/dto/CreateMemberCommand.java
@@ -1,4 +1,4 @@
-package com.now.naaga.member.application;
+package com.now.naaga.member.application.dto;
public record CreateMemberCommand(String email) {
}
diff --git a/backend/src/main/java/com/now/naaga/member/application/DeleteMemberCommand.java b/backend/src/main/java/com/now/naaga/member/application/dto/DeleteMemberCommand.java
similarity index 54%
rename from backend/src/main/java/com/now/naaga/member/application/DeleteMemberCommand.java
rename to backend/src/main/java/com/now/naaga/member/application/dto/DeleteMemberCommand.java
index 347ede966..8a43da3b1 100644
--- a/backend/src/main/java/com/now/naaga/member/application/DeleteMemberCommand.java
+++ b/backend/src/main/java/com/now/naaga/member/application/dto/DeleteMemberCommand.java
@@ -1,4 +1,4 @@
-package com.now.naaga.member.application;
+package com.now.naaga.member.application.dto;
public record DeleteMemberCommand(Long memberId) {
}
diff --git a/backend/src/main/java/com/now/naaga/place/application/PlaceService.java b/backend/src/main/java/com/now/naaga/place/application/PlaceService.java
index 5f33a39c1..d27464e1a 100644
--- a/backend/src/main/java/com/now/naaga/place/application/PlaceService.java
+++ b/backend/src/main/java/com/now/naaga/place/application/PlaceService.java
@@ -3,7 +3,6 @@
import static com.now.naaga.place.exception.PlaceExceptionType.NO_EXIST;
import com.now.naaga.common.domain.OrderType;
-import com.now.naaga.common.infrastructure.FileManager;
import com.now.naaga.place.application.dto.CreatePlaceCommand;
import com.now.naaga.place.application.dto.FindAllPlaceCommand;
import com.now.naaga.place.application.dto.FindPlaceByIdCommand;
@@ -17,13 +16,10 @@
import com.now.naaga.place.persistence.repository.PlaceRepository;
import com.now.naaga.player.application.PlayerService;
import com.now.naaga.player.domain.Player;
-import java.io.File;
+import com.now.naaga.temporaryplace.application.TemporaryPlaceService;
import java.util.List;
-
-import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
-import org.springframework.web.multipart.MultipartFile;
@Transactional
@Service
@@ -33,22 +29,22 @@ public class PlaceService {
private final PlayerService playerService;
+ private final TemporaryPlaceService temporaryPlaceService;
+
private final PlaceCheckService placeCheckService;
private final PlaceRecommendService placeRecommendService;
- private final FileManager fileManager;
-
public PlaceService(final PlaceRepository placeRepository,
final PlayerService playerService,
+ final TemporaryPlaceService temporaryPlaceService,
final PlaceCheckService placeCheckService,
- final PlaceRecommendService placeRecommendService,
- final FileManager fileManager) {
+ final PlaceRecommendService placeRecommendService) {
this.placeRepository = placeRepository;
this.playerService = playerService;
+ this.temporaryPlaceService = temporaryPlaceService;
this.placeCheckService = placeCheckService;
this.placeRecommendService = placeRecommendService;
- this.fileManager = fileManager;
}
@Transactional(readOnly = true)
@@ -63,7 +59,7 @@ public List findAllPlace(final FindAllPlaceCommand findAllPlaceCommand) {
@Transactional(readOnly = true)
public Place findPlaceById(final FindPlaceByIdCommand findPlaceByIdCommand) {
return placeRepository.findById(findPlaceByIdCommand.placeId())
- .orElseThrow(() -> new PlaceException(NO_EXIST));
+ .orElseThrow(() -> new PlaceException(NO_EXIST));
}
@Transactional(readOnly = true)
@@ -73,23 +69,18 @@ public Place recommendPlaceByPosition(final RecommendPlaceCommand recommendPlace
}
public Place createPlace(final CreatePlaceCommand createPlaceCommand) {
- final Position position = createPlaceCommand.position();
- placeCheckService.checkOtherPlaceNearby(position);
- final File uploadPath = fileManager.save(createPlaceCommand.imageFile());
- try {
- final Long playerId = createPlaceCommand.playerId();
- final Player registeredPlayer = playerService.findPlayerById(playerId);
- final Place place = new Place(
- createPlaceCommand.name(),
- createPlaceCommand.description(),
- position,
- fileManager.convertToUrlPath(uploadPath),
- registeredPlayer);
- placeRepository.save(place);
- return place;
- } catch (final RuntimeException exception) {
- uploadPath.delete();
- throw exception;
- }
+ placeCheckService.checkOtherPlaceNearby(createPlaceCommand.position());
+
+ final Long registeredPlayerId = createPlaceCommand.registeredPlayerId();
+ final Player registeredPlayer = playerService.findPlayerById(registeredPlayerId);
+ final Place place = new Place(createPlaceCommand.name(),
+ createPlaceCommand.description(),
+ createPlaceCommand.position(),
+ createPlaceCommand.imageUrl(),
+ registeredPlayer);
+
+ placeRepository.save(place);
+ temporaryPlaceService.deleteById(createPlaceCommand.temporaryPlaceId());
+ return place;
}
}
diff --git a/backend/src/main/java/com/now/naaga/place/application/dto/CreatePlaceCommand.java b/backend/src/main/java/com/now/naaga/place/application/dto/CreatePlaceCommand.java
index ff174d6e3..c4787af5c 100644
--- a/backend/src/main/java/com/now/naaga/place/application/dto/CreatePlaceCommand.java
+++ b/backend/src/main/java/com/now/naaga/place/application/dto/CreatePlaceCommand.java
@@ -2,24 +2,24 @@
import com.now.naaga.place.domain.Position;
import com.now.naaga.place.presentation.dto.CreatePlaceRequest;
-import com.now.naaga.player.presentation.dto.PlayerRequest;
-import org.springframework.web.multipart.MultipartFile;
-public record CreatePlaceCommand(Long playerId,
- String name,
+public record CreatePlaceCommand(String name,
String description,
Position position,
- MultipartFile imageFile) {
+ String imageUrl,
+ Long registeredPlayerId,
+ Long temporaryPlaceId) {
- public static CreatePlaceCommand of(final PlayerRequest playerRequest,
- final CreatePlaceRequest createPlaceRequest) {
+ public static CreatePlaceCommand from(final CreatePlaceRequest createPlaceRequest) {
Position position = Position.of(createPlaceRequest.latitude(), createPlaceRequest.longitude());
+
return new CreatePlaceCommand(
- playerRequest.playerId(),
createPlaceRequest.name(),
createPlaceRequest.description(),
position,
- createPlaceRequest.imageFile()
+ createPlaceRequest.imageUrl(),
+ createPlaceRequest.registeredPlayerId(),
+ createPlaceRequest.temporaryPlaceId()
);
}
}
diff --git a/backend/src/main/java/com/now/naaga/place/domain/Place.java b/backend/src/main/java/com/now/naaga/place/domain/Place.java
index cf1135688..6b2682faa 100644
--- a/backend/src/main/java/com/now/naaga/place/domain/Place.java
+++ b/backend/src/main/java/com/now/naaga/place/domain/Place.java
@@ -6,14 +6,8 @@
import com.now.naaga.place.exception.PlaceException;
import com.now.naaga.place.exception.PlaceExceptionType;
import com.now.naaga.player.domain.Player;
-import jakarta.persistence.CascadeType;
-import jakarta.persistence.Embedded;
-import jakarta.persistence.Entity;
-import jakarta.persistence.GeneratedValue;
-import jakarta.persistence.GenerationType;
-import jakarta.persistence.Id;
-import jakarta.persistence.JoinColumn;
-import jakarta.persistence.ManyToOne;
+import jakarta.persistence.*;
+
import java.util.Objects;
@Entity
@@ -32,7 +26,7 @@ public class Place extends BaseEntity {
private String imageUrl;
- @ManyToOne
+ @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "player_id")
private Player registeredPlayer;
diff --git a/backend/src/main/java/com/now/naaga/place/domain/PlaceCheckService.java b/backend/src/main/java/com/now/naaga/place/domain/PlaceCheckService.java
index 9020f64ec..b44b9ac6e 100644
--- a/backend/src/main/java/com/now/naaga/place/domain/PlaceCheckService.java
+++ b/backend/src/main/java/com/now/naaga/place/domain/PlaceCheckService.java
@@ -3,10 +3,11 @@
import com.now.naaga.place.exception.PlaceException;
import com.now.naaga.place.exception.PlaceExceptionType;
import com.now.naaga.place.persistence.repository.PlaceRepository;
-import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
+import java.util.List;
+
@Transactional
@Service
public class PlaceCheckService {
@@ -20,7 +21,7 @@ public PlaceCheckService(final PlaceRepository placeRepository) {
@Transactional(readOnly = true)
public void checkOtherPlaceNearby(final Position position) {
List places = placeRepository.findPlaceByPositionAndDistance(position, 0.02);
- if (places.size() > 0) {
+ if (!places.isEmpty()) {
throw new PlaceException(PlaceExceptionType.ALREADY_EXIST_NEARBY);
}
}
diff --git a/backend/src/main/java/com/now/naaga/place/domain/PlaceRecommendService.java b/backend/src/main/java/com/now/naaga/place/domain/PlaceRecommendService.java
index 559eb79de..93060f064 100644
--- a/backend/src/main/java/com/now/naaga/place/domain/PlaceRecommendService.java
+++ b/backend/src/main/java/com/now/naaga/place/domain/PlaceRecommendService.java
@@ -10,11 +10,10 @@
import static com.now.naaga.place.exception.PlaceExceptionType.NO_EXIST;
-@Transactional
@Service
public class PlaceRecommendService {
- private static final int DISTANCE = 500;
+ private static final double DISTANCE = 0.3;
private final PlaceRepository placeRepository;
diff --git a/backend/src/main/java/com/now/naaga/place/presentation/PlaceController.java b/backend/src/main/java/com/now/naaga/place/presentation/PlaceController.java
index 780f16269..19c388596 100644
--- a/backend/src/main/java/com/now/naaga/place/presentation/PlaceController.java
+++ b/backend/src/main/java/com/now/naaga/place/presentation/PlaceController.java
@@ -14,9 +14,9 @@
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@@ -54,14 +54,13 @@ public ResponseEntity findPlaceById(@PathVariable Long placeId) {
}
@PostMapping
- public ResponseEntity createPlace(@Auth final PlayerRequest playerRequest,
- @ModelAttribute final CreatePlaceRequest createPlaceRequest) {
- CreatePlaceCommand createPlaceCommand = CreatePlaceCommand.of(playerRequest, createPlaceRequest);
- final Place savedPlace = placeService.createPlace(createPlaceCommand);
- final PlaceResponse response = PlaceResponse.from(savedPlace);
+ public ResponseEntity createPlace(@RequestBody final CreatePlaceRequest createPlaceRequest) {
+ final CreatePlaceCommand createPlaceCommand = CreatePlaceCommand.from(createPlaceRequest);
+ final Place place = placeService.createPlace(createPlaceCommand);
+ final PlaceResponse response = PlaceResponse.from(place);
return ResponseEntity
.status(HttpStatus.CREATED)
- .location(URI.create("/places/" + savedPlace.getId()))
+ .location(URI.create("/places/" + place.getId()))
.body(response);
}
}
diff --git a/backend/src/main/java/com/now/naaga/place/presentation/dto/CreatePlaceRequest.java b/backend/src/main/java/com/now/naaga/place/presentation/dto/CreatePlaceRequest.java
index eb68b05ac..40f666f9d 100644
--- a/backend/src/main/java/com/now/naaga/place/presentation/dto/CreatePlaceRequest.java
+++ b/backend/src/main/java/com/now/naaga/place/presentation/dto/CreatePlaceRequest.java
@@ -1,10 +1,10 @@
package com.now.naaga.place.presentation.dto;
-import org.springframework.web.multipart.MultipartFile;
-
public record CreatePlaceRequest(String name,
String description,
Double latitude,
Double longitude,
- MultipartFile imageFile) {
+ String imageUrl,
+ Long registeredPlayerId,
+ Long temporaryPlaceId) {
}
diff --git a/backend/src/main/java/com/now/naaga/place/presentation/dto/PlaceResponse.java b/backend/src/main/java/com/now/naaga/place/presentation/dto/PlaceResponse.java
index ccca84bff..c30e133b8 100644
--- a/backend/src/main/java/com/now/naaga/place/presentation/dto/PlaceResponse.java
+++ b/backend/src/main/java/com/now/naaga/place/presentation/dto/PlaceResponse.java
@@ -2,7 +2,6 @@
import com.now.naaga.place.domain.Place;
import java.util.List;
-import java.util.stream.Collectors;
public record PlaceResponse(Long id,
String name,
@@ -22,7 +21,7 @@ public static PlaceResponse from(final Place savedPlace) {
public static List convertToPlaceResponses(final List