diff --git a/.github/workflows/cd-back-dev.yml b/.github/workflows/cd-back-dev.yml
index c6ec9db95..e23c71e9a 100644
--- a/.github/workflows/cd-back-dev.yml
+++ b/.github/workflows/cd-back-dev.yml
@@ -4,10 +4,14 @@ on:
push:
branches:
- dev
- paths: 'backend/**'
-
+ paths:
+ - 'backend/**'
workflow_dispatch:
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
defaults:
run:
working-directory: backend
@@ -16,28 +20,38 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- - name: 리포지토리 체크아웃
+ - name: repository checkout
uses: actions/checkout@v3
with:
submodules: recursive
token: ${{ secrets.SUBMODULE_TOKEN }}
- - name: 자바 설치
+ - name: install java 17
uses: actions/setup-java@v3
with:
java-version: 17
distribution: 'zulu'
- - name: gradlew 권한 부여
+ - name: assign grant gradlew
run: chmod +x gradlew
- - name: Gradle Test
- run: ./gradlew test
+ - name: bootJar with gradle
+ run: ./gradlew bootJar
+
+ - name: Docker Login
+ uses: docker/login-action@v3.1.0
+ with:
+ username: ${{ vars.DOCKER_HUB_DEV_USERNAME }}
+ password: ${{ secrets.DOCKER_HUB_DEV_LOGIN_TOKEN }}
+
+ - name: Build And Push docker image
+ run: docker build --platform linux/arm64/v8 --push --tag ${{ vars.DOCKER_DEV_TAG }} .
- - name: trigger to jenkins dev cd
- uses: appleboy/jenkins-action@master
+ - name: run application use ssh
+ uses: appleboy/ssh-action@master
with:
- url: ${{ secrets.JENKINS_URL }}
- user: "festago"
- token: ${{ secrets.JENKINS_API_TOKEN}}
- job: "festago-dev-cd"
+ host: ${{ vars.FESTAGO_DEV_IP }}
+ username: ${{ vars.FESTAGO_DEV_USERNAME }}
+ key: ${{secrets.FESTAGO_DEV_SSH_KEY}}
+ script_stop: true
+ script: ${{ vars.FESTAGO_DEV_DEPLOY_COMMAND }}
diff --git a/.github/workflows/cd-back-prod.yml b/.github/workflows/cd-back-prod.yml
new file mode 100644
index 000000000..5bf67a8af
--- /dev/null
+++ b/.github/workflows/cd-back-prod.yml
@@ -0,0 +1,48 @@
+name: CD-Back-Prod
+
+on:
+ workflow_dispatch:
+
+defaults:
+ run:
+ working-directory: backend
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: repository checkout
+ uses: actions/checkout@v3
+ with:
+ submodules: recursive
+ token: ${{ secrets.SUBMODULE_TOKEN }}
+
+ - name: install java 17
+ uses: actions/setup-java@v3
+ with:
+ java-version: 17
+ distribution: 'zulu'
+
+ - name: assign grant gradlew
+ run: chmod +x gradlew
+
+ - name: bootJar with gradle
+ run: ./gradlew bootJar
+
+ - name: Docker Login
+ uses: docker/login-action@v3.1.0
+ with:
+ username: ${{ vars.DOCKER_HUB_PROD_USERNAME }}
+ password: ${{ secrets.DOCKER_HUB_PROD_LOGIN_TOKEN }}
+
+ - name: Build And Push docker image
+ run: docker build --platform linux/arm64/v8 --push --tag ${{ vars.DOCKER_PROD_TAG }} .
+
+ - name: run application use ssh
+ uses: appleboy/ssh-action@master
+ with:
+ host: ${{ vars.FESTAGO_PROD_IP }}
+ username: ${{ vars.FESTAGO_PROD_USERNAME }}
+ key: ${{secrets.FESTAGO_PROD_SSH_KEY}}
+ script_stop: true
+ script: ${{ vars.FESTAGO_PROD_DEPLOY_COMMAND }}
diff --git a/.github/workflows/ci-back.yml b/.github/workflows/ci-back.yml
index 849fea334..b15191978 100644
--- a/.github/workflows/ci-back.yml
+++ b/.github/workflows/ci-back.yml
@@ -1,11 +1,13 @@
-name: CI-Back
+name: CI Back
on:
pull_request:
branches:
- dev
- main
- paths: 'backend/**'
+ - feat/**
+ paths:
+ - backend/**
defaults:
run:
@@ -15,33 +17,42 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- - name: 리포지토리 체크아웃
+ - name: Repository checkout
uses: actions/checkout@v3
with:
submodules: recursive
token: ${{ secrets.SUBMODULE_TOKEN }}
- - name: 자바 설치
+ - name: Setup java 17
uses: actions/setup-java@v3
with:
java-version: 17
distribution: 'zulu'
- - name: gradlew 권한 부여
+ - name: Cache gradle packages
+ uses: actions/cache@v3
+ with:
+ path: |
+ ~/.gradle/caches
+ ~/.gradle/wrapper
+ key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
+ restore-keys: |
+ ${{ runner.os }}-gradle-
+
+ - name: Assign grant gradlew
run: chmod +x gradlew
- - name: Gradle build
- run: ./gradlew build
+ - name: Test with gradle
+ run: ./gradlew --info test
- - name: 테스트 결과 PR에 커멘트 등록
- uses: EnricoMi/publish-unit-test-result-action@v1
+ - name: Publish test results
+ uses: EnricoMi/publish-unit-test-result-action@v2
if: always()
with:
files: '**/build/test-results/test/TEST-*.xml'
- - name: 테스트 실패 Check 코멘트 등록
- uses: mikepenz/action-junit-report@v3
+ - name: Publish test report
+ uses: mikepenz/action-junit-report@v4
if: always()
with:
report_paths: '**/build/test-results/test/TEST-*.xml'
- token: ${{ github.token }}
diff --git a/.github/workflows/closed-issue-notification.yml b/.github/workflows/closed-issue-notification.yml
deleted file mode 100644
index 6031248af..000000000
--- a/.github/workflows/closed-issue-notification.yml
+++ /dev/null
@@ -1,40 +0,0 @@
-name: Closed Issue Notification
-on:
- issues:
- types:
- - closed
-
-jobs:
- create-issue:
- name: Send closed issue notification to slack
- runs-on: ubuntu-latest
- steps:
- - name: Send Issue
- uses: 8398a7/action-slack@v3
- with:
- status: custom
- custom_payload: |
- {
- text: "*이슈가 닫혔습니다!*",
- attachments: [{
- fallback: 'fallback',
- color: '#7539DE',
- title: 'Title',
- text: '<${{ github.event.issue.html_url }}|${{ github.event.issue.title }}>',
- fields: [{
- title: 'Issue number',
- value: '#${{ github.event.issue.number }}',
- short: true
- },
- {
- title: 'Author',
- value: '${{ github.event.issue.user.login }}',
- short: true
- }],
- actions: [{
- }]
- }]
- }
- env:
- SLACK_WEBHOOK_URL: ${{ secrets.SLACK_ISSUE_WEBHOOK_URL }}
- if: always()
diff --git a/.github/workflows/closed-pr-notification.yml b/.github/workflows/closed-pr-notification.yml
deleted file mode 100644
index d92c2d37e..000000000
--- a/.github/workflows/closed-pr-notification.yml
+++ /dev/null
@@ -1,106 +0,0 @@
-name: Closed PR Notification
-on:
- pull_request:
- branches:
- - dev
- - main
- types:
- - closed
-
-jobs:
- create-issue:
- name: PR closed notification to slack
- runs-on: ubuntu-latest
- steps:
- - name: Send closed PR notification
- if: github.event.pull_request.merged != true
- uses: slackapi/slack-github-action@v1.24.0
- with:
- payload: |
- {
- "text": "*PR이 닫혔습니다!*",
- "attachments": [
- {
- "color": "#CF2027",
- "blocks": [
- {
- "type": "section",
- "text": {
- "type": "mrkdwn",
- "text": "*Title*\n<${{ github.event.pull_request.html_url }}|${{ github.event.pull_request.title }}>"
- }
- },
- {
- "type": "section",
- "fields": [
- {
- "type": "mrkdwn",
- "text": "*Base branch*\n${{ github.base_ref }}"
- },
- {
- "type": "mrkdwn",
- "text": "*Compare branch*\n${{ github.head_ref }}"
- },
- {
- "type": "mrkdwn",
- "text": "*PR number*\n#${{ github.event.pull_request.number }}"
- },
- {
- "type": "mrkdwn",
- "text": "*Author*\n${{ github.event.pull_request.user.login }}"
- }
- ]
- }
- ]
- }
- ]
- }
- env:
- SLACK_WEBHOOK_URL: ${{ secrets.SLACK_PR_WEBHOOK_URL }}
- SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
- - name: Send merged PR notification
- if: github.event.pull_request.merged == true
- uses: slackapi/slack-github-action@v1.24.0
- with:
- payload: |
- {
- "text": "*PR이 머지됐습니다!*",
- "attachments": [
- {
- "color": "#7539DE",
- "blocks": [
- {
- "type": "section",
- "text": {
- "type": "mrkdwn",
- "text": "*Title*\n<${{ github.event.pull_request.html_url }}|${{ github.event.pull_request.title }}>"
- }
- },
- {
- "type": "section",
- "fields": [
- {
- "type": "mrkdwn",
- "text": "*Base branch*\n${{ github.base_ref }}"
- },
- {
- "type": "mrkdwn",
- "text": "*Compare branch*\n${{ github.head_ref }}"
- },
- {
- "type": "mrkdwn",
- "text": "*PR number*\n#${{ github.event.pull_request.number }}"
- },
- {
- "type": "mrkdwn",
- "text": "*Author*\n${{ github.event.pull_request.user.login }}"
- }
- ]
- }
- ]
- }
- ]
- }
- env:
- SLACK_WEBHOOK_URL: ${{ secrets.SLACK_PR_WEBHOOK_URL }}
- SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
diff --git a/.github/workflows/opend-issue-notification.yml b/.github/workflows/opend-issue-notification.yml
deleted file mode 100644
index 2204395bb..000000000
--- a/.github/workflows/opend-issue-notification.yml
+++ /dev/null
@@ -1,40 +0,0 @@
-name: Opend Issue Notification
-on:
- issues:
- types:
- - opened
-
-jobs:
- create-issue:
- name: Send opend issue notification to slack
- runs-on: ubuntu-latest
- steps:
- - name: Send Issue
- uses: 8398a7/action-slack@v3
- with:
- status: custom
- custom_payload: |
- {
- text: "*새로운 이슈가 생성되었습니다!*",
- attachments: [{
- fallback: 'fallback',
- color: '#1F7629',
- title: 'Title',
- text: '<${{ github.event.issue.html_url }}|${{ github.event.issue.title }}>',
- fields: [{
- title: 'Issue number',
- value: '#${{ github.event.issue.number }}',
- short: true
- },
- {
- title: 'Author',
- value: '${{ github.event.issue.user.login }}',
- short: true
- }],
- actions: [{
- }]
- }]
- }
- env:
- SLACK_WEBHOOK_URL: ${{ secrets.SLACK_ISSUE_WEBHOOK_URL }}
- if: always()
diff --git a/.github/workflows/opened-pr-notification.yml b/.github/workflows/opened-pr-notification.yml
deleted file mode 100644
index 1ff6bba58..000000000
--- a/.github/workflows/opened-pr-notification.yml
+++ /dev/null
@@ -1,59 +0,0 @@
-name: Opened PR Notification
-on:
- pull_request:
- branches:
- - dev
- - main
- types:
- - opened
-
-jobs:
- create-issue:
- name: PR opened notification to slack
- runs-on: ubuntu-latest
- steps:
- - name: Send opened PR notification
- uses: slackapi/slack-github-action@v1.24.0
- with:
- payload: |
- {
- "text": "*새로운 PR이 생성되었습니다!*",
- "attachments": [
- {
- "color": "#1F7629",
- "blocks": [
- {
- "type": "section",
- "text": {
- "type": "mrkdwn",
- "text": "*Title*\n<${{ github.event.pull_request.html_url }}|${{ github.event.pull_request.title }}>"
- }
- },
- {
- "type": "section",
- "fields": [
- {
- "type": "mrkdwn",
- "text": "*Base branch*\n${{ github.base_ref }}"
- },
- {
- "type": "mrkdwn",
- "text": "*Compare branch*\n${{ github.head_ref }}"
- },
- {
- "type": "mrkdwn",
- "text": "*PR number*\n#${{ github.event.pull_request.number }}"
- },
- {
- "type": "mrkdwn",
- "text": "*Author*\n${{ github.event.pull_request.user.login }}"
- }
- ]
- }
- ]
- }
- ]
- }
- env:
- SLACK_WEBHOOK_URL: ${{ secrets.SLACK_PR_WEBHOOK_URL }}
- SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
diff --git a/.gitmodules b/.gitmodules
index 550533915..76d6e720b 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,3 @@
-[submodule "backend/src/main/resources/festago-config"]
- path = backend/src/main/resources/festago-config
+[submodule "backend/src/main/resources/config"]
+ path = backend/src/main/resources/config
url = https://github.com/festago/festago-config.git
diff --git a/README.md b/README.md
index 4bd7a0b20..856c9024b 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,73 @@
-# 2023-festa-go
+# 페스타고, 대학 축제를 더욱 즐겁게!
+> 대학 축제 줄서기 및 축제 정보 제공 서비스 "페스타고"
+
+
+
+다들 즐거워 하는 대학 축제이지만, 한 가지 걱정되는 점이 있습니다. 바로 ‘줄 서기’입니다. 티켓팅과 예매를 위한 지루한 줄서기 과정은 축제의 재미를 반감시키는 요인입니다.
+
+페스타고는 이러한 문제를 해결하여, 우리 모두가 대학 축제를 더 편리하게 즐기기 위해 만들어졌습니다.
+
+페스타고를 통해 티켓을 예매하기 위해 불편한 줄 서기 과정을 거칠 필요 없이 온라인으로 티켓을 예매하고, 복잡한 절차 없이 스마트폰의 QR 코드만으로 입장을 할 수 있습니다.
+
+
+
+
+
+
+**▷ 📲 다운로드 |** [PlayStore](https://play.google.com/store/apps/details?id=com.festago.festago)
+
+**▷ 📝 팀블로그 |** [Festago 팀블로그](https://festago.github.io/)
+
+**▷ 📧 연락처 |** team.festago@gmail.com
+
+## Android
+
+
프로젝트 아키텍처
+

+
+
+
+
기술 스택
+

+
+
+## Backend
+
+
백엔드 인프라 아키텍처
+

+
+
+
+
+
기술 스택
+

+

+

+

+

+

+

+
+

+

+

+

+

+

+

+
+
+
+## 🎉 축제 스태프를 소개합니다
+
+|BackEnd|BackEnd|BackEnd|BackEnd|Android|Android|Android|
+|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
+||||||||
+|[푸우](https://github.com/BGuga)|[글렌](https://github.com/seokjin8678)|[애쉬](https://github.com/xxeol2)|[오리](https://github.com/carsago)|[베르](https://github.com/SeongHoonC)|[해시](https://github.com/EmilyCh0)|[아크](https://github.com/re4rk)|
+
+
+
+## ⛔️ 공연 관람시 주의사항
+> 페스타고 팀의 그라운드 룰을 소개합니다.
+
+
diff --git a/android/festago/.editorconfig b/android/festago/.editorconfig
new file mode 100644
index 000000000..eb5a61772
--- /dev/null
+++ b/android/festago/.editorconfig
@@ -0,0 +1,7 @@
+
+root = true
+[*.{kt,kts}]
+ktlint_standard_annotation = disabled
+ktlint_standard_function-signature = disabled
+ktlint_standard_string-template-indent = disabled
+ktlint_standard_multiline-expression-wrapping = disabled
diff --git a/android/festago/.idea/.gitignore b/android/festago/.idea/.gitignore
index 26d33521a..8f00030d5 100644
--- a/android/festago/.idea/.gitignore
+++ b/android/festago/.idea/.gitignore
@@ -1,3 +1,5 @@
# Default ignored files
/shelf/
/workspace.xml
+# GitHub Copilot persisted chat sessions
+/copilot/chatSessions
diff --git a/android/festago/.idea/gradle.xml b/android/festago/.idea/gradle.xml
index 72b4213c2..4ddc1b665 100644
--- a/android/festago/.idea/gradle.xml
+++ b/android/festago/.idea/gradle.xml
@@ -4,17 +4,22 @@
diff --git a/android/festago/.idea/misc.xml b/android/festago/.idea/misc.xml
index e541a32a1..3b798c287 100644
--- a/android/festago/.idea/misc.xml
+++ b/android/festago/.idea/misc.xml
@@ -1,6 +1,6 @@
-
+
diff --git a/android/festago/app/build.gradle.kts b/android/festago/app/build.gradle.kts
index c92b8aae2..9745bdb39 100644
--- a/android/festago/app/build.gradle.kts
+++ b/android/festago/app/build.gradle.kts
@@ -1,11 +1,7 @@
-import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties
-
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
- id("kotlin-parcelize")
id("kotlin-kapt")
- kotlin("plugin.serialization") version "1.8.22"
id("com.google.gms.google-services")
id("com.google.firebase.crashlytics")
id("org.jlleitschuh.gradle.ktlint")
@@ -20,14 +16,10 @@ android {
applicationId = "com.festago.festago"
minSdk = 28
targetSdk = 34
- versionCode = 3
- versionName = "1.0.1"
+ versionCode = 13
+ versionName = "2.1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
-
- buildConfigField("String", "BASE_URL", getSecretKey("base_url"))
- buildConfigField("String", "KAKAO_NATIVE_APP_KEY", getSecretKey("kakao_native_app_key"))
- resValue("string", "kakao_redirection_scheme", getSecretKey("kakao_redirection_scheme"))
}
buildFeatures {
@@ -35,9 +27,14 @@ android {
}
buildTypes {
+ debug {
+ buildConfigField("Boolean", "DEBUG_MODE", "true")
+ }
+
release {
isMinifyEnabled = true
isShrinkResources = true
+ buildConfigField("Boolean", "DEBUG_MODE", "false")
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
@@ -69,97 +66,10 @@ kapt {
}
dependencies {
- // domain
- implementation(project(":domain"))
-
- // android
- implementation("androidx.core:core-ktx:1.10.1")
- 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(":data"))
+ implementation(project(":presentation"))
// hilt
implementation("com.google.dagger:hilt-android:2.44")
kapt("com.google.dagger:hilt-android-compiler:2.44")
-
- // recyclerview
- implementation("androidx.recyclerview:recyclerview:1.3.1-rc01")
-
- // lifecycle
- implementation("androidx.lifecycle:lifecycle-extensions:2.2.0")
-
- // glide
- implementation("com.github.bumptech.glide:glide:4.15.1")
-
- // okhttp3
- implementation("com.squareup.okhttp3:okhttp:4.11.0")
-
- // retrofit
- implementation("com.squareup.retrofit2:retrofit:2.9.0")
-
- // kotlin-serialization
- implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")
- implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
-
- // junit4
- testImplementation("junit:junit:4.13.2")
- testImplementation("androidx.test.ext:junit:1.1.5")
- testImplementation("androidx.test:runner:1.5.2")
-
- // assertJ
- testImplementation("org.assertj:assertj-core:3.22.0")
-
- // android-test
- testImplementation("androidx.arch.core:core-testing:2.2.0")
-
- // mock
- testImplementation("io.mockk:mockk-android:1.13.5")
-
- // okhttp3-mockwebserver
- implementation("com.squareup.okhttp3:mockwebserver:4.11.0")
-
- // espresso
- androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
- androidTestImplementation("androidx.test.ext:junit:1.1.5")
-
- // coroutine
- implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
- implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.6.4")
- testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4")
-
- // viewModel
- implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
- implementation("androidx.activity:activity-ktx:1.7.2")
- implementation("androidx.fragment:fragment-ktx:1.6.0")
-
- // zxing
- implementation("com.journeyapps:zxing-android-embedded:4.3.0")
-
- // firebase
- 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-messaging-ktx:23.2.1")
-
- // swiperefreshlayout
- implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
-
- // kakao login
- implementation("com.kakao.sdk:v2-user:2.12.0")
-
- // Encrypted SharedPreference
- implementation("androidx.security:security-crypto-ktx:1.1.0-alpha06")
-
- // turbine
- testImplementation("app.cash.turbine:turbine:1.0.0")
-
- // inApp Update
- implementation("com.google.android.play:app-update-ktx:2.1.0")
-
- // splash
- implementation("androidx.core:core-splashscreen:1.1.0-alpha02")
-}
-
-fun getSecretKey(propertyKey: String): String {
- return gradleLocalProperties(rootDir).getProperty(propertyKey)
}
diff --git a/android/festago/app/proguard-rules.pro b/android/festago/app/proguard-rules.pro
index 2b3412c13..794f592b0 100644
--- a/android/festago/app/proguard-rules.pro
+++ b/android/festago/app/proguard-rules.pro
@@ -22,6 +22,11 @@
# https://developers.kakao.com/docs/latest/en/getting-started/sdk-android#configure-for-shrinking-and-obfuscation-(optional)
-keep class com.kakao.sdk.**.model.* { ; }
+-keep class * extends com.google.gson.TypeAdapter
+-keep interface com.kakao.sdk.**.*Api
+
+#---------------------------------------- Parcelize
+-keep @kotlinx.parcelize.Parcelize public class *
#---------------------------------------- Retrofit
# Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and
diff --git a/android/festago/app/src/androidTest/java/com/festago/festago/presentation/ui/reservationcomplete/ReservationCompleteActivityTest.kt b/android/festago/app/src/androidTest/java/com/festago/festago/presentation/ui/reservationcomplete/ReservationCompleteActivityTest.kt
deleted file mode 100644
index 867e40c6c..000000000
--- a/android/festago/app/src/androidTest/java/com/festago/festago/presentation/ui/reservationcomplete/ReservationCompleteActivityTest.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-package com.festago.festago.presentation.ui.reservationcomplete
-
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.espresso.Espresso.onView
-import androidx.test.espresso.assertion.ViewAssertions.matches
-import androidx.test.espresso.matcher.ViewMatchers.withId
-import androidx.test.espresso.matcher.ViewMatchers.withText
-import androidx.test.ext.junit.rules.ActivityScenarioRule
-import com.festago.festago.R
-import org.junit.Rule
-import org.junit.Test
-import java.time.LocalDateTime
-import java.time.format.DateTimeFormatter
-
-class ReservationCompleteActivityTest {
- private val reservationComplete = ReservedTicketArg(1L, 123, LocalDateTime.now())
-
- private val intent =
- ReservationCompleteActivity.getIntent(
- context = ApplicationProvider.getApplicationContext(),
- reservationComplete = reservationComplete,
- )
-
- @get:Rule
- val activityRule = ActivityScenarioRule(intent)
-
- @Test
- fun 예약에_성공한_메세지를_확인한다() {
- // given
-
- // when & then
- onView(withId(R.id.tvReservationComplete)).check(matches(withText("예매에 성공했습니다!")))
- }
-
- @Test
- fun 티켓_번호_문구가_표시된다() {
- // given
-
- // when & then
- onView(withId(R.id.tvTicketNumberPrompt)).check(matches(withText("나의 티켓 번호")))
- }
-
- @Test
- fun 티켓_번호가_보인다() {
- // given
-
- // when & then
- onView(withId(R.id.tvReservationCompleteNumber)).check(matches(withText(reservationComplete.number.toString())))
- }
-
- @Test
- fun 입장_가능_시간이_보인다() {
- // given
- val entryTime = reservationComplete.entryTime.format(DateTimeFormatter.ofPattern("HH:mm"))
-
- // when & then
- onView(withId(R.id.tvEntryTime)).check(matches(withText("[입장 가능 시간] $entryTime")))
- }
-
- @Test
- fun 축제_공연_날짜가_보인다() {
- // given
- val entryTime =
- reservationComplete.entryTime.format(DateTimeFormatter.ofPattern("yyyy.MM.dd"))
-
- // when & then
- onView(withId(R.id.tvEntryDate)).check(matches(withText(entryTime)))
- }
-}
diff --git a/android/festago/app/src/main/AndroidManifest.xml b/android/festago/app/src/main/AndroidManifest.xml
index 140e91a45..bbe9127fe 100644
--- a/android/festago/app/src/main/AndroidManifest.xml
+++ b/android/festago/app/src/main/AndroidManifest.xml
@@ -10,70 +10,13 @@
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
- android:icon="@mipmap/ic_festago_logo"
+ android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
- android:roundIcon="@mipmap/ic_festago_logo_round"
+ android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
- android:theme="@style/Theme.Festago"
+ android:theme="@style/Base.Theme.Festago"
android:usesCleartextTraffic="true"
- tools:targetApi="31">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ tools:targetApi="34">
diff --git a/android/festago/app/src/main/ic_festago_logo-playstore.png b/android/festago/app/src/main/ic_festago_logo-playstore.png
deleted file mode 100644
index bc1cf755b..000000000
Binary files a/android/festago/app/src/main/ic_festago_logo-playstore.png and /dev/null differ
diff --git a/android/festago/app/src/main/ic_festago_logo_playstore.png b/android/festago/app/src/main/ic_festago_logo_playstore.png
new file mode 100644
index 000000000..ae1c8bc13
Binary files /dev/null and b/android/festago/app/src/main/ic_festago_logo_playstore.png differ
diff --git a/android/festago/app/src/main/java/com/festago/festago/FestagoApplication.kt b/android/festago/app/src/main/java/com/festago/festago/FestagoApplication.kt
index 8f07fa96d..f4010ac32 100644
--- a/android/festago/app/src/main/java/com/festago/festago/FestagoApplication.kt
+++ b/android/festago/app/src/main/java/com/festago/festago/FestagoApplication.kt
@@ -1,11 +1,6 @@
package com.festago.festago
import android.app.Application
-import android.app.NotificationChannel
-import android.app.NotificationManager
-import android.content.Context
-import com.festago.festago.presentation.fcm.FcmMessageType
-import com.kakao.sdk.common.KakaoSdk
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
@@ -13,22 +8,17 @@ class FestagoApplication : Application() {
override fun onCreate() {
super.onCreate()
- initKakaoSdk()
- initNotificationChannel()
+// initNotificationChannel()
}
- private fun initKakaoSdk() {
- KakaoSdk.init(this, BuildConfig.KAKAO_NATIVE_APP_KEY)
- }
-
- private fun initNotificationChannel() {
- val channel = NotificationChannel(
- FcmMessageType.ENTRY_ALERT.channelId,
- getString(R.string.entry_alert_channel_name),
- NotificationManager.IMPORTANCE_DEFAULT
- )
- val notificationManager =
- getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
- notificationManager.createNotificationChannel(channel)
- }
+// private fun initNotificationChannel() {
+// val channel = NotificationChannel(
+// FcmMessageType.ENTRY_ALERT.channelId,
+// getString(R.string.entry_alert_channel_name),
+// NotificationManager.IMPORTANCE_DEFAULT,
+// )
+// val notificationManager =
+// getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+// notificationManager.createNotificationChannel(channel)
+// }
}
diff --git a/android/festago/app/src/main/java/com/festago/festago/analytics/AnalyticsHelper.kt b/android/festago/app/src/main/java/com/festago/festago/analytics/AnalyticsHelper.kt
deleted file mode 100644
index dfdb0275d..000000000
--- a/android/festago/app/src/main/java/com/festago/festago/analytics/AnalyticsHelper.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package com.festago.festago.analytics
-
-interface AnalyticsHelper {
- fun logEvent(event: AnalyticsEvent)
-}
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/di/singletonscope/AnalyticsModule.kt b/android/festago/app/src/main/java/com/festago/festago/data/di/singletonscope/AnalyticsModule.kt
deleted file mode 100644
index 9434899ef..000000000
--- a/android/festago/app/src/main/java/com/festago/festago/data/di/singletonscope/AnalyticsModule.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.festago.festago.data.di.singletonscope
-
-import com.festago.festago.analytics.AnalyticsHelper
-import com.festago.festago.analytics.FirebaseAnalyticsHelper
-import dagger.Binds
-import dagger.Module
-import dagger.hilt.InstallIn
-import dagger.hilt.components.SingletonComponent
-import javax.inject.Singleton
-
-@InstallIn(SingletonComponent::class)
-@Module
-interface AnalyticsModule {
- @Binds
- @Singleton
- fun bindsFirebaseAnalyticsHelper(
- analyticsHelper: FirebaseAnalyticsHelper
- ): AnalyticsHelper
-}
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/di/singletonscope/ApiModule.kt b/android/festago/app/src/main/java/com/festago/festago/data/di/singletonscope/ApiModule.kt
deleted file mode 100644
index 6d6a91e99..000000000
--- a/android/festago/app/src/main/java/com/festago/festago/data/di/singletonscope/ApiModule.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-package com.festago.festago.data.di.singletonscope
-
-import com.festago.festago.BuildConfig
-import com.festago.festago.data.retrofit.AuthInterceptor
-import com.festago.festago.repository.AuthRepository
-import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
-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.Qualifier
-import javax.inject.Singleton
-
-@Qualifier
-@Retention(AnnotationRetention.BINARY)
-annotation class AuthOkHttpClientQualifier
-
-@Qualifier
-@Retention(AnnotationRetention.BINARY)
-annotation class NormalRetrofitQualifier
-
-@Qualifier
-@Retention(AnnotationRetention.BINARY)
-annotation class AuthRetrofitQualifier
-
-@Qualifier
-@Retention(AnnotationRetention.BINARY)
-annotation class BaseUrlQualifier
-
-@InstallIn(SingletonComponent::class)
-@Module
-object ApiModule {
-
- @Provides
- @Singleton
- @AuthOkHttpClientQualifier
- fun provideOkHttpClient(authRepository: AuthRepository): OkHttpClient = OkHttpClient
- .Builder()
- .addInterceptor(AuthInterceptor(authRepository))
- .build()
-
- @Provides
- @Singleton
- fun provideRetrofitConverterFactory(): retrofit2.Converter.Factory {
- val json = Json {
- ignoreUnknownKeys = true
- }
- return json.asConverterFactory("application/json".toMediaType())
- }
-
- @Provides
- @Singleton
- @NormalRetrofitQualifier
- fun providesNormalRetrofit(
- @BaseUrlQualifier baseUrl: String,
- converterFactory: retrofit2.Converter.Factory,
- ): Retrofit = Retrofit.Builder()
- .baseUrl(baseUrl)
- .addConverterFactory(converterFactory)
- .build()
-
- @Provides
- @Singleton
- @AuthRetrofitQualifier
- fun providesAuthRetrofit(
- @BaseUrlQualifier baseUrl: String,
- @AuthOkHttpClientQualifier okHttpClient: OkHttpClient,
- converterFactory: retrofit2.Converter.Factory,
- ): Retrofit = Retrofit.Builder()
- .baseUrl(baseUrl)
- .client(okHttpClient)
- .addConverterFactory(converterFactory)
- .build()
-
- @Provides
- @Singleton
- @BaseUrlQualifier
- fun providesBaseUrl(): String = BuildConfig.BASE_URL
-}
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/repository/FestivalDefaultRepository.kt b/android/festago/app/src/main/java/com/festago/festago/data/repository/FestivalDefaultRepository.kt
deleted file mode 100644
index 12326dd03..000000000
--- a/android/festago/app/src/main/java/com/festago/festago/data/repository/FestivalDefaultRepository.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.festago.festago.data.repository
-
-import com.festago.festago.data.service.FestivalRetrofitService
-import com.festago.festago.data.util.onSuccessOrCatch
-import com.festago.festago.data.util.runCatchingResponse
-import com.festago.festago.model.Festival
-import com.festago.festago.model.Reservation
-import com.festago.festago.repository.FestivalRepository
-import javax.inject.Inject
-
-class FestivalDefaultRepository @Inject constructor(
- private val festivalRetrofitService: FestivalRetrofitService,
-) : FestivalRepository {
- override suspend fun loadFestivals(): Result> =
- runCatchingResponse { festivalRetrofitService.getFestivals() }
- .onSuccessOrCatch { it.toDomain() }
-
- override suspend fun loadFestivalDetail(festivalId: Long): Result =
- runCatchingResponse { festivalRetrofitService.getFestivalDetail(festivalId) }
- .onSuccessOrCatch { it.toDomain() }
-}
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/repository/TicketDefaultRepository.kt b/android/festago/app/src/main/java/com/festago/festago/data/repository/TicketDefaultRepository.kt
deleted file mode 100644
index 33ac652e5..000000000
--- a/android/festago/app/src/main/java/com/festago/festago/data/repository/TicketDefaultRepository.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-package com.festago.festago.data.repository
-
-import com.festago.festago.data.dto.ReservedTicketRequest
-import com.festago.festago.data.service.TicketRetrofitService
-import com.festago.festago.data.util.onSuccessOrCatch
-import com.festago.festago.data.util.runCatchingResponse
-import com.festago.festago.model.ReservedTicket
-import com.festago.festago.model.Ticket
-import com.festago.festago.model.TicketCode
-import com.festago.festago.repository.TicketRepository
-import javax.inject.Inject
-
-class TicketDefaultRepository @Inject constructor(
- private val ticketRetrofitService: TicketRetrofitService,
-) : TicketRepository {
-
- override suspend fun loadTicket(ticketId: Long): Result =
- runCatchingResponse { ticketRetrofitService.getTicket(ticketId) }
- .onSuccessOrCatch { it.toDomain() }
-
- override suspend fun loadCurrentTickets(): Result> =
- runCatchingResponse { ticketRetrofitService.getCurrentTickets() }
- .onSuccessOrCatch { it.toDomain() }
-
- override suspend fun loadTicketCode(ticketId: Long): Result =
- runCatchingResponse { ticketRetrofitService.getTicketCode(ticketId) }
- .onSuccessOrCatch { it.toDomain() }
-
- override suspend fun loadHistoryTickets(size: Int): Result> =
- runCatchingResponse { ticketRetrofitService.getHistoryTickets(size) }
- .onSuccessOrCatch { it.toDomain() }
-
- override suspend fun reserveTicket(ticketId: Int): Result =
- runCatchingResponse {
- ticketRetrofitService.postReserveTicket(
- ReservedTicketRequest(ticketId),
- )
- }.onSuccessOrCatch { it.toDomain() }
-}
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/service/FestivalRetrofitService.kt b/android/festago/app/src/main/java/com/festago/festago/data/service/FestivalRetrofitService.kt
deleted file mode 100644
index 0626fb95c..000000000
--- a/android/festago/app/src/main/java/com/festago/festago/data/service/FestivalRetrofitService.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.festago.festago.data.service
-
-import com.festago.festago.data.dto.FestivalsResponse
-import com.festago.festago.data.dto.ReservationFestivalResponse
-import retrofit2.Response
-import retrofit2.http.GET
-import retrofit2.http.Path
-
-interface FestivalRetrofitService {
- @GET("/festivals")
- suspend fun getFestivals(): Response
-
- @GET("/festivals/{festivalId}")
- suspend fun getFestivalDetail(
- @Path("festivalId") festivalId: Long,
- ): Response
-}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/HomeActivity.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/HomeActivity.kt
deleted file mode 100644
index 9b7fe0a8a..000000000
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/HomeActivity.kt
+++ /dev/null
@@ -1,157 +0,0 @@
-package com.festago.festago.presentation.ui.home
-
-import android.content.Context
-import android.content.Intent
-import android.os.Bundle
-import android.widget.Toast
-import androidx.activity.result.ActivityResultLauncher
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.activity.viewModels
-import androidx.appcompat.app.AppCompatActivity
-import androidx.fragment.app.Fragment
-import com.festago.festago.R
-import com.festago.festago.databinding.ActivityHomeBinding
-import com.festago.festago.presentation.ui.home.festivallist.FestivalListFragment
-import com.festago.festago.presentation.ui.home.mypage.MyPageFragment
-import com.festago.festago.presentation.ui.home.ticketlist.TicketListFragment
-import com.festago.festago.presentation.ui.signin.SignInActivity
-import com.festago.festago.presentation.util.repeatOnStarted
-import com.festago.festago.presentation.util.requestNotificationPermission
-import com.google.android.material.navigation.NavigationBarView
-import dagger.hilt.android.AndroidEntryPoint
-
-@AndroidEntryPoint
-class HomeActivity : AppCompatActivity() {
-
- private val binding by lazy { ActivityHomeBinding.inflate(layoutInflater) }
-
- private val vm: HomeViewModel by viewModels()
-
- private lateinit var resultLauncher: ActivityResultLauncher
-
- private val navigationBarView by lazy { binding.nvHome as NavigationBarView }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- initBinding()
- initView()
- initObserve()
- initResultLauncher()
- }
-
- private fun initResultLauncher() {
- resultLauncher =
- registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
- if (result.resultCode == SignInActivity.RESULT_NOT_SIGN_IN) {
- navigationBarView.selectedItemId = R.id.item_festival
- }
- }
- initNotificationPermission()
- }
-
- private fun initBinding() {
- setContentView(binding.root)
- }
-
- private fun initView() {
- navigationBarView.setOnItemSelectedListener {
- vm.selectItem(getItemType(it.itemId))
- true
- }
-
- binding.fabTicket.setOnClickListener {
- navigationBarView.selectedItemId = R.id.item_ticket
- }
-
- changeFragment()
- }
-
- private fun initObserve() {
- repeatOnStarted(this) {
- vm.event.collect { event ->
- when (event) {
- is HomeEvent.ShowSignIn -> showSignIn()
- }
- }
- }
-
- repeatOnStarted(this) {
- vm.selectedItem.collect { homeItemType ->
- when (homeItemType) {
- HomeItemType.FESTIVAL_LIST -> showFestivalList()
- HomeItemType.TICKET_LIST -> showTicketList()
- HomeItemType.MY_PAGE -> showMyPage()
- }
- }
- }
- }
-
- private fun initNotificationPermission() {
- val requestPermissionLauncher = registerForActivityResult(
- ActivityResultContracts.RequestPermission(),
- ) { isGranted: Boolean ->
- if (!isGranted) {
- Toast.makeText(
- this,
- getString(R.string.home_notification_permission_denied),
- Toast.LENGTH_SHORT,
- ).show()
- }
- }
- requestNotificationPermission(requestPermissionLauncher)
- }
-
- private fun getItemType(menuItemId: Int): HomeItemType {
- return when (menuItemId) {
- R.id.item_festival -> HomeItemType.FESTIVAL_LIST
- R.id.item_mypage -> HomeItemType.MY_PAGE
- R.id.item_ticket -> HomeItemType.TICKET_LIST
- else -> throw IllegalArgumentException("menu item id not found")
- }
- }
-
- private fun showFestivalList() {
- changeFragment()
- binding.fabTicket.isSelected = false
- }
-
- private fun showTicketList() {
- changeFragment()
- binding.fabTicket.isSelected = true
- }
-
- private fun showMyPage() {
- changeFragment()
- binding.fabTicket.isSelected = false
- }
-
- private fun showSignIn() {
- resultLauncher.launch(SignInActivity.getIntent(this))
- }
-
- private inline fun changeFragment() {
- val tag = T::class.java.name
- val fragmentTransaction = supportFragmentManager.beginTransaction()
-
- supportFragmentManager.fragments.forEach { fragment ->
- fragmentTransaction.hide(fragment)
- }
-
- var targetFragment = supportFragmentManager.findFragmentByTag(tag)
-
- if (targetFragment == null) {
- targetFragment = T::class.java.newInstance()
- fragmentTransaction.add(R.id.fcv_home_container, targetFragment, tag)
- } else {
- fragmentTransaction.show(targetFragment)
- }
-
- fragmentTransaction.commit()
- }
-
- companion object {
- fun getIntent(context: Context): Intent {
- return Intent(context, HomeActivity::class.java)
- }
- }
-}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListFragment.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListFragment.kt
deleted file mode 100644
index 04e701e44..000000000
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListFragment.kt
+++ /dev/null
@@ -1,114 +0,0 @@
-package com.festago.festago.presentation.ui.home.festivallist
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.viewModels
-import androidx.recyclerview.widget.GridLayoutManager
-import com.festago.festago.R
-import com.festago.festago.databinding.FragmentFestivalListBinding
-import com.festago.festago.presentation.ui.home.ticketlist.TicketListFragment
-import com.festago.festago.presentation.ui.ticketreserve.TicketReserveActivity
-import com.festago.festago.presentation.util.repeatOnStarted
-import dagger.hilt.android.AndroidEntryPoint
-
-@AndroidEntryPoint
-class FestivalListFragment : Fragment(R.layout.fragment_festival_list) {
-
- private var _binding: FragmentFestivalListBinding? = null
- private val binding get() = _binding!!
-
- private val vm: FestivalListViewModel by viewModels()
-
- private lateinit var adapter: FestivalListAdapter
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?,
- ): View {
- _binding = FragmentFestivalListBinding.inflate(inflater)
- return binding.root
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- initObserve()
- initView()
- }
-
- private fun initObserve() {
- repeatOnStarted(viewLifecycleOwner) {
- vm.uiState.collect {
- binding.uiState = it
- updateUi(it)
- }
- }
- repeatOnStarted(viewLifecycleOwner) {
- vm.event.collect {
- handleEvent(it)
- }
- }
- }
-
- private val Int.dp: Int get() = (this / resources.displayMetrics.density).toInt()
-
- private fun initView() {
- adapter = FestivalListAdapter()
- binding.rvFestivalList.adapter = adapter
-
- binding.rvFestivalList.layoutManager.apply {
- if (this is GridLayoutManager) {
- val spanSize = (resources.displayMetrics.widthPixels.dp / 160)
- spanCount = when {
- spanSize < 2 -> 2
- spanSize > 4 -> 4
- else -> spanSize
- }
- }
- }
-
- vm.loadFestivals()
-
- binding.srlFestivalList.setOnRefreshListener {
- vm.loadFestivals()
- binding.srlFestivalList.isRefreshing = false
- }
- }
-
- private fun updateUi(uiState: FestivalListUiState) {
- when (uiState) {
- is FestivalListUiState.Loading,
- is FestivalListUiState.Error,
- -> Unit
-
- is FestivalListUiState.Success -> handleSuccess(uiState)
- }
- }
-
- private fun handleSuccess(uiState: FestivalListUiState.Success) {
- adapter.submitList(uiState.festivals)
- }
-
- private fun handleEvent(event: FestivalListEvent) {
- when (event) {
- is FestivalListEvent.ShowTicketReserve -> {
- removeTicketListFragment()
- startActivity(TicketReserveActivity.getIntent(requireContext(), event.festivalId))
- }
- }
- }
-
- private fun removeTicketListFragment() {
- parentFragmentManager.findFragmentByTag(TicketListFragment::class.java.name)?.let {
- parentFragmentManager.beginTransaction().remove(it).commit()
- }
- }
-
- override fun onDestroyView() {
- _binding = null
- super.onDestroyView()
- }
-}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListUiState.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListUiState.kt
deleted file mode 100644
index eda06b3a9..000000000
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListUiState.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.festago.festago.presentation.ui.home.festivallist
-
-sealed interface FestivalListUiState {
- object Loading : FestivalListUiState
-
- data class Success(
- val festivals: List
- ) : FestivalListUiState {
- val hasFestival get() = festivals.isNotEmpty()
- }
-
- object Error : FestivalListUiState
-
- val shouldShowSuccess get() = this is Success && hasFestival
- val shouldShowSuccessAndEmpty get() = this is Success && !hasFestival
- val shouldShowLoading get() = this is Loading
- val shouldShowError get() = this is Error
-}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListViewModel.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListViewModel.kt
deleted file mode 100644
index 78d86dc88..000000000
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListViewModel.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-package com.festago.festago.presentation.ui.home.festivallist
-
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import com.festago.festago.analytics.AnalyticsHelper
-import com.festago.festago.analytics.logNetworkFailure
-import com.festago.festago.presentation.ui.home.festivallist.FestivalListEvent.ShowTicketReserve
-import com.festago.festago.repository.FestivalRepository
-import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharedFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asSharedFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.launch
-import javax.inject.Inject
-
-@HiltViewModel
-class FestivalListViewModel @Inject constructor(
- private val festivalRepository: FestivalRepository,
- private val analyticsHelper: AnalyticsHelper,
-) : ViewModel() {
-
- private val _uiState = MutableStateFlow(FestivalListUiState.Loading)
- val uiState: StateFlow = _uiState.asStateFlow()
-
- private val _event = MutableSharedFlow()
- val event: SharedFlow = _event.asSharedFlow()
-
- fun loadFestivals() {
- viewModelScope.launch {
- festivalRepository.loadFestivals()
- .onSuccess {
- _uiState.value = FestivalListUiState.Success(
- festivals = it.map { festival ->
- FestivalItemUiState(
- id = festival.id,
- name = festival.name,
- startDate = festival.startDate,
- endDate = festival.endDate,
- thumbnail = festival.thumbnail,
- onFestivalDetail = ::showTicketReserve,
- )
- },
- )
- }.onFailure {
- _uiState.value = FestivalListUiState.Error
- analyticsHelper.logNetworkFailure(KEY_LOAD_FESTIVALS_LOG, it.message.toString())
- }
- }
- }
-
- fun showTicketReserve(festivalId: Long) {
- viewModelScope.launch {
- _event.emit(ShowTicketReserve(festivalId))
- }
- }
-
- companion object {
- private const val KEY_LOAD_FESTIVALS_LOG = "load_festivals"
- }
-}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/mypage/MyPageFragment.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/mypage/MyPageFragment.kt
deleted file mode 100644
index 8859752bc..000000000
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/mypage/MyPageFragment.kt
+++ /dev/null
@@ -1,134 +0,0 @@
-package com.festago.festago.presentation.ui.home.mypage
-
-import android.app.AlertDialog
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.viewModels
-import com.festago.festago.R
-import com.festago.festago.databinding.FragmentMyPageBinding
-import com.festago.festago.presentation.ui.home.HomeActivity
-import com.festago.festago.presentation.ui.selectschool.SelectSchoolActivity
-import com.festago.festago.presentation.ui.signin.SignInActivity
-import com.festago.festago.presentation.ui.tickethistory.TicketHistoryActivity
-import com.festago.festago.presentation.util.repeatOnStarted
-import dagger.hilt.android.AndroidEntryPoint
-
-@AndroidEntryPoint
-class MyPageFragment : Fragment(R.layout.fragment_my_page) {
-
- private var _binding: FragmentMyPageBinding? = null
- private val binding get() = _binding!!
-
- private val vm: MyPageViewModel by viewModels()
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?,
- ): View {
- _binding = FragmentMyPageBinding.inflate(inflater)
- return binding.root
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- initObserve()
- initView()
- }
-
- private fun initObserve() {
- repeatOnStarted(viewLifecycleOwner) {
- vm.uiState.collect { uiState ->
- handleUiState(uiState)
- }
- }
- repeatOnStarted(viewLifecycleOwner) {
- vm.event.collect { event ->
- handleEvent(event)
- }
- }
- }
-
- private fun handleUiState(uiState: MyPageUiState) {
- binding.uiState = uiState
- when (uiState) {
- is MyPageUiState.Loading, is MyPageUiState.Error -> Unit
-
- is MyPageUiState.Success -> handleSuccess(uiState)
- }
- }
-
- private fun handleEvent(event: MyPageEvent) {
- when (event) {
- is MyPageEvent.ShowSignIn -> handleShowSignInEvent()
- is MyPageEvent.SignOutSuccess -> handleSignOutSuccessEvent()
- is MyPageEvent.DeleteAccountSuccess -> handleDeleteAccountSuccess()
- is MyPageEvent.ShowTicketHistory -> handleShowTicketHistory()
- is MyPageEvent.ShowConfirmDelete -> handleShowConfirmDelete()
- }
- }
-
- private fun handleShowSignInEvent() {
- startActivity(SignInActivity.getIntent(requireContext()))
- }
-
- private fun handleSignOutSuccessEvent() {
- restartHome()
- }
-
- private fun handleDeleteAccountSuccess() {
- restartHome()
- }
-
- private fun restartHome() {
- requireActivity().finishAffinity()
- startActivity(HomeActivity.getIntent(requireContext()))
- }
-
- private fun handleShowTicketHistory() {
- startActivity(TicketHistoryActivity.getIntent(requireContext()))
- }
-
- private fun handleShowConfirmDelete() {
- val dialog = AlertDialog.Builder(requireContext()).apply {
- setTitle(getString(R.string.confirm_delete_dialog_title))
- setMessage(getString(R.string.confirm_delete_dialog_message))
- setPositiveButton(getString(R.string.confirm_delete_dialog_yes)) { dialog, _ ->
- vm.deleteAccount()
- dialog.dismiss()
- }
- setNegativeButton(getString(R.string.confirm_delete_dialog_no)) { dialog, _ ->
- dialog.dismiss()
- }
- }
- dialog.show()
- }
-
- private fun initView() {
- binding.vm = vm
-
- vm.loadUserInfo()
-
- binding.srlMyPage.setOnRefreshListener {
- vm.loadUserInfo()
- binding.srlMyPage.isRefreshing = false
- }
-
- binding.tvSchoolAuthorization.setOnClickListener {
- startActivity(SelectSchoolActivity.getIntent(requireContext()))
- }
- }
-
- private fun handleSuccess(uiState: MyPageUiState.Success) {
- binding.successState = uiState
- }
-
- override fun onDestroyView() {
- _binding = null
- super.onDestroyView()
- }
-}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/mypage/MyPageViewModel.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/mypage/MyPageViewModel.kt
deleted file mode 100644
index d1d91d66f..000000000
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/mypage/MyPageViewModel.kt
+++ /dev/null
@@ -1,109 +0,0 @@
-package com.festago.festago.presentation.ui.home.mypage
-
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import com.festago.festago.analytics.AnalyticsHelper
-import com.festago.festago.analytics.logNetworkFailure
-import com.festago.festago.repository.AuthRepository
-import com.festago.festago.repository.TicketRepository
-import com.festago.festago.repository.UserRepository
-import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.coroutines.async
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharedFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asSharedFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.launch
-import javax.inject.Inject
-
-@HiltViewModel
-class MyPageViewModel @Inject constructor(
- private val userRepository: UserRepository,
- private val ticketRepository: TicketRepository,
- private val authRepository: AuthRepository,
- private val analyticsHelper: AnalyticsHelper,
-) : ViewModel() {
-
- private val _uiState = MutableStateFlow(MyPageUiState.Loading)
- val uiState: StateFlow = _uiState.asStateFlow()
-
- private val _event = MutableSharedFlow()
- val event: SharedFlow = _event.asSharedFlow()
-
- fun loadUserInfo() {
- if (!authRepository.isSigned) {
- viewModelScope.launch {
- _event.emit(MyPageEvent.ShowSignIn)
- _uiState.value = MyPageUiState.Error
- }
- return
- }
- viewModelScope.launch {
- val deferredUserProfile = async { userRepository.loadUserProfile() }
- val deferredHistoryTicket = async { ticketRepository.loadHistoryTickets(size = 1) }
-
- runCatching {
- _uiState.value = MyPageUiState.Success(
- userProfile = deferredUserProfile.await().getOrThrow(),
- ticket = deferredHistoryTicket.await().getOrThrow().firstOrNull(),
- )
- }.onFailure {
- _uiState.value = MyPageUiState.Error
- analyticsHelper.logNetworkFailure(
- key = KEY_LOAD_USER_INFO,
- value = it.message.toString(),
- )
- }
- }
- }
-
- fun signOut() {
- viewModelScope.launch {
- authRepository.signOut()
- .onSuccess {
- _event.emit(MyPageEvent.SignOutSuccess)
- _uiState.value = MyPageUiState.Error
- }.onFailure {
- analyticsHelper.logNetworkFailure(
- key = KEY_SIGN_OUT,
- value = it.message.toString(),
- )
- }
- }
- }
-
- fun showConfirmDelete() {
- viewModelScope.launch {
- _event.emit(MyPageEvent.ShowConfirmDelete)
- }
- }
-
- fun deleteAccount() {
- viewModelScope.launch {
- authRepository.deleteAccount()
- .onSuccess {
- _event.emit(MyPageEvent.DeleteAccountSuccess)
- _uiState.value = MyPageUiState.Error
- }.onFailure {
- analyticsHelper.logNetworkFailure(
- key = KEY_DELETE_ACCOUNT,
- value = it.message.toString(),
- )
- }
- }
- }
-
- fun showTicketHistory() {
- viewModelScope.launch {
- _event.emit(MyPageEvent.ShowTicketHistory)
- }
- }
-
- companion object {
- private const val KEY_LOAD_USER_INFO = "loadUserInfo"
- private const val KEY_SIGN_OUT = "KEY_SIGN_OUT"
- private const val KEY_DELETE_ACCOUNT = "KEY_DELETE_ACCOUNT"
- }
-}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListFragment.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListFragment.kt
deleted file mode 100644
index b3fa56d5a..000000000
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListFragment.kt
+++ /dev/null
@@ -1,116 +0,0 @@
-package com.festago.festago.presentation.ui.home.ticketlist
-
-import android.content.Intent
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.activity.result.ActivityResultLauncher
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.viewModels
-import com.festago.festago.R
-import com.festago.festago.databinding.FragmentTicketListBinding
-import com.festago.festago.presentation.ui.ticketentry.TicketEntryActivity
-import com.festago.festago.presentation.util.repeatOnStarted
-import dagger.hilt.android.AndroidEntryPoint
-
-@AndroidEntryPoint
-class TicketListFragment : Fragment(R.layout.fragment_ticket_list) {
-
- private var _binding: FragmentTicketListBinding? = null
- private val binding get() = _binding!!
-
- private lateinit var adapter: TicketListAdapter
-
- private lateinit var resultLauncher: ActivityResultLauncher
-
- private val vm: TicketListViewModel by viewModels()
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?,
- ): View {
- _binding = FragmentTicketListBinding.inflate(inflater)
- binding.lifecycleOwner = viewLifecycleOwner
- return binding.root
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- initObserve()
- initView()
- initActivityResult()
- }
-
- private fun initObserve() {
- repeatOnStarted(viewLifecycleOwner) {
- vm.uiState.collect {
- binding.uiState = it
- updateUi(it)
- }
- }
- repeatOnStarted(viewLifecycleOwner) {
- vm.event.collect { event ->
- handleEvent(event)
- }
- }
- }
-
- private fun updateUi(uiState: TicketListUiState) {
- when (uiState) {
- is TicketListUiState.Loading,
- is TicketListUiState.Error,
- -> Unit
-
- is TicketListUiState.Success -> {
- adapter.submitList(uiState.tickets)
- }
- }
- }
-
- private fun handleEvent(event: TicketListEvent) {
- when (event) {
- is TicketListEvent.ShowTicketEntry -> showTicketEntry(event)
- }
- }
-
- private fun showTicketEntry(event: TicketListEvent.ShowTicketEntry) {
- resultLauncher.launch(
- TicketEntryActivity.getIntent(
- context = requireContext(),
- ticketId = event.ticketId,
- ),
- )
- }
-
- private fun initActivityResult() {
- resultLauncher =
- registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
- if (result.resultCode == TicketEntryActivity.RESULT_OK) {
- requireActivity().supportFragmentManager.beginTransaction()
- .replace(R.id.fcv_home_container, TicketListFragment()).commit()
- }
- }
- }
-
- private fun initView() {
- adapter = TicketListAdapter()
- binding.rvTicketList.adapter = adapter
- vm.loadCurrentTickets()
- initRefresh()
- }
-
- private fun initRefresh() {
- binding.srlTicketList.setOnRefreshListener {
- vm.loadCurrentTickets()
- binding.srlTicketList.isRefreshing = false
- }
- }
-
- override fun onDestroyView() {
- _binding = null
- super.onDestroyView()
- }
-}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/signin/SignInActivity.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/signin/SignInActivity.kt
deleted file mode 100644
index dcfa93055..000000000
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/signin/SignInActivity.kt
+++ /dev/null
@@ -1,111 +0,0 @@
-package com.festago.festago.presentation.ui.signin
-
-import android.content.Context
-import android.content.Intent
-import android.os.Bundle
-import android.text.Spannable
-import android.text.SpannableStringBuilder
-import android.text.style.ForegroundColorSpan
-import androidx.activity.OnBackPressedCallback
-import androidx.activity.viewModels
-import androidx.appcompat.app.AppCompatActivity
-import com.festago.festago.R
-import com.festago.festago.databinding.ActivitySignInBinding
-import com.festago.festago.presentation.ui.customview.OkDialogFragment
-import com.festago.festago.presentation.ui.home.HomeActivity
-import com.festago.festago.presentation.util.repeatOnStarted
-import dagger.hilt.android.AndroidEntryPoint
-
-@AndroidEntryPoint
-class SignInActivity : AppCompatActivity() {
-
- private lateinit var binding: ActivitySignInBinding
-
- private val vm: SignInViewModel by viewModels()
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- initBinding()
- initView()
- initObserve()
- }
-
- private fun initBinding() {
- binding = ActivitySignInBinding.inflate(layoutInflater)
- setContentView(binding.root)
- }
-
- private fun initView() {
- binding.lifecycleOwner = this
- binding.vm = vm
- initComment()
- initBackPressed()
- }
-
- private fun initBackPressed() {
- val callback = object : OnBackPressedCallback(true) {
- override fun handleOnBackPressed() {
- setResult(RESULT_NOT_SIGN_IN, intent)
- finish()
- }
- }
- this.onBackPressedDispatcher.addCallback(this, callback)
- }
-
- private fun initObserve() {
- repeatOnStarted(this) {
- vm.event.collect { event ->
- when (event) {
- is SignInEvent.SignInSuccess -> handleSuccessEvent()
- is SignInEvent.SignInFailure -> handleFailureEvent()
- }
- }
- }
- }
-
- private fun initComment() {
- val spannableStringBuilder = SpannableStringBuilder(
- getString(R.string.mypage_tv_signin_description),
- ).apply {
- setSpan(
- ForegroundColorSpan(getColor(R.color.seed)),
- COLOR_SPAN_START_INDEX,
- COLOR_SPAN_END_INDEX,
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
- )
- }
- binding.tvLoginDescription.text = spannableStringBuilder
- }
-
- private fun handleSuccessEvent() {
- showHomeWithFinish()
- }
-
- private fun handleFailureEvent() {
- val dialog = OkDialogFragment.newInstance(FAILURE_SIGN_IN).apply {
- listener = OkDialogFragment.OnClickListener {
- showHomeWithFinish()
- }
- }
- dialog.show(supportFragmentManager, OkDialogFragment::class.java.name)
- }
-
- private fun showHomeWithFinish() {
- val intent = HomeActivity.getIntent(this).apply {
- addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
- }
- finishAffinity()
- startActivity(intent)
- }
-
- companion object {
- private const val COLOR_SPAN_START_INDEX = 0
- private const val COLOR_SPAN_END_INDEX = 4
-
- private const val FAILURE_SIGN_IN = "로그인에 실패했습니다."
- const val RESULT_NOT_SIGN_IN = 1
- fun getIntent(context: Context): Intent {
- return Intent(context, SignInActivity::class.java)
- }
- }
-}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/signin/SignInViewModel.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/signin/SignInViewModel.kt
deleted file mode 100644
index f9054058e..000000000
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/signin/SignInViewModel.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.festago.festago.presentation.ui.signin
-
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import com.festago.festago.analytics.AnalyticsHelper
-import com.festago.festago.analytics.logNetworkFailure
-import com.festago.festago.repository.AuthRepository
-import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.SharedFlow
-import kotlinx.coroutines.launch
-import javax.inject.Inject
-
-@HiltViewModel
-class SignInViewModel @Inject constructor(
- private val authRepository: AuthRepository,
- private val analyticsHelper: AnalyticsHelper,
-) : ViewModel() {
-
- private val _event = MutableSharedFlow()
- val event: SharedFlow = _event
-
- fun signIn() {
- viewModelScope.launch {
- authRepository.signIn().onSuccess {
- _event.emit(SignInEvent.SignInSuccess)
- }.onFailure {
- _event.emit(SignInEvent.SignInFailure)
- analyticsHelper.logNetworkFailure(KEY_SIGN_IN_LOG, it.message.toString())
- }
- }
- }
-
- companion object {
- private const val KEY_SIGN_IN_LOG = "KEY_SIGN_IN_LOG"
- }
-}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/splash/SplashActivity.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/splash/SplashActivity.kt
deleted file mode 100644
index 71606d264..000000000
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/splash/SplashActivity.kt
+++ /dev/null
@@ -1,92 +0,0 @@
-package com.festago.festago.presentation.ui.splash
-
-import android.annotation.SuppressLint
-import android.content.Intent
-import android.net.Uri
-import android.os.Bundle
-import android.widget.Toast
-import androidx.activity.ComponentActivity
-import androidx.appcompat.app.AlertDialog
-import androidx.core.splashscreen.SplashScreen
-import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
-import com.festago.festago.R
-import com.festago.festago.databinding.ActivitySplashBinding
-import com.festago.festago.presentation.ui.home.HomeActivity
-import com.google.android.play.core.appupdate.AppUpdateInfo
-import com.google.android.play.core.appupdate.AppUpdateManagerFactory
-import com.google.android.play.core.install.model.UpdateAvailability
-import dagger.hilt.android.AndroidEntryPoint
-
-@SuppressLint("CustomSplashScreen")
-@AndroidEntryPoint
-class SplashActivity : ComponentActivity() {
-
- val binding by lazy {
- ActivitySplashBinding.inflate(layoutInflater)
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- val splashScreen = installSplashScreen()
- splashScreen.setKeepOnScreenCondition { true }
- checkAppUpdate(splashScreen)
- setContentView(binding.root)
- }
-
- private fun checkAppUpdate(splashScreen: SplashScreen) {
- val appUpdateManager = AppUpdateManagerFactory.create(this)
- appUpdateManager.appUpdateInfo
- .addOnSuccessListener { appUpdateInfo ->
- handleOnSuccess(appUpdateInfo, splashScreen)
- }.addOnFailureListener {
- showHome()
- }
- }
-
- private fun handleOnSuccess(appUpdateInfo: AppUpdateInfo, splashScreen: SplashScreen) {
- if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) {
- splashScreen.setKeepOnScreenCondition { false }
- requestUpdate()
- } else {
- showHome()
- }
- }
-
- private fun showHome() {
- startActivity(HomeActivity.getIntent(this))
- finish()
- }
-
- private fun requestUpdate() {
- AlertDialog.Builder(this).apply {
- setTitle(getString(R.string.splash_app_update_request_dialog_title))
- setMessage(getString(R.string.splash_app_update_request_dialog_message))
- setNegativeButton(R.string.ok_dialog_btn_cancel) { _, _ ->
- handleCancelUpdate()
- }
- setPositiveButton(R.string.ok_dialog_btn_ok) { _, _ ->
- handleOkUpdate()
- }
- setCancelable(false)
- }.show()
- }
-
- private fun handleCancelUpdate() {
- Toast.makeText(
- this@SplashActivity,
- getString(R.string.splash_app_update_denied),
- Toast.LENGTH_SHORT,
- ).show()
- finish()
- }
-
- private fun handleOkUpdate() {
- navigateToAppStore()
- finish()
- }
-
- private fun navigateToAppStore() {
- startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=$packageName")))
- finish()
- }
-}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/util/Event.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/util/Event.kt
deleted file mode 100644
index e9298dfbb..000000000
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/util/Event.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.festago.festago.presentation.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/festago/app/src/main/java/com/festago/festago/presentation/util/MutableSingleLiveData.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/util/MutableSingleLiveData.kt
deleted file mode 100644
index 68236a79c..000000000
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/util/MutableSingleLiveData.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.festago.festago.presentation.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/festago/app/src/main/java/com/festago/festago/presentation/util/ParcelizeUtil.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/util/ParcelizeUtil.kt
deleted file mode 100644
index f32cd1e3e..000000000
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/util/ParcelizeUtil.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.festago.festago.presentation.util
-
-import android.content.Intent
-import android.os.Build
-import android.os.Bundle
-import android.os.Parcelable
-
-@Suppress("DEPRECATION")
-inline fun Bundle.getParcelableCompat(key: String): T? {
- return if (Build.VERSION.SDK_INT >= 33) {
- getParcelable(key, T::class.java)
- } else {
- getParcelable(key)
- }
-}
-
-@Suppress("DEPRECATION")
-inline fun Bundle.getParcelableArrayListCompat(key: String): ArrayList? {
- return if (Build.VERSION.SDK_INT >= 33) {
- getParcelableArrayList(key, T::class.java)
- } else {
- getParcelableArrayList(key)
- }
-}
-
-@Suppress("DEPRECATION")
-inline fun Intent.getParcelableExtraCompat(key: String): T? {
- return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- getParcelableExtra(key, T::class.java)
- } else {
- getParcelableExtra(key) as? T
- }
-}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/util/SingleLiveData.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/util/SingleLiveData.kt
deleted file mode 100644
index 434744392..000000000
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/util/SingleLiveData.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.festago.festago.presentation.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/festago/app/src/main/res/layout/fragment_festival_list.xml b/android/festago/app/src/main/res/layout/fragment_festival_list.xml
deleted file mode 100644
index b4b4a20f3..000000000
--- a/android/festago/app/src/main/res/layout/fragment_festival_list.xml
+++ /dev/null
@@ -1,83 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/android/festago/app/src/main/res/layout/fragment_ticket_reserve_bottom_sheet.xml b/android/festago/app/src/main/res/layout/fragment_ticket_reserve_bottom_sheet.xml
deleted file mode 100644
index f34d68337..000000000
--- a/android/festago/app/src/main/res/layout/fragment_ticket_reserve_bottom_sheet.xml
+++ /dev/null
@@ -1,83 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/android/festago/app/src/main/res/values/strings.xml b/android/festago/app/src/main/res/values/strings.xml
deleted file mode 100644
index 35daf2b7f..000000000
--- a/android/festago/app/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,143 +0,0 @@
-
-
- 페스타고
-
-
- yyyy.MM.dd
-
-
- 입장전
- 입장완료
- 외출중
-
-
- 재학생용
- 방문객용
- 기타
-
-
- 업데이트 알림
- 새로운 페스타고를 사용하기 위해 업데이트 해주세요.
- 업데이트 후 정상 사용가능합니다.
- 페스타고 실행 중 문제가 발생했습니다. 페스타고로 문의해주세요.
-
-
- HH:mm 티켓 활성화
- 티켓 제시
- 입장 전
- 입장 완료
- 외출중
-
-
- 입장 전
- 입장 완료
- 외출중
- 티켓 조회 과정에 문제가 발생했습니다.
-
-
- 축제 목록
- 티켓 목록
- 마이페이지
- 알림 권한을 거부했습니다 :(
-
-
- %1s ~ %1s
- yyyy.MM.dd
- MM.dd (E) HH:mm
- [라인업]
- [예매 가능 티켓]
- %1$s(%2$s/%3$s)
- ", "
- ⚠️ 재학생용 티켓예매를 위해 사전에 학교 인증이 필요합니다.
- 축제 조회에 실패했습니다.
- 티켓 예매
- (%1$s/%2$s)
- 예매 하기
- MM월 dd일 HH:mm 오픈예정
- 로그인 후 예매
-
-
- yyyy.MM.dd
- 진행중
- 지난축제
- 축제 조회에 실패했습니다.
- 조회된 축제가 없습니다.
- %s - %s
-
-
- 티켓 목록
- yyyy.MM.dd HH:mm
- HH:mm 티켓 활성화
- 입장하기
- %d번
- 무대 시작 시간 %s
- 입장 시작 시간 %s
- 사용할 수 있는 티켓이 없습니다
- 티켓 목록 조회에 실패했습니다.
-
-
- 확인
- 취소
-
-
- 카카오로 로그인
- 페스타고로\n대학교 축제를 즐겨보세요.
-
-
- 예매에 성공했습니다!
- 나의 티켓 번호
- HH:mm
- yyyy.MM.dd
- [입장 가능 시간] %s
-
-
- 계정
- 학교 인증
- 로그아웃
- 탈퇴하기
- 예매 목록
- [무대 시작 시간]
- [입장 시작 시간]
- [입장 번호]
- [예매 일자]
- 더보기 >
- 예매 내역이 존재하지 않습니다.
- yyyy.MM.dd. HH:mm
- 마이페이지 정보 받아오기에 실패했습니다.
-
-
- 예매 목록
- [무대 시작 시간]
- [입장 시작 시간]
- [입장 번호]
- %d번
- [예매 일자]
- 티켓 조회에 실패했습니다.
- 조회된 티켓이 없습니다.
-
-
- 정말 탈퇴하시겠어요?
- 탈퇴 버튼 선택 시, 계정은 삭제되며 복구되지 않습니다.
- 탈퇴
- 취소
-
-
-
- 학교 이메일
- 인증 코드
- 인증 번호 받기
- 인증 번호 확인
- \@%s
- mm:ss
- 학교 정보 받아오기에 실패했습니다.
-
-
- 다음
- 학교 선택
- 학교 목록 불러오기에 실패했습니다.
- 학교 선택
-
-
- 공연 입장 알림
-
-
diff --git a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveViewModelTest.kt b/android/festago/app/src/test/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveViewModelTest.kt
deleted file mode 100644
index b6a5582c7..000000000
--- a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveViewModelTest.kt
+++ /dev/null
@@ -1,229 +0,0 @@
-package com.festago.festago.presentation.ui.ticketreserve
-
-import app.cash.turbine.test
-import com.festago.festago.analytics.AnalyticsHelper
-import com.festago.festago.model.Reservation
-import com.festago.festago.model.ReservationStage
-import com.festago.festago.model.ReservationTicket
-import com.festago.festago.model.ReservationTickets
-import com.festago.festago.model.ReservedTicket
-import com.festago.festago.model.TicketType
-import com.festago.festago.repository.AuthRepository
-import com.festago.festago.repository.FestivalRepository
-import com.festago.festago.repository.ReservationTicketRepository
-import com.festago.festago.repository.TicketRepository
-import io.mockk.coEvery
-import io.mockk.mockk
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.resetMain
-import kotlinx.coroutines.test.runTest
-import kotlinx.coroutines.test.setMain
-import org.assertj.core.api.AssertionsForClassTypes.assertThat
-import org.assertj.core.api.SoftAssertions
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import java.time.LocalDate
-import java.time.LocalDateTime
-
-class TicketReserveViewModelTest {
- private lateinit var vm: TicketReserveViewModel
- private lateinit var reservationTicketRepository: ReservationTicketRepository
- private lateinit var festivalRepository: FestivalRepository
- private lateinit var ticketRepository: TicketRepository
- private lateinit var authRepository: AuthRepository
- private lateinit var analyticsHelper: AnalyticsHelper
-
- private val fakeReservationTickets = ReservationTickets(
- listOf(
- ReservationTicket(1, TicketType.STUDENT, 219, 500),
- ReservationTicket(1, TicketType.VISITOR, 212, 300),
- ),
- )
- private val fakeReservationStage = ReservationStage(
- id = 1,
- lineUp = "르세라핌, 아이브, 뉴진스",
- reservationTickets = fakeReservationTickets,
- startTime = LocalDateTime.now(),
- ticketOpenTime = LocalDateTime.now(),
- )
- private val fakeReservationStages = List(5) { fakeReservationStage }
- private val fakeReservation = Reservation(
- id = 1,
- name = "테코대학교",
- reservationStages = fakeReservationStages,
- startDate = LocalDate.now(),
- endDate = LocalDate.now(),
- thumbnail = "https://search2.kakaocdn.net/argon/656x0_80_wr/8vLywd3V06c",
- )
-
- private val fakeReservedTicket = ReservedTicket(
- id = 1,
- entryTime = LocalDateTime.now(),
- number = 1,
- )
-
- @OptIn(ExperimentalCoroutinesApi::class)
- @Before
- fun setUp() {
- Dispatchers.setMain(UnconfinedTestDispatcher())
- reservationTicketRepository = mockk()
- festivalRepository = mockk()
- ticketRepository = mockk()
- authRepository = mockk()
- analyticsHelper = mockk(relaxed = true)
- vm = TicketReserveViewModel(
- reservationTicketRepository,
- festivalRepository,
- ticketRepository,
- authRepository,
- analyticsHelper,
- )
- }
-
- @OptIn(ExperimentalCoroutinesApi::class)
- @After
- fun tearDown() {
- Dispatchers.resetMain()
- }
-
- private fun `예약 정보 요청 결과가 다음과 같을 때`(result: Result) {
- coEvery { festivalRepository.loadFestivalDetail(any()) } returns result
- }
-
- private fun `인증 여부가 다음과 같을 때`(isSigned: Boolean) {
- coEvery { authRepository.isSigned } answers { isSigned }
- }
-
- private fun `특정 공연의 티켓 타입 요청 결과가 다음과 같을 때`(result: Result) {
- coEvery { reservationTicketRepository.loadTicketTypes(any()) } returns result
- }
-
- private fun `티켓 예약 요청 결과가 다음과 같을 때`(result: Result) {
- coEvery { ticketRepository.reserveTicket(any()) } returns result
- }
-
- @Test
- fun `예약 정보를 불러오면 성공 이벤트가 발생하고 리스트를 반환한다`() {
- // given
- `예약 정보 요청 결과가 다음과 같을 때`(Result.success(fakeReservation))
- `인증 여부가 다음과 같을 때`(true)
-
- // when
- vm.loadReservation()
-
- // then
- assertThat(vm.uiState.value).isInstanceOf(TicketReserveUiState.Success::class.java)
-
- // and
- val festival = (vm.uiState.value as TicketReserveUiState.Success).festival
- val expected = ReservationFestivalUiState(
- id = festival.id,
- name = festival.name,
- thumbnail = festival.thumbnail,
- endDate = festival.endDate,
- startDate = festival.startDate,
- )
- assertThat(festival).isEqualTo(expected)
- }
-
- @Test
- fun `예약 정보를 불러오는 것을 실패하면 에러 이벤트가 발생한다`() {
- // given
- `예약 정보 요청 결과가 다음과 같을 때`(Result.failure(Exception()))
-
- // when
- vm.loadReservation(0)
-
- // then
- assertThat(vm.uiState.value).isEqualTo(TicketReserveUiState.Error)
- }
-
- @Test
- fun `예약 정보를 불러오는 중이면 로딩 이벤트가 발생한다`() {
- // given
- coEvery {
- festivalRepository.loadFestivalDetail(0)
- } coAnswers {
- delay(1000)
- Result.success(fakeReservation)
- }
-
- // when
- vm.loadReservation()
-
- // then
- assertThat(vm.uiState.value).isEqualTo(TicketReserveUiState.Loading)
- }
-
- @Test
- fun `특정 공연의 티켓 타입을 보여주는 이벤트가 발생하면 해당 공연의 티켓 타입을 보여준다`() = runTest {
- // given
- `특정 공연의 티켓 타입 요청 결과가 다음과 같을 때`(Result.success(fakeReservationTickets))
- `인증 여부가 다음과 같을 때`(true)
-
- vm.event.test {
- // when
- vm.showTicketTypes(1, LocalDateTime.MIN)
-
- // then
- val softly = SoftAssertions().apply {
- val event = awaitItem()
- assertThat(event).isExactlyInstanceOf(TicketReserveEvent.ShowTicketTypes::class.java)
-
- // and
- val actual = (event as? TicketReserveEvent.ShowTicketTypes)?.tickets
- assertThat(actual).isEqualTo(fakeReservationTickets.sortedByTicketTypes())
- }
- softly.assertAll()
- }
- }
-
- @Test
- fun `특정 공연의 티켓 타입을 보여주는 것을 실패하면 에러 이벤트가 발생한다`() {
- // given
- `특정 공연의 티켓 타입 요청 결과가 다음과 같을 때`(Result.failure(Exception()))
- `인증 여부가 다음과 같을 때`(true)
-
- // when
- vm.showTicketTypes(1, LocalDateTime.MIN)
-
- // then
- assertThat(vm.uiState.value).isEqualTo(TicketReserveUiState.Error)
- }
-
- @Test
- fun `티켓 유형을 선택하고 예약하면 예약 성공 이벤트가 발생한다`() = runTest {
- // given
- coEvery {
- ticketRepository.reserveTicket(any())
- } answers {
- Result.success(fakeReservedTicket)
- }
-
- vm.event.test {
- // when
- vm.reserveTicket(0)
-
- // then
- assertThat(awaitItem()).isExactlyInstanceOf(TicketReserveEvent.ReserveTicketSuccess::class.java)
- }
- }
-
- @Test
- fun `티켓 유형을 선택하고 예약하는 것을 실패하면 예약 실패 이벤트가 발생한다`() = runTest {
- // given
- `티켓 예약 요청 결과가 다음과 같을 때`(Result.failure(Exception()))
-
- vm.event.test {
- // when
- vm.reserveTicket(0)
-
- // then
- assertThat(awaitItem()).isExactlyInstanceOf(TicketReserveEvent.ReserveTicketFailed::class.java)
- }
- }
-}
diff --git a/android/festago/build.gradle.kts b/android/festago/build.gradle.kts
index 649598d35..a39bf1aab 100644
--- a/android/festago/build.gradle.kts
+++ b/android/festago/build.gradle.kts
@@ -7,6 +7,8 @@ plugins {
val kotlinVersion = "1.8.20"
kotlin("android") version kotlinVersion apply false
kotlin("jvm") version kotlinVersion apply false
+ kotlin("kapt") version kotlinVersion apply false
+
id("org.jlleitschuh.gradle.ktlint") version "11.1.0" apply false
id("com.google.gms.google-services") version "4.3.15" apply false
@@ -14,4 +16,12 @@ plugins {
id("com.google.firebase.crashlytics") version "2.9.7" apply false
id("com.google.dagger.hilt.android") version "2.44" apply false
+
+ id("org.jetbrains.kotlin.plugin.serialization") version "1.8.10" apply false
+
+ id("androidx.navigation.safeargs") version "2.5.3" apply false
+}
+
+allprojects {
+ apply(plugin = "org.jlleitschuh.gradle.ktlint")
}
diff --git a/android/festago/common/.gitignore b/android/festago/common/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/android/festago/common/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/android/festago/common/build.gradle.kts b/android/festago/common/build.gradle.kts
new file mode 100644
index 000000000..9fd20d703
--- /dev/null
+++ b/android/festago/common/build.gradle.kts
@@ -0,0 +1,56 @@
+plugins {
+ id("com.android.library")
+ id("org.jetbrains.kotlin.android")
+ kotlin("kapt")
+}
+
+android {
+ namespace = "com.festago.festago.common"
+ compileSdk = 34
+
+ defaultConfig {
+ minSdk = 26
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ debug {
+ buildConfigField("Boolean", "DEBUG_MODE", "true")
+ }
+ release {
+ isMinifyEnabled = false
+ buildConfigField("Boolean", "DEBUG_MODE", "false")
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro",
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+ kotlinOptions {
+ jvmTarget = "17"
+ }
+}
+
+kotlin.jvmToolchain(17)
+
+dependencies {
+ // common
+ implementation(project(":domain"))
+ // hilt
+ implementation("com.google.dagger:hilt-android:2.44")
+ kapt("com.google.dagger:hilt-android-compiler:2.44")
+
+ // kakao login
+ implementation("com.kakao.sdk:v2-user:2.12.0")
+
+ // firebase
+ 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-messaging-ktx:23.4.1")
+ implementation("com.google.firebase:firebase-config:21.6.3")
+}
diff --git a/android/festago/common/consumer-rules.pro b/android/festago/common/consumer-rules.pro
new file mode 100644
index 000000000..e69de29bb
diff --git a/android/festago/common/proguard-rules.pro b/android/festago/common/proguard-rules.pro
new file mode 100644
index 000000000..481bb4348
--- /dev/null
+++ b/android/festago/common/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/android/festago/common/src/main/AndroidManifest.xml b/android/festago/common/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..275af8650
--- /dev/null
+++ b/android/festago/common/src/main/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/festago/app/src/main/java/com/festago/festago/analytics/AnalyticsEvent.kt b/android/festago/common/src/main/java/com/festago/festago/common/analytics/AnalyticsEvent.kt
similarity index 77%
rename from android/festago/app/src/main/java/com/festago/festago/analytics/AnalyticsEvent.kt
rename to android/festago/common/src/main/java/com/festago/festago/common/analytics/AnalyticsEvent.kt
index f002b199f..cafbe6a91 100644
--- a/android/festago/app/src/main/java/com/festago/festago/analytics/AnalyticsEvent.kt
+++ b/android/festago/common/src/main/java/com/festago/festago/common/analytics/AnalyticsEvent.kt
@@ -1,4 +1,4 @@
-package com.festago.festago.analytics
+package com.festago.festago.common.analytics
data class AnalyticsEvent(
val type: String,
diff --git a/android/festago/app/src/main/java/com/festago/festago/analytics/AnalyticsExtensions.kt b/android/festago/common/src/main/java/com/festago/festago/common/analytics/AnalyticsExtensions.kt
similarity index 83%
rename from android/festago/app/src/main/java/com/festago/festago/analytics/AnalyticsExtensions.kt
rename to android/festago/common/src/main/java/com/festago/festago/common/analytics/AnalyticsExtensions.kt
index 1bf3f4345..167fe97f0 100644
--- a/android/festago/app/src/main/java/com/festago/festago/analytics/AnalyticsExtensions.kt
+++ b/android/festago/common/src/main/java/com/festago/festago/common/analytics/AnalyticsExtensions.kt
@@ -1,4 +1,4 @@
-package com.festago.festago.analytics
+package com.festago.festago.common.analytics
fun AnalyticsHelper.logNetworkFailure(key: String, value: String) {
logEvent(
diff --git a/android/festago/common/src/main/java/com/festago/festago/common/analytics/AnalyticsHelper.kt b/android/festago/common/src/main/java/com/festago/festago/common/analytics/AnalyticsHelper.kt
new file mode 100644
index 000000000..7fddbc6cb
--- /dev/null
+++ b/android/festago/common/src/main/java/com/festago/festago/common/analytics/AnalyticsHelper.kt
@@ -0,0 +1,5 @@
+package com.festago.festago.common.analytics
+
+interface AnalyticsHelper {
+ fun logEvent(event: AnalyticsEvent)
+}
diff --git a/android/festago/app/src/main/java/com/festago/festago/analytics/FirebaseAnalyticsHelper.kt b/android/festago/common/src/main/java/com/festago/festago/common/analytics/FirebaseAnalyticsHelper.kt
similarity index 89%
rename from android/festago/app/src/main/java/com/festago/festago/analytics/FirebaseAnalyticsHelper.kt
rename to android/festago/common/src/main/java/com/festago/festago/common/analytics/FirebaseAnalyticsHelper.kt
index 393795bf6..05af4066a 100644
--- a/android/festago/app/src/main/java/com/festago/festago/analytics/FirebaseAnalyticsHelper.kt
+++ b/android/festago/common/src/main/java/com/festago/festago/common/analytics/FirebaseAnalyticsHelper.kt
@@ -1,4 +1,4 @@
-package com.festago.festago.analytics
+package com.festago.festago.common.analytics
import android.content.Context
import android.os.Bundle
@@ -7,7 +7,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
class FirebaseAnalyticsHelper @Inject constructor(
- @ApplicationContext context: Context
+ @ApplicationContext context: Context,
) : AnalyticsHelper {
private val firebaseAnalytics: FirebaseAnalytics by lazy {
diff --git a/android/festago/common/src/main/java/com/festago/festago/common/analytics/di/AnalyticsModule.kt b/android/festago/common/src/main/java/com/festago/festago/common/analytics/di/AnalyticsModule.kt
new file mode 100644
index 000000000..3fe5aa3a7
--- /dev/null
+++ b/android/festago/common/src/main/java/com/festago/festago/common/analytics/di/AnalyticsModule.kt
@@ -0,0 +1,19 @@
+package com.festago.festago.common.analytics.di
+
+import com.festago.festago.common.analytics.AnalyticsHelper
+import com.festago.festago.common.analytics.FirebaseAnalyticsHelper
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@InstallIn(SingletonComponent::class)
+@Module
+interface AnalyticsModule {
+ @Binds
+ @Singleton
+ fun bindsFirebaseAnalyticsHelper(
+ analyticsHelper: FirebaseAnalyticsHelper,
+ ): AnalyticsHelper
+}
diff --git a/android/festago/common/src/main/java/com/festago/festago/common/kakao/KakaoAuthorization.kt b/android/festago/common/src/main/java/com/festago/festago/common/kakao/KakaoAuthorization.kt
new file mode 100644
index 000000000..d9a97983f
--- /dev/null
+++ b/android/festago/common/src/main/java/com/festago/festago/common/kakao/KakaoAuthorization.kt
@@ -0,0 +1,86 @@
+package com.festago.festago.common.kakao
+
+import android.content.Context
+import com.festago.festago.domain.model.nonce.NonceGenerator
+import com.kakao.sdk.auth.TokenManagerProvider
+import com.kakao.sdk.common.model.ClientError
+import com.kakao.sdk.common.model.ClientErrorCause
+import com.kakao.sdk.user.UserApiClient
+import javax.inject.Inject
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+import kotlin.coroutines.suspendCoroutine
+
+class KakaoAuthorization @Inject constructor() {
+
+ suspend fun getIdToken(context: Context): Result {
+ return runCatching {
+ val nonce = NonceGenerator().generate()
+ if (UserApiClient.instance.isKakaoTalkLoginAvailable(context)) {
+ try {
+ loginWithKakaoTalk(context, nonce)
+ } catch (error: Throwable) {
+ if (error is ClientError && error.reason == ClientErrorCause.Cancelled) throw error
+ loginWithKakaoAccount(context, nonce)
+ }
+ } else {
+ loginWithKakaoAccount(context, nonce)
+ }
+ }
+ }
+
+ private suspend fun loginWithKakaoTalk(context: Context, nonce: String?): String {
+ return suspendCoroutine { continuation ->
+ UserApiClient.instance.loginWithKakaoTalk(context, nonce = nonce) { token, error ->
+ if (error != null) {
+ continuation.resumeWithException(error)
+ } else if (token != null) {
+ if (token.idToken != null) {
+ continuation.resume(token.idToken!!)
+ } else {
+ continuation.resumeWithException(RuntimeException("Failure get kakao id token"))
+ }
+ } else {
+ continuation.resumeWithException(RuntimeException("Failure get kakao access token"))
+ }
+ }
+ }
+ }
+
+ private suspend fun loginWithKakaoAccount(context: Context, nonce: String?): String {
+ return suspendCoroutine { continuation ->
+ UserApiClient.instance.loginWithKakaoAccount(context, nonce = nonce) { token, error ->
+ if (error != null) {
+ continuation.resumeWithException(error)
+ } else if (token != null) {
+ if (token.idToken != null) {
+ continuation.resume(token.idToken!!)
+ } else {
+ continuation.resumeWithException(RuntimeException("Failure get kakao id token"))
+ }
+ } else {
+ continuation.resumeWithException(RuntimeException("Failure get kakao access token"))
+ }
+ }
+ }
+ }
+
+ suspend fun signOut(): Result {
+ UserApiClient.instance.logout {}
+ return Result.success(Unit)
+ }
+
+ suspend fun deleteAccount(): Result {
+ return suspendCoroutine> { continuation ->
+ TokenManagerProvider.instance.manager.getToken()?.let {
+ UserApiClient.instance.unlink { error ->
+ if (error == null) {
+ continuation.resume(Result.success(Unit))
+ } else {
+ continuation.resumeWithException(error)
+ }
+ }
+ } ?: continuation.resume(Result.success(Unit))
+ }
+ }
+}
diff --git a/android/festago/data-legacy/.gitignore b/android/festago/data-legacy/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/android/festago/data-legacy/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/android/festago/data-legacy/build.gradle.kts b/android/festago/data-legacy/build.gradle.kts
new file mode 100644
index 000000000..a40a9a331
--- /dev/null
+++ b/android/festago/data-legacy/build.gradle.kts
@@ -0,0 +1,90 @@
+import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties
+
+plugins {
+ id("com.android.library")
+ id("org.jetbrains.kotlin.android")
+ id("kotlin-parcelize")
+ id("kotlinx-serialization")
+ id("kotlin-kapt")
+}
+
+android {
+ namespace = "com.festago.festago.data"
+ compileSdk = 34
+
+ defaultConfig {
+ minSdk = 28
+
+ buildConfigField("String", "BASE_URL", getSecretKey("base_url"))
+ }
+
+ buildTypes {
+ debug {
+ buildConfigField("Boolean", "DEBUG_MODE", "true")
+ }
+
+ release {
+ isMinifyEnabled = false
+ buildConfigField("Boolean", "DEBUG_MODE", "false")
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro",
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+ kotlinOptions {
+ jvmTarget = "17"
+ }
+
+ testOptions {
+ unitTests.isReturnDefaultValues = true
+ }
+}
+
+kotlin.jvmToolchain(17)
+
+dependencies {
+ implementation(project(":domain-legacy"))
+
+ // hilt
+ implementation("com.google.dagger:hilt-android:2.50")
+ implementation("com.google.firebase:firebase-messaging-ktx:23.4.0")
+ kapt("com.google.dagger:hilt-android-compiler:2.50")
+
+ // okhttp3
+ implementation("com.squareup.okhttp3:okhttp:4.11.0")
+
+ // okhttp3-mockwebserver
+ implementation("com.squareup.okhttp3:mockwebserver:4.11.0")
+
+ // kotlin-serialization
+ implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")
+ implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
+
+ // Encrypted SharedPreference
+ implementation("androidx.security:security-crypto-ktx:1.1.0-alpha06")
+
+ // kakao login
+ implementation("com.kakao.sdk:v2-user:2.12.0")
+
+ // junit4
+ testImplementation("junit:junit:4.13.2")
+ testImplementation("androidx.test.ext:junit:1.1.5")
+ testImplementation("androidx.test:runner:1.5.2")
+
+ // assertJ
+ testImplementation("org.assertj:assertj-core:3.22.0")
+
+ // coroutine
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.6.4")
+ testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4")
+}
+
+fun getSecretKey(propertyKey: String): String {
+ return gradleLocalProperties(rootDir).getProperty(propertyKey)
+}
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/MockWeb.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/MockWeb.kt
similarity index 88%
rename from android/festago/app/src/main/java/com/festago/festago/data/MockWeb.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/MockWeb.kt
index 219ad3c2e..6c35516b2 100644
--- a/android/festago/app/src/main/java/com/festago/festago/data/MockWeb.kt
+++ b/android/festago/data-legacy/src/main/java/com/festago/festago/data/MockWeb.kt
@@ -12,7 +12,7 @@ class MockWeb {
init {
val thread = Thread {
- mockWebServer.dispatcher = dispatcher
+ mockWebServer.dispatcher = com.festago.festago.data.MockWeb.Companion.dispatcher
mockWebServer.url("/")
url = mockWebServer.url("").toString()
}
@@ -30,7 +30,7 @@ class MockWeb {
when {
path.startsWith("/member-tickets") -> MockResponse()
.setResponseCode(201)
- .setBody(getQrCode())
+ .setBody(com.festago.festago.data.MockWeb.Companion.getQrCode())
else -> MockResponse().setResponseCode(404)
}
@@ -43,21 +43,25 @@ class MockWeb {
MockResponse()
.setHeader("Content-Type", "application/json")
.setResponseCode(200)
- .setBody(getTicket(ticketId))
+ .setBody(
+ com.festago.festago.data.MockWeb.Companion.getTicket(
+ ticketId
+ )
+ )
}
path.startsWith("/member-tickets") -> {
MockResponse()
.setHeader("Content-Type", "application/json")
.setResponseCode(200)
- .setBody(getTickets())
+ .setBody(com.festago.festago.data.MockWeb.Companion.getTickets())
}
path.startsWith("/festivals") -> {
MockResponse()
.setHeader("Content-Type", "application/json")
.setResponseCode(200)
- .setBody(getFestivals())
+ .setBody(com.festago.festago.data.MockWeb.Companion.getFestivals())
}
else -> MockResponse().setResponseCode(404)
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/datasource/TokenDataSource.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/datasource/TokenDataSource.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/datasource/TokenDataSource.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/datasource/TokenDataSource.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/datasource/TokenLocalDataSource.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/datasource/TokenLocalDataSource.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/datasource/TokenLocalDataSource.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/datasource/TokenLocalDataSource.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/di/ViewModelScopeModule.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/di/ViewModelScopeModule.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/di/ViewModelScopeModule.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/di/ViewModelScopeModule.kt
diff --git a/android/festago/data-legacy/src/main/java/com/festago/festago/data/di/singletonscope/ApiModule.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/di/singletonscope/ApiModule.kt
new file mode 100644
index 000000000..f839c885e
--- /dev/null
+++ b/android/festago/data-legacy/src/main/java/com/festago/festago/data/di/singletonscope/ApiModule.kt
@@ -0,0 +1,83 @@
+package com.festago.festago.data.di.singletonscope
+
+import com.festago.festago.data.BuildConfig
+import com.festago.festago.data.retrofit.AuthInterceptor
+import com.festago.festago.repository.AuthRepository
+import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
+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.Qualifier
+import javax.inject.Singleton
+
+@Qualifier
+@Retention(AnnotationRetention.BINARY)
+annotation class AuthOkHttpClientQualifier
+
+@Qualifier
+@Retention(AnnotationRetention.BINARY)
+annotation class NormalRetrofitQualifier
+
+@Qualifier
+@Retention(AnnotationRetention.BINARY)
+annotation class AuthRetrofitQualifier
+
+@Qualifier
+@Retention(AnnotationRetention.BINARY)
+annotation class BaseUrlQualifier
+
+@InstallIn(SingletonComponent::class)
+@Module
+object ApiModule {
+
+ @Provides
+ @Singleton
+ @AuthOkHttpClientQualifier
+ fun provideOkHttpClient(authRepository: AuthRepository): OkHttpClient = OkHttpClient
+ .Builder()
+ .addInterceptor(AuthInterceptor(authRepository))
+ .build()
+
+ @Provides
+ @Singleton
+ fun provideRetrofitConverterFactory(): retrofit2.Converter.Factory {
+ val json = Json {
+ ignoreUnknownKeys = true
+ }
+ return json.asConverterFactory("application/json".toMediaType())
+ }
+
+ @Provides
+ @Singleton
+ @NormalRetrofitQualifier
+ fun providesNormalRetrofit(
+ @BaseUrlQualifier baseUrl: String,
+ converterFactory: retrofit2.Converter.Factory,
+ ): Retrofit = Retrofit.Builder()
+ .baseUrl(baseUrl)
+ .addConverterFactory(converterFactory)
+ .build()
+
+ @Provides
+ @Singleton
+ @AuthRetrofitQualifier
+ fun providesAuthRetrofit(
+ @BaseUrlQualifier baseUrl: String,
+ @AuthOkHttpClientQualifier okHttpClient: OkHttpClient,
+ converterFactory: retrofit2.Converter.Factory,
+ ): Retrofit = Retrofit.Builder()
+ .baseUrl(baseUrl)
+ .client(okHttpClient)
+ .addConverterFactory(converterFactory)
+ .build()
+
+ @Provides
+ @Singleton
+ @BaseUrlQualifier
+ fun providesBaseUrl(): String = BuildConfig.BASE_URL
+}
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/di/singletonscope/DataSourceModule.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/di/singletonscope/DataSourceModule.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/di/singletonscope/DataSourceModule.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/di/singletonscope/DataSourceModule.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/di/singletonscope/FirebaseModule.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/di/singletonscope/FirebaseModule.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/di/singletonscope/FirebaseModule.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/di/singletonscope/FirebaseModule.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/di/singletonscope/RepositoryModule.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/di/singletonscope/RepositoryModule.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/di/singletonscope/RepositoryModule.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/di/singletonscope/RepositoryModule.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/di/singletonscope/ServiceModule.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/di/singletonscope/ServiceModule.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/di/singletonscope/ServiceModule.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/di/singletonscope/ServiceModule.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/dto/FestivalResponse.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/FestivalResponse.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/dto/FestivalResponse.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/FestivalResponse.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/dto/FestivalsResponse.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/FestivalsResponse.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/dto/FestivalsResponse.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/FestivalsResponse.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/dto/MemberTicketFestivalResponse.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/MemberTicketFestivalResponse.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/dto/MemberTicketFestivalResponse.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/MemberTicketFestivalResponse.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/dto/MemberTicketResponse.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/MemberTicketResponse.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/dto/MemberTicketResponse.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/MemberTicketResponse.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/dto/MemberTicketsResponse.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/MemberTicketsResponse.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/dto/MemberTicketsResponse.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/MemberTicketsResponse.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/dto/OauthRequest.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/OauthRequest.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/dto/OauthRequest.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/OauthRequest.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/dto/OauthTokenResponse.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/OauthTokenResponse.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/dto/OauthTokenResponse.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/OauthTokenResponse.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/dto/ReservationFestivalResponse.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/ReservationFestivalResponse.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/dto/ReservationFestivalResponse.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/ReservationFestivalResponse.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/dto/ReservationStageResponse.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/ReservationStageResponse.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/dto/ReservationStageResponse.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/ReservationStageResponse.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/dto/ReservationTicketResponse.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/ReservationTicketResponse.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/dto/ReservationTicketResponse.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/ReservationTicketResponse.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/dto/ReservationTicketsResponse.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/ReservationTicketsResponse.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/dto/ReservationTicketsResponse.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/ReservationTicketsResponse.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/dto/ReservedTicketRequest.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/ReservedTicketRequest.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/dto/ReservedTicketRequest.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/ReservedTicketRequest.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/dto/ReservedTicketResponse.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/ReservedTicketResponse.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/dto/ReservedTicketResponse.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/ReservedTicketResponse.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/dto/SchoolResponse.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/SchoolResponse.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/dto/SchoolResponse.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/SchoolResponse.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/dto/SchoolsResponse.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/SchoolsResponse.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/dto/SchoolsResponse.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/SchoolsResponse.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/dto/SendVerificationRequest.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/SendVerificationRequest.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/dto/SendVerificationRequest.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/SendVerificationRequest.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/dto/StageResponse.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/StageResponse.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/dto/StageResponse.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/StageResponse.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/dto/TicketCodeDto.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/TicketCodeDto.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/dto/TicketCodeDto.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/TicketCodeDto.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/dto/UserProfileResponse.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/UserProfileResponse.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/dto/UserProfileResponse.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/UserProfileResponse.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/dto/VerificationRequest.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/VerificationRequest.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/dto/VerificationRequest.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/dto/VerificationRequest.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/repository/AuthDefaultRepository.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/repository/AuthDefaultRepository.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/repository/AuthDefaultRepository.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/repository/AuthDefaultRepository.kt
diff --git a/android/festago/data-legacy/src/main/java/com/festago/festago/data/repository/FestivalDefaultRepository.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/repository/FestivalDefaultRepository.kt
new file mode 100644
index 000000000..d9d999223
--- /dev/null
+++ b/android/festago/data-legacy/src/main/java/com/festago/festago/data/repository/FestivalDefaultRepository.kt
@@ -0,0 +1,23 @@
+package com.festago.festago.data.repository
+
+import com.festago.festago.data.service.FestivalRetrofitService
+import com.festago.festago.data.util.onSuccessOrCatch
+import com.festago.festago.data.util.runCatchingResponse
+import com.festago.festago.model.Festival
+import com.festago.festago.model.FestivalFilter
+import com.festago.festago.model.Reservation
+import com.festago.festago.repository.FestivalRepository
+import javax.inject.Inject
+
+class FestivalDefaultRepository @Inject constructor(
+ private val festivalRetrofitService: FestivalRetrofitService,
+) : FestivalRepository {
+ override suspend fun loadFestivals(festivalFilter: FestivalFilter): Result> =
+ runCatchingResponse {
+ festivalRetrofitService.getFestivals(festivalFilter.name)
+ }.onSuccessOrCatch { it.toDomain() }
+
+ override suspend fun loadFestivalDetail(festivalId: Long): Result =
+ runCatchingResponse { festivalRetrofitService.getFestivalDetail(festivalId) }
+ .onSuccessOrCatch { it.toDomain() }
+}
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/repository/ReservationTicketDefaultRepository.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/repository/ReservationTicketDefaultRepository.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/repository/ReservationTicketDefaultRepository.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/repository/ReservationTicketDefaultRepository.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/repository/SchoolDefaultRepository.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/repository/SchoolDefaultRepository.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/repository/SchoolDefaultRepository.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/repository/SchoolDefaultRepository.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/repository/SocialAuthKakaoRepository.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/repository/SocialAuthKakaoRepository.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/repository/SocialAuthKakaoRepository.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/repository/SocialAuthKakaoRepository.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/repository/StudentVerificationDefaultRepository.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/repository/StudentVerificationDefaultRepository.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/repository/StudentVerificationDefaultRepository.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/repository/StudentVerificationDefaultRepository.kt
diff --git a/android/festago/data-legacy/src/main/java/com/festago/festago/data/repository/TicketDefaultRepository.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/repository/TicketDefaultRepository.kt
new file mode 100644
index 000000000..43d9197aa
--- /dev/null
+++ b/android/festago/data-legacy/src/main/java/com/festago/festago/data/repository/TicketDefaultRepository.kt
@@ -0,0 +1,47 @@
+package com.festago.festago.data.repository
+
+import com.festago.festago.data.dto.ReservedTicketRequest
+import com.festago.festago.data.service.TicketRetrofitService
+import com.festago.festago.data.util.onSuccessOrCatch
+import com.festago.festago.data.util.runCatchingResponse
+import com.festago.festago.model.ErrorCode
+import com.festago.festago.model.ReservedTicket
+import com.festago.festago.model.Ticket
+import com.festago.festago.model.TicketCode
+import com.festago.festago.repository.TicketRepository
+import javax.inject.Inject
+
+class TicketDefaultRepository @Inject constructor(
+ private val ticketRetrofitService: TicketRetrofitService,
+) : TicketRepository {
+
+ override suspend fun loadTicket(ticketId: Long): Result =
+ runCatchingResponse { ticketRetrofitService.getTicket(ticketId) }
+ .onSuccessOrCatch { it.toDomain() }
+
+ override suspend fun loadCurrentTickets(): Result> =
+ runCatchingResponse { ticketRetrofitService.getCurrentTickets() }
+ .onSuccessOrCatch { it.toDomain() }
+
+ override suspend fun loadTicketCode(ticketId: Long): Result =
+ runCatchingResponse { ticketRetrofitService.getTicketCode(ticketId) }
+ .onSuccessOrCatch { it.toDomain() }
+
+ override suspend fun loadHistoryTickets(size: Int): Result> =
+ runCatchingResponse { ticketRetrofitService.getHistoryTickets(size) }
+ .onSuccessOrCatch { it.toDomain() }
+
+ override suspend fun reserveTicket(ticketId: Int): Result =
+ runCatchingResponse { ticketRetrofitService.postReserveTicket(ReservedTicketRequest(ticketId)) }
+ .onSuccessOrCatch { it.toDomain() }
+ .onFailure { throwable ->
+ val message = throwable.message ?: "ERROR_UNKNOWN"
+ val error: Throwable = when {
+ "NEED_STUDENT_VERIFICATION" in message -> ErrorCode.NEED_STUDENT_VERIFICATION()
+ "RESERVE_TICKET_OVER_AMOUNT" in message -> ErrorCode.RESERVE_TICKET_OVER_AMOUNT()
+ "TICKET_SOLD_OUT" in message -> ErrorCode.TICKET_SOLD_OUT()
+ else -> throwable
+ }
+ return Result.failure(error)
+ }
+}
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/repository/UserDefaultRepository.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/repository/UserDefaultRepository.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/repository/UserDefaultRepository.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/repository/UserDefaultRepository.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/retrofit/AuthInterceptor.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/retrofit/AuthInterceptor.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/retrofit/AuthInterceptor.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/retrofit/AuthInterceptor.kt
diff --git a/android/festago/data-legacy/src/main/java/com/festago/festago/data/service/FestivalRetrofitService.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/service/FestivalRetrofitService.kt
new file mode 100644
index 000000000..551442889
--- /dev/null
+++ b/android/festago/data-legacy/src/main/java/com/festago/festago/data/service/FestivalRetrofitService.kt
@@ -0,0 +1,20 @@
+package com.festago.festago.data.service
+
+import com.festago.festago.data.dto.FestivalsResponse
+import com.festago.festago.data.dto.ReservationFestivalResponse
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface FestivalRetrofitService {
+ @GET("/festivals")
+ suspend fun getFestivals(
+ @Query("festivalFilter") festivalFilter: String,
+ ): Response
+
+ @GET("/festivals/{festivalId}")
+ suspend fun getFestivalDetail(
+ @Path("festivalId") festivalId: Long,
+ ): Response
+}
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/service/ReservationTicketRetrofitService.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/service/ReservationTicketRetrofitService.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/service/ReservationTicketRetrofitService.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/service/ReservationTicketRetrofitService.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/service/SchoolRetrofitService.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/service/SchoolRetrofitService.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/service/SchoolRetrofitService.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/service/SchoolRetrofitService.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/service/StudentVerificationRetrofitService.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/service/StudentVerificationRetrofitService.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/service/StudentVerificationRetrofitService.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/service/StudentVerificationRetrofitService.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/service/TicketRetrofitService.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/service/TicketRetrofitService.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/service/TicketRetrofitService.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/service/TicketRetrofitService.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/service/TokenRetrofitService.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/service/TokenRetrofitService.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/service/TokenRetrofitService.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/service/TokenRetrofitService.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/service/UserRetrofitService.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/service/UserRetrofitService.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/service/UserRetrofitService.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/service/UserRetrofitService.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/util/ResponseExt.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/util/ResponseExt.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/util/ResponseExt.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/util/ResponseExt.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/data/util/ResultExt.kt b/android/festago/data-legacy/src/main/java/com/festago/festago/data/util/ResultExt.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/data/util/ResultExt.kt
rename to android/festago/data-legacy/src/main/java/com/festago/festago/data/util/ResultExt.kt
diff --git a/android/festago/app/src/test/java/com/festago/festago/data/repository/ReservationTicketRetrofitServiceTest.kt b/android/festago/data-legacy/src/test/java/com/festago/festago/data/repository/ReservationTicketRetrofitServiceTest.kt
similarity index 99%
rename from android/festago/app/src/test/java/com/festago/festago/data/repository/ReservationTicketRetrofitServiceTest.kt
rename to android/festago/data-legacy/src/test/java/com/festago/festago/data/repository/ReservationTicketRetrofitServiceTest.kt
index 8863bb511..bc49a8c24 100644
--- a/android/festago/app/src/test/java/com/festago/festago/data/repository/ReservationTicketRetrofitServiceTest.kt
+++ b/android/festago/data-legacy/src/test/java/com/festago/festago/data/repository/ReservationTicketRetrofitServiceTest.kt
@@ -198,8 +198,7 @@ class ReservationTicketRetrofitServiceTest {
startTime = "2023-07-09T16:00:00",
ticketOpenTime = "2023-07-08T14:00:00",
lineUp = "르세라핌,아이브,뉴진스",
- tickets =
- listOf(
+ tickets = listOf(
ReservationTicketResponse(
id = 1,
ticketType = "STUDENT",
diff --git a/android/festago/data/.gitignore b/android/festago/data/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/android/festago/data/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/android/festago/data/build.gradle.kts b/android/festago/data/build.gradle.kts
new file mode 100644
index 000000000..065a0b329
--- /dev/null
+++ b/android/festago/data/build.gradle.kts
@@ -0,0 +1,101 @@
+import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties
+
+plugins {
+ id("com.android.library")
+ id("org.jetbrains.kotlin.android")
+ id("kotlin-parcelize")
+ id("kotlinx-serialization")
+ id("kotlin-kapt")
+}
+
+android {
+ namespace = "com.festago.festago.data"
+ compileSdk = 34
+
+ defaultConfig {
+ minSdk = 28
+
+ buildConfigField("String", "BASE_URL", getSecretKey("base_url"))
+ }
+
+ buildTypes {
+ debug {
+ buildConfigField("Boolean", "DEBUG_MODE", "true")
+ }
+
+ release {
+ isMinifyEnabled = false
+ buildConfigField("Boolean", "DEBUG_MODE", "false")
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro",
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+ kotlinOptions {
+ jvmTarget = "17"
+ }
+
+ testOptions {
+ unitTests.isReturnDefaultValues = true
+ }
+}
+
+kotlin.jvmToolchain(17)
+
+dependencies {
+ implementation(project(":domain"))
+ implementation(project(":common"))
+
+ // hilt
+ implementation("com.google.dagger:hilt-android:2.50")
+ implementation("com.google.firebase:firebase-messaging-ktx:23.4.0")
+ kapt("com.google.dagger:hilt-android-compiler:2.50")
+
+ // okhttp3
+ implementation("com.squareup.okhttp3:okhttp:4.11.0")
+
+ // okhttp3-mockwebserver
+ implementation("com.squareup.okhttp3:mockwebserver:4.11.0")
+
+ // kotlin-serialization
+ implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")
+ implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
+
+ // Encrypted SharedPreference
+ implementation("androidx.security:security-crypto-ktx:1.1.0-alpha06")
+
+ // junit4
+ testImplementation("junit:junit:4.13.2")
+ testImplementation("androidx.test.ext:junit:1.1.5")
+ testImplementation("androidx.test:runner:1.5.2")
+
+ // assertJ
+ testImplementation("org.assertj:assertj-core:3.22.0")
+
+ // coroutine
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.6.4")
+ testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4")
+
+ // room
+ val room_version = "2.6.1"
+ implementation("androidx.room:room-runtime:$room_version")
+ annotationProcessor("androidx.room:room-compiler:$room_version")
+ kapt("androidx.room:room-compiler:$room_version")
+ implementation("androidx.room:room-ktx:$room_version")
+
+ // logging httpLoggingInterceptor
+ implementation("com.squareup.okhttp3:logging-interceptor:4.11.0")
+
+ // gson
+ implementation("com.google.code.gson:gson:2.9.0")
+}
+
+fun getSecretKey(propertyKey: String): String {
+ return gradleLocalProperties(rootDir).getProperty(propertyKey)
+}
diff --git a/android/festago/data/consumer-rules.pro b/android/festago/data/consumer-rules.pro
new file mode 100644
index 000000000..e69de29bb
diff --git a/android/festago/data/proguard-rules.pro b/android/festago/data/proguard-rules.pro
new file mode 100644
index 000000000..481bb4348
--- /dev/null
+++ b/android/festago/data/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/android/festago/data/src/main/AndroidManifest.xml b/android/festago/data/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..a5918e68a
--- /dev/null
+++ b/android/festago/data/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/dao/RecentSearchQueryDao.kt b/android/festago/data/src/main/java/com/festago/festago/data/dao/RecentSearchQueryDao.kt
new file mode 100644
index 000000000..611889f8c
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/dao/RecentSearchQueryDao.kt
@@ -0,0 +1,22 @@
+package com.festago.festago.data.dao
+
+import androidx.room.Dao
+import androidx.room.Delete
+import androidx.room.Query
+import androidx.room.Upsert
+import com.festago.festago.data.model.RecentSearchQueryEntity
+
+@Dao
+interface RecentSearchQueryDao {
+ @Query(value = "SELECT * FROM recentSearchQueries ORDER BY created_at DESC LIMIT :limit")
+ suspend fun getRecentSearchQueryEntities(limit: Int): List
+
+ @Upsert
+ suspend fun insertOrReplaceRecentSearchQuery(recentSearchQuery: RecentSearchQueryEntity)
+
+ @Delete
+ suspend fun deleteRecentSearchQuery(recentSearchQuery: RecentSearchQueryEntity)
+
+ @Query(value = "DELETE FROM recentSearchQueries")
+ suspend fun clearRecentSearchQueries()
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/database/FestagoDatabase.kt b/android/festago/data/src/main/java/com/festago/festago/data/database/FestagoDatabase.kt
new file mode 100644
index 000000000..6db6158bf
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/database/FestagoDatabase.kt
@@ -0,0 +1,11 @@
+package com.festago.festago.data.database
+
+import androidx.room.Database
+import androidx.room.RoomDatabase
+import com.festago.festago.data.dao.RecentSearchQueryDao
+import com.festago.festago.data.model.RecentSearchQueryEntity
+
+@Database(entities = [RecentSearchQueryEntity::class], version = 1)
+abstract class FestagoDatabase : RoomDatabase() {
+ abstract fun recentSearchQueryDao(): RecentSearchQueryDao
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/datasource/bookmark/BookmarkDataSource.kt b/android/festago/data/src/main/java/com/festago/festago/data/datasource/bookmark/BookmarkDataSource.kt
new file mode 100644
index 000000000..290acc4ce
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/datasource/bookmark/BookmarkDataSource.kt
@@ -0,0 +1,11 @@
+package com.festago.festago.data.datasource.bookmark
+
+import com.festago.festago.domain.model.bookmark.BookmarkType
+
+interface BookmarkDataSource {
+ fun addBookmark(id: Long, type: BookmarkType)
+ fun isBookmarked(id: Long, type: BookmarkType): Boolean
+ fun deleteBookmark(id: Long, type: BookmarkType)
+ fun getBookmarks(type: BookmarkType): List
+ fun setBookmarks(type: BookmarkType, ids: List)
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/datasource/bookmark/DefaultBookMarkDataSource.kt b/android/festago/data/src/main/java/com/festago/festago/data/datasource/bookmark/DefaultBookMarkDataSource.kt
new file mode 100644
index 000000000..e42c639ee
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/datasource/bookmark/DefaultBookMarkDataSource.kt
@@ -0,0 +1,30 @@
+package com.festago.festago.data.datasource.bookmark
+
+import com.festago.festago.domain.model.bookmark.BookmarkType
+import javax.inject.Inject
+
+class DefaultBookMarkDataSource @Inject constructor() : BookmarkDataSource {
+ private val bookmarkStore = mutableMapOf>()
+
+ override fun addBookmark(id: Long, type: BookmarkType) {
+ bookmarkStore[type] = (bookmarkStore[type] ?: listOf()) + id
+ }
+
+ override fun isBookmarked(id: Long, type: BookmarkType): Boolean {
+ bookmarkStore[type]?.let { return it.contains(id) }
+ return false
+ }
+
+ override fun deleteBookmark(id: Long, type: BookmarkType) {
+ val bookmarks = bookmarkStore[type] ?: return
+ bookmarkStore[type] = bookmarks - id
+ }
+
+ override fun getBookmarks(type: BookmarkType): List {
+ return bookmarkStore[type] ?: listOf()
+ }
+
+ override fun setBookmarks(type: BookmarkType, ids: List) {
+ bookmarkStore[type] = ids
+ }
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/datasource/token/TokenDataSource.kt b/android/festago/data/src/main/java/com/festago/festago/data/datasource/token/TokenDataSource.kt
new file mode 100644
index 000000000..d61066b05
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/datasource/token/TokenDataSource.kt
@@ -0,0 +1,8 @@
+package com.festago.festago.data.datasource.token
+
+import com.festago.festago.data.model.TokenEntity
+
+interface TokenDataSource {
+ var accessToken: TokenEntity?
+ var refreshToken: TokenEntity?
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/datasource/token/TokenLocalDataSource.kt b/android/festago/data/src/main/java/com/festago/festago/data/datasource/token/TokenLocalDataSource.kt
new file mode 100644
index 000000000..27bffe848
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/datasource/token/TokenLocalDataSource.kt
@@ -0,0 +1,43 @@
+package com.festago.festago.data.datasource.token
+
+import android.content.Context
+import android.content.SharedPreferences
+import androidx.security.crypto.EncryptedSharedPreferences
+import androidx.security.crypto.MasterKey
+import com.festago.festago.data.model.TokenEntity
+import com.festago.festago.data.util.getObject
+import com.festago.festago.data.util.putObject
+import dagger.hilt.android.qualifiers.ApplicationContext
+import javax.inject.Inject
+
+class TokenLocalDataSource @Inject constructor(
+ @ApplicationContext context: Context,
+) : TokenDataSource {
+
+ private val sharedPreference: SharedPreferences by lazy {
+ val masterKeyAlias = MasterKey
+ .Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS)
+ .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
+ .build()
+
+ EncryptedSharedPreferences(context, ENCRYPTED_PREF_FILE, masterKeyAlias)
+ }
+
+ override var accessToken: TokenEntity?
+ get() = sharedPreference.getObject(ACCESS_TOKEN_KEY, null)
+ set(value) {
+ sharedPreference.putObject(ACCESS_TOKEN_KEY, value)
+ }
+
+ override var refreshToken: TokenEntity?
+ get() = sharedPreference.getObject(REFRESH_TOKEN_KEY, null)
+ set(value) {
+ sharedPreference.putObject(REFRESH_TOKEN_KEY, value)
+ }
+
+ companion object {
+ private const val ENCRYPTED_PREF_FILE = "encrypted_pref_file"
+ private const val ACCESS_TOKEN_KEY = "access_token_key"
+ private const val REFRESH_TOKEN_KEY = "refresh_token_key"
+ }
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/datasource/userinfo/UserInfoDataSource.kt b/android/festago/data/src/main/java/com/festago/festago/data/datasource/userinfo/UserInfoDataSource.kt
new file mode 100644
index 000000000..dc2f0dc2e
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/datasource/userinfo/UserInfoDataSource.kt
@@ -0,0 +1,7 @@
+package com.festago.festago.data.datasource.userinfo
+
+import com.festago.festago.data.model.UserInfoEntity
+
+interface UserInfoDataSource {
+ var userInfo: UserInfoEntity?
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/datasource/userinfo/UserInfoLocalDataSource.kt b/android/festago/data/src/main/java/com/festago/festago/data/datasource/userinfo/UserInfoLocalDataSource.kt
new file mode 100644
index 000000000..2a9b3d9bb
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/datasource/userinfo/UserInfoLocalDataSource.kt
@@ -0,0 +1,29 @@
+package com.festago.festago.data.datasource.userinfo
+
+import android.content.Context
+import android.content.SharedPreferences
+import com.festago.festago.data.model.UserInfoEntity
+import com.festago.festago.data.util.getObject
+import com.festago.festago.data.util.putObject
+import dagger.hilt.android.qualifiers.ApplicationContext
+import javax.inject.Inject
+
+class UserInfoLocalDataSource @Inject constructor(
+ @ApplicationContext context: Context,
+) : UserInfoDataSource {
+
+ private val sharedPreference: SharedPreferences by lazy {
+ context.getSharedPreferences(USER_INFO_PREF, Context.MODE_PRIVATE)
+ }
+
+ override var userInfo: UserInfoEntity?
+ get() = sharedPreference.getObject(USER_ID_KEY, null)
+ set(value) {
+ sharedPreference.putObject(USER_ID_KEY, value)
+ }
+
+ companion object {
+ private const val USER_INFO_PREF = "user_info_pref"
+ private const val USER_ID_KEY = "user_info_key"
+ }
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/di/singletonscope/ApiModule.kt b/android/festago/data/src/main/java/com/festago/festago/data/di/singletonscope/ApiModule.kt
new file mode 100644
index 000000000..ffe5fcb73
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/di/singletonscope/ApiModule.kt
@@ -0,0 +1,82 @@
+package com.festago.festago.data.di.singletonscope
+
+import com.festago.festago.data.BuildConfig
+import com.festago.festago.data.retrofit.AuthInterceptor
+import com.festago.festago.domain.repository.UserRepository
+import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
+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.Qualifier
+import javax.inject.Singleton
+
+@Qualifier
+@Retention(AnnotationRetention.BINARY)
+annotation class NormalRetrofitQualifier
+
+@Qualifier
+@Retention(AnnotationRetention.BINARY)
+annotation class BaseUrlQualifier
+
+@Qualifier
+@Retention(AnnotationRetention.BINARY)
+annotation class AuthRetrofitQualifier
+
+@Qualifier
+@Retention(AnnotationRetention.BINARY)
+annotation class AuthOkHttpClientQualifier
+
+@InstallIn(SingletonComponent::class)
+@Module
+object ApiModule {
+ @Provides
+ @Singleton
+ fun provideRetrofitConverterFactory(): retrofit2.Converter.Factory {
+ val json = Json {
+ ignoreUnknownKeys = true
+ }
+ return json.asConverterFactory("application/json".toMediaType())
+ }
+
+ @Provides
+ @Singleton
+ @AuthOkHttpClientQualifier
+ fun provideOkHttpClient(userRepository: UserRepository): OkHttpClient = OkHttpClient
+ .Builder()
+ .addInterceptor(AuthInterceptor(userRepository))
+ .build()
+
+ @Provides
+ @Singleton
+ @NormalRetrofitQualifier
+ fun providesNormalRetrofit(
+ @BaseUrlQualifier baseUrl: String,
+ converterFactory: retrofit2.Converter.Factory,
+ ): Retrofit = Retrofit.Builder()
+ .baseUrl(baseUrl)
+ .addConverterFactory(converterFactory)
+ .build()
+
+ @Provides
+ @Singleton
+ @AuthRetrofitQualifier
+ fun providesAuthRetrofit(
+ @BaseUrlQualifier baseUrl: String,
+ @AuthOkHttpClientQualifier okHttpClient: OkHttpClient,
+ converterFactory: retrofit2.Converter.Factory,
+ ): Retrofit = Retrofit.Builder()
+ .baseUrl(baseUrl)
+ .client(okHttpClient)
+ .addConverterFactory(converterFactory)
+ .build()
+
+ @Provides
+ @Singleton
+ @BaseUrlQualifier
+ fun providesBaseUrl(): String = BuildConfig.BASE_URL
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/di/singletonscope/DaosModule.kt b/android/festago/data/src/main/java/com/festago/festago/data/di/singletonscope/DaosModule.kt
new file mode 100644
index 000000000..6ad5ca163
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/di/singletonscope/DaosModule.kt
@@ -0,0 +1,17 @@
+package com.festago.festago.data.di.singletonscope
+
+import com.festago.festago.data.dao.RecentSearchQueryDao
+import com.festago.festago.data.database.FestagoDatabase
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+
+@Module
+@InstallIn(SingletonComponent::class)
+object DaosModule {
+
+ @Provides
+ fun providesRecentSearchQueryDao(database: FestagoDatabase): RecentSearchQueryDao =
+ database.recentSearchQueryDao()
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/di/singletonscope/DataSourceModule.kt b/android/festago/data/src/main/java/com/festago/festago/data/di/singletonscope/DataSourceModule.kt
new file mode 100644
index 000000000..20646927a
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/di/singletonscope/DataSourceModule.kt
@@ -0,0 +1,30 @@
+package com.festago.festago.data.di.singletonscope
+
+import com.festago.festago.data.datasource.bookmark.BookmarkDataSource
+import com.festago.festago.data.datasource.bookmark.DefaultBookMarkDataSource
+import com.festago.festago.data.datasource.token.TokenDataSource
+import com.festago.festago.data.datasource.token.TokenLocalDataSource
+import com.festago.festago.data.datasource.userinfo.UserInfoDataSource
+import com.festago.festago.data.datasource.userinfo.UserInfoLocalDataSource
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+interface DataSourceModule {
+
+ @Binds
+ @Singleton
+ fun bindsTokenDataSource(tokenDataSource: TokenLocalDataSource): TokenDataSource
+
+ @Binds
+ @Singleton
+ fun bindsUserInfoDataSource(userInfoDataSource: UserInfoLocalDataSource): UserInfoDataSource
+
+ @Binds
+ @Singleton
+ fun bindBookmarkDataSource(bookmarkDataSource: DefaultBookMarkDataSource): BookmarkDataSource
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/di/singletonscope/DatabaseModule.kt b/android/festago/data/src/main/java/com/festago/festago/data/di/singletonscope/DatabaseModule.kt
new file mode 100644
index 000000000..2c4e9c218
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/di/singletonscope/DatabaseModule.kt
@@ -0,0 +1,25 @@
+package com.festago.festago.data.di.singletonscope
+
+import android.content.Context
+import androidx.room.Room
+import com.festago.festago.data.database.FestagoDatabase
+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)
+object DatabaseModule {
+ @Provides
+ @Singleton
+ fun providesFestagoDatabase(
+ @ApplicationContext context: Context,
+ ): FestagoDatabase = Room.databaseBuilder(
+ context,
+ FestagoDatabase::class.java,
+ "festago-database",
+ ).build()
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/di/singletonscope/RepositoryModule.kt b/android/festago/data/src/main/java/com/festago/festago/data/di/singletonscope/RepositoryModule.kt
new file mode 100644
index 000000000..4d316faf7
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/di/singletonscope/RepositoryModule.kt
@@ -0,0 +1,53 @@
+package com.festago.festago.data.di.singletonscope
+
+import com.festago.festago.data.repository.DefaultArtistRepository
+import com.festago.festago.data.repository.DefaultBookmarkRepository
+import com.festago.festago.data.repository.DefaultFestivalRepository
+import com.festago.festago.data.repository.DefaultRecentSearchRepository
+import com.festago.festago.data.repository.DefaultSchoolRepository
+import com.festago.festago.data.repository.DefaultSearchRepository
+import com.festago.festago.data.repository.DefaultUserRepository
+import com.festago.festago.domain.repository.ArtistRepository
+import com.festago.festago.domain.repository.BookmarkRepository
+import com.festago.festago.domain.repository.FestivalRepository
+import com.festago.festago.domain.repository.RecentSearchRepository
+import com.festago.festago.domain.repository.SchoolRepository
+import com.festago.festago.domain.repository.SearchRepository
+import com.festago.festago.domain.repository.UserRepository
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@InstallIn(SingletonComponent::class)
+@Module
+interface RepositoryModule {
+ @Binds
+ @Singleton
+ fun bindsFestivalRepository(festivalRepository: DefaultFestivalRepository): FestivalRepository
+
+ @Binds
+ @Singleton
+ fun bindsArtistRepository(artistRepository: DefaultArtistRepository): ArtistRepository
+
+ @Binds
+ @Singleton
+ fun bindsSchoolRepository(schoolRepository: DefaultSchoolRepository): SchoolRepository
+
+ @Binds
+ @Singleton
+ fun bindsRecentSearchRepository(recentSearchRepository: DefaultRecentSearchRepository): RecentSearchRepository
+
+ @Binds
+ @Singleton
+ fun bindsSearchRepository(searchRepository: DefaultSearchRepository): SearchRepository
+
+ @Binds
+ @Singleton
+ fun bindsBookmarkRepository(bookmarkRepository: DefaultBookmarkRepository): BookmarkRepository
+
+ @Binds
+ @Singleton
+ fun bindsUserRepository(userRepository: DefaultUserRepository): UserRepository
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/di/singletonscope/ServiceModule.kt b/android/festago/data/src/main/java/com/festago/festago/data/di/singletonscope/ServiceModule.kt
new file mode 100644
index 000000000..a3414d88d
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/di/singletonscope/ServiceModule.kt
@@ -0,0 +1,66 @@
+package com.festago.festago.data.di.singletonscope
+
+import com.festago.festago.data.service.ArtistRetrofitService
+import com.festago.festago.data.service.AuthRetrofitService
+import com.festago.festago.data.service.BookmarkRetrofitService
+import com.festago.festago.data.service.FestivalRetrofitService
+import com.festago.festago.data.service.SchoolRetrofitService
+import com.festago.festago.data.service.SearchRetrofitService
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import retrofit2.Retrofit
+import javax.inject.Singleton
+
+@InstallIn(SingletonComponent::class)
+@Module
+object ServiceModule {
+ @Provides
+ @Singleton
+ fun providesFestivalRetrofitService(
+ @NormalRetrofitQualifier retrofit: Retrofit,
+ ): FestivalRetrofitService {
+ return retrofit.create(FestivalRetrofitService::class.java)
+ }
+
+ @Provides
+ @Singleton
+ fun providesArtistRetrofitService(
+ @NormalRetrofitQualifier retrofit: Retrofit,
+ ): ArtistRetrofitService {
+ return retrofit.create(ArtistRetrofitService::class.java)
+ }
+
+ @Provides
+ @Singleton
+ fun providesSchoolRetrofitService(
+ @NormalRetrofitQualifier retrofit: Retrofit,
+ ): SchoolRetrofitService {
+ return retrofit.create(SchoolRetrofitService::class.java)
+ }
+
+ @Provides
+ @Singleton
+ fun providesSearchRetrofitService(
+ @NormalRetrofitQualifier retrofit: Retrofit,
+ ): SearchRetrofitService {
+ return retrofit.create(SearchRetrofitService::class.java)
+ }
+
+ @Provides
+ @Singleton
+ fun providesBookmarkRetrofitService(
+ @AuthRetrofitQualifier retrofit: Retrofit,
+ ): BookmarkRetrofitService {
+ return retrofit.create(BookmarkRetrofitService::class.java)
+ }
+
+ @Provides
+ @Singleton
+ fun providesAuthRetrofitService(
+ @NormalRetrofitQualifier retrofit: Retrofit,
+ ): AuthRetrofitService {
+ return retrofit.create(AuthRetrofitService::class.java)
+ }
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/dto/artist/ArtistDetailResponse.kt b/android/festago/data/src/main/java/com/festago/festago/data/dto/artist/ArtistDetailResponse.kt
new file mode 100644
index 000000000..3025956a9
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/dto/artist/ArtistDetailResponse.kt
@@ -0,0 +1,22 @@
+package com.festago.festago.data.dto.artist
+
+import com.festago.festago.data.dto.school.SocialMediaResponse
+import com.festago.festago.domain.model.artist.ArtistDetail
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ArtistDetailResponse(
+ val id: Int,
+ val name: String?,
+ val profileImageUrl: String?,
+ val backgroundImageUrl: String?,
+ val socialMedias: List,
+) {
+ fun toDomain() = ArtistDetail(
+ id = id,
+ artistName = name ?: "",
+ profileUrl = profileImageUrl ?: "",
+ backgroundUrl = backgroundImageUrl ?: "",
+ artistMedia = socialMedias.map { it.toDomain() },
+ )
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/dto/artist/ArtistResponse.kt b/android/festago/data/src/main/java/com/festago/festago/data/dto/artist/ArtistResponse.kt
new file mode 100644
index 000000000..f87a1026d
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/dto/artist/ArtistResponse.kt
@@ -0,0 +1,17 @@
+package com.festago.festago.data.dto.artist
+
+import com.festago.festago.domain.model.artist.Artist
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ArtistResponse(
+ val id: Long,
+ val name: String,
+ val profileImageUrl: String,
+) {
+ fun toDomain() = Artist(
+ id = id,
+ name = name,
+ imageUrl = profileImageUrl,
+ )
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/dto/artist/ArtistSearchResponse.kt b/android/festago/data/src/main/java/com/festago/festago/data/dto/artist/ArtistSearchResponse.kt
new file mode 100644
index 000000000..99e4db4fb
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/dto/artist/ArtistSearchResponse.kt
@@ -0,0 +1,21 @@
+package com.festago.festago.data.dto.artist
+
+import com.festago.festago.domain.model.search.ArtistSearch
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ArtistSearchResponse(
+ val id: Long,
+ val name: String,
+ val profileImageUrl: String,
+ val todayStage: Int,
+ val plannedStage: Int,
+) {
+ fun toDomain() = ArtistSearch(
+ id = id,
+ name = name,
+ profileImageUrl = profileImageUrl,
+ todayStage = todayStage,
+ upcomingStage = plannedStage,
+ )
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/dto/bookmark/ArtistBookmarkInfoResponse.kt b/android/festago/data/src/main/java/com/festago/festago/data/dto/bookmark/ArtistBookmarkInfoResponse.kt
new file mode 100644
index 000000000..f2fafb515
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/dto/bookmark/ArtistBookmarkInfoResponse.kt
@@ -0,0 +1,17 @@
+package com.festago.festago.data.dto.bookmark
+
+import com.festago.festago.domain.model.bookmark.ArtistBookmarkInfo
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ArtistBookmarkInfoResponse(
+ val id: Long,
+ val name: String,
+ val profileImageUrl: String,
+) {
+ fun toDomain() = ArtistBookmarkInfo(
+ id = id,
+ name = name,
+ profileImageUrl = profileImageUrl,
+ )
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/dto/bookmark/ArtistBookmarkResponse.kt b/android/festago/data/src/main/java/com/festago/festago/data/dto/bookmark/ArtistBookmarkResponse.kt
new file mode 100644
index 000000000..6c9673cb4
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/dto/bookmark/ArtistBookmarkResponse.kt
@@ -0,0 +1,16 @@
+package com.festago.festago.data.dto.bookmark
+
+import com.festago.festago.domain.model.bookmark.ArtistBookmark
+import kotlinx.serialization.Serializable
+import java.time.LocalDateTime
+
+@Serializable
+data class ArtistBookmarkResponse(
+ val artist: ArtistBookmarkInfoResponse,
+ val createdAt: String,
+) {
+ fun toDomain() = ArtistBookmark(
+ artist = artist.toDomain(),
+ createdAt = LocalDateTime.parse(createdAt),
+ )
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/dto/bookmark/FestivalBookmarkResponse.kt b/android/festago/data/src/main/java/com/festago/festago/data/dto/bookmark/FestivalBookmarkResponse.kt
new file mode 100644
index 000000000..c872ec11a
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/dto/bookmark/FestivalBookmarkResponse.kt
@@ -0,0 +1,17 @@
+package com.festago.festago.data.dto.bookmark
+
+import com.festago.festago.data.dto.festival.FestivalResponse
+import com.festago.festago.domain.model.bookmark.FestivalBookmark
+import kotlinx.serialization.Serializable
+import java.time.LocalDateTime
+
+@Serializable
+class FestivalBookmarkResponse(
+ val festival: FestivalResponse,
+ val createdAt: String,
+) {
+ fun toDomain() = FestivalBookmark(
+ festival.toDomain(),
+ createdAt = LocalDateTime.parse(createdAt),
+ )
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/dto/bookmark/SchoolBookmarkInfoResponse.kt b/android/festago/data/src/main/java/com/festago/festago/data/dto/bookmark/SchoolBookmarkInfoResponse.kt
new file mode 100644
index 000000000..ee9f7686a
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/dto/bookmark/SchoolBookmarkInfoResponse.kt
@@ -0,0 +1,17 @@
+package com.festago.festago.data.dto.bookmark
+
+import com.festago.festago.domain.model.bookmark.SchoolBookmarkInfo
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class SchoolBookmarkInfoResponse(
+ val id: Long,
+ val name: String,
+ val logoUrl: String,
+) {
+ fun toDomain() = SchoolBookmarkInfo(
+ id = id,
+ name = name,
+ logoUrl = logoUrl,
+ )
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/dto/bookmark/SchoolBookmarkResponse.kt b/android/festago/data/src/main/java/com/festago/festago/data/dto/bookmark/SchoolBookmarkResponse.kt
new file mode 100644
index 000000000..fc1233760
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/dto/bookmark/SchoolBookmarkResponse.kt
@@ -0,0 +1,16 @@
+package com.festago.festago.data.dto.bookmark
+
+import com.festago.festago.domain.model.bookmark.SchoolBookmark
+import kotlinx.serialization.Serializable
+import java.time.LocalDateTime
+
+@Serializable
+data class SchoolBookmarkResponse(
+ val school: SchoolBookmarkInfoResponse,
+ val createdAt: String,
+) {
+ fun toDomain() = SchoolBookmark(
+ school = school.toDomain(),
+ createdAt = LocalDateTime.parse(createdAt),
+ )
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/dto/festival/FestivalDetailResponse.kt b/android/festago/data/src/main/java/com/festago/festago/data/dto/festival/FestivalDetailResponse.kt
new file mode 100644
index 000000000..9d8c55da7
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/dto/festival/FestivalDetailResponse.kt
@@ -0,0 +1,31 @@
+package com.festago.festago.data.dto.festival
+
+import com.festago.festago.data.dto.school.SchoolResponse
+import com.festago.festago.data.dto.school.SocialMediaResponse
+import com.festago.festago.data.dto.stage.StageResponse
+import com.festago.festago.domain.model.festival.FestivalDetail
+import kotlinx.serialization.Serializable
+import java.time.LocalDate
+
+@Serializable
+data class FestivalDetailResponse(
+ val id: Long,
+ val name: String,
+ val startDate: String,
+ val endDate: String,
+ val posterImageUrl: String,
+ val school: SchoolResponse,
+ val socialMedias: List,
+ val stages: List,
+) {
+ fun toDomain() = FestivalDetail(
+ id,
+ name,
+ LocalDate.parse(startDate),
+ LocalDate.parse(endDate),
+ posterImageUrl,
+ school.toDomain(),
+ socialMedias.map { it.toDomain() },
+ stages.map { it.toDomain() },
+ )
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/dto/festival/FestivalResponse.kt b/android/festago/data/src/main/java/com/festago/festago/data/dto/festival/FestivalResponse.kt
new file mode 100644
index 000000000..65ca92527
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/dto/festival/FestivalResponse.kt
@@ -0,0 +1,28 @@
+package com.festago.festago.data.dto.festival
+
+import com.festago.festago.data.dto.artist.ArtistResponse
+import com.festago.festago.data.dto.school.SchoolResponse
+import com.festago.festago.domain.model.festival.Festival
+import kotlinx.serialization.Serializable
+import java.time.LocalDate
+
+@Serializable
+data class FestivalResponse(
+ val id: Long,
+ val name: String,
+ val startDate: String,
+ val endDate: String,
+ val posterImageUrl: String,
+ val school: SchoolResponse? = null,
+ val artists: List,
+) {
+ fun toDomain(): Festival = Festival(
+ id = id,
+ name = name,
+ startDate = LocalDate.parse(startDate),
+ endDate = LocalDate.parse(endDate),
+ imageUrl = posterImageUrl,
+ school = school?.toDomain(),
+ artists = artists.map { it.toDomain() },
+ )
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/dto/festival/FestivalSearchResponse.kt b/android/festago/data/src/main/java/com/festago/festago/data/dto/festival/FestivalSearchResponse.kt
new file mode 100644
index 000000000..acb3a9f52
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/dto/festival/FestivalSearchResponse.kt
@@ -0,0 +1,25 @@
+package com.festago.festago.data.dto.festival
+
+import com.festago.festago.data.dto.artist.ArtistResponse
+import com.festago.festago.domain.model.search.FestivalSearch
+import kotlinx.serialization.Serializable
+import java.time.LocalDate
+
+@Serializable
+data class FestivalSearchResponse(
+ val id: Long,
+ val name: String,
+ val startDate: String,
+ val endDate: String,
+ val posterImageUrl: String,
+ val artists: List,
+) {
+ fun toDomain(): FestivalSearch = FestivalSearch(
+ id = id,
+ name = name,
+ startDate = LocalDate.parse(startDate),
+ endDate = LocalDate.parse(endDate),
+ imageUrl = posterImageUrl,
+ artists = artists.map { it.toDomain() },
+ )
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/dto/festival/FestivalsResponse.kt b/android/festago/data/src/main/java/com/festago/festago/data/dto/festival/FestivalsResponse.kt
new file mode 100644
index 000000000..c382209ef
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/dto/festival/FestivalsResponse.kt
@@ -0,0 +1,15 @@
+package com.festago.festago.data.dto.festival
+
+import com.festago.festago.domain.model.festival.FestivalsPage
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class FestivalsResponse(
+ val last: Boolean,
+ val content: List,
+) {
+ fun toDomain() = FestivalsPage(
+ isLastPage = last,
+ festivals = content.map { it.toDomain() },
+ )
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/dto/festival/PopularFestivalsResponse.kt b/android/festago/data/src/main/java/com/festago/festago/data/dto/festival/PopularFestivalsResponse.kt
new file mode 100644
index 000000000..10656ba9a
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/dto/festival/PopularFestivalsResponse.kt
@@ -0,0 +1,15 @@
+package com.festago.festago.data.dto.festival
+
+import com.festago.festago.domain.model.festival.PopularFestivals
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class PopularFestivalsResponse(
+ val title: String,
+ val content: List,
+) {
+ fun toDomain() = PopularFestivals(
+ title = title,
+ festivals = content.map { it.toDomain() },
+ )
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/dto/school/SchoolResponse.kt b/android/festago/data/src/main/java/com/festago/festago/data/dto/school/SchoolResponse.kt
new file mode 100644
index 000000000..70d309900
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/dto/school/SchoolResponse.kt
@@ -0,0 +1,17 @@
+package com.festago.festago.data.dto.school
+
+import com.festago.festago.domain.model.school.School
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class SchoolResponse(
+ val id: Long,
+ val name: String,
+ val profileImageUrl: String = "",
+) {
+ fun toDomain() = School(
+ id = id,
+ name = name,
+ imageUrl = profileImageUrl,
+ )
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/dto/school/SchoolSearchResponse.kt b/android/festago/data/src/main/java/com/festago/festago/data/dto/school/SchoolSearchResponse.kt
new file mode 100644
index 000000000..a8ab759a7
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/dto/school/SchoolSearchResponse.kt
@@ -0,0 +1,20 @@
+package com.festago.festago.data.dto.school
+
+import com.festago.festago.domain.model.search.SchoolSearch
+import kotlinx.serialization.Serializable
+import java.time.LocalDate
+
+@Serializable
+data class SchoolSearchResponse(
+ val id: Long,
+ val name: String,
+ val logoUrl: String,
+ val upcomingFestivalStartDate: String?,
+) {
+ fun toDomain() = SchoolSearch(
+ id = id,
+ name = name,
+ logoUrl = logoUrl,
+ upcomingFestivalStartDate = upcomingFestivalStartDate?.let { LocalDate.parse(it) },
+ )
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/dto/school/SocialMediaResponse.kt b/android/festago/data/src/main/java/com/festago/festago/data/dto/school/SocialMediaResponse.kt
new file mode 100644
index 000000000..1fb0650fb
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/dto/school/SocialMediaResponse.kt
@@ -0,0 +1,25 @@
+package com.festago.festago.data.dto.school
+
+import com.festago.festago.domain.model.social.SocialMedia
+import com.festago.festago.domain.model.social.SocialMediaType
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class SocialMediaResponse(
+ val type: String,
+ val name: String,
+ val logoUrl: String,
+ val url: String,
+) {
+ fun toDomain(): SocialMedia {
+ val type = when (this.type) {
+ "FACEBOOK" -> SocialMediaType.FACEBOOK
+ "INSTAGRAM" -> SocialMediaType.INSTAGRAM
+ "YOUTUBE" -> SocialMediaType.YOUTUBE
+ "X" -> SocialMediaType.X
+ else -> SocialMediaType.NONE
+ }
+
+ return SocialMedia(type = type, name = name, logoUrl = logoUrl, url = url)
+ }
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/dto/schooldetail/SchoolFestivalArtistResponse.kt b/android/festago/data/src/main/java/com/festago/festago/data/dto/schooldetail/SchoolFestivalArtistResponse.kt
new file mode 100644
index 000000000..60c187114
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/dto/schooldetail/SchoolFestivalArtistResponse.kt
@@ -0,0 +1,17 @@
+package com.festago.festago.data.dto.schooldetail
+
+import com.festago.festago.domain.model.artist.Artist
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class SchoolFestivalArtistResponse(
+ val id: Int,
+ val name: String,
+ val profileImageUrl: String,
+) {
+ fun toDomain() = Artist(
+ id = id.toLong(),
+ name = name,
+ imageUrl = profileImageUrl,
+ )
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/dto/schooldetail/SchoolFestivalResponse.kt b/android/festago/data/src/main/java/com/festago/festago/data/dto/schooldetail/SchoolFestivalResponse.kt
new file mode 100644
index 000000000..aed20cd2d
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/dto/schooldetail/SchoolFestivalResponse.kt
@@ -0,0 +1,30 @@
+package com.festago.festago.data.dto.schooldetail
+
+import com.festago.festago.domain.model.festival.Festival
+import com.festago.festago.domain.model.school.School
+import kotlinx.serialization.Serializable
+import java.time.LocalDate
+
+@Serializable
+data class SchoolFestivalResponse(
+ val id: Int,
+ val name: String,
+ val startDate: String,
+ val endDate: String,
+ val posterImageUrl: String,
+ val artists: List
+) {
+ fun toDomain() = Festival(
+ id = id.toLong(),
+ name = name,
+ startDate = LocalDate.parse(startDate),
+ endDate = LocalDate.parse(endDate),
+ imageUrl = posterImageUrl,
+ school = School(
+ id = -1,
+ name = "",
+ imageUrl = ""
+ ),
+ artists = artists.map { it.toDomain() }
+ )
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/dto/schooldetail/SchoolFestivalsResponse.kt b/android/festago/data/src/main/java/com/festago/festago/data/dto/schooldetail/SchoolFestivalsResponse.kt
new file mode 100644
index 000000000..f265f943b
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/dto/schooldetail/SchoolFestivalsResponse.kt
@@ -0,0 +1,15 @@
+package com.festago.festago.data.dto.schooldetail
+
+import com.festago.festago.domain.model.festival.FestivalsPage
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class SchoolFestivalsResponse(
+ val last: Boolean,
+ val content: List
+) {
+ fun toDomain() = FestivalsPage(
+ isLastPage = last,
+ festivals = content.map { it.toDomain() },
+ )
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/dto/schooldetail/SchoolInfoResponse.kt b/android/festago/data/src/main/java/com/festago/festago/data/dto/schooldetail/SchoolInfoResponse.kt
new file mode 100644
index 000000000..1f0d061be
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/dto/schooldetail/SchoolInfoResponse.kt
@@ -0,0 +1,22 @@
+package com.festago.festago.data.dto.schooldetail
+
+import com.festago.festago.data.dto.school.SocialMediaResponse
+import com.festago.festago.domain.model.school.SchoolInfo
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class SchoolInfoResponse(
+ val id: Int,
+ val name: String?,
+ val logoUrl: String?,
+ val backgroundImageUrl: String?,
+ val socialMedias: List,
+) {
+ fun toDomain(): SchoolInfo = SchoolInfo(
+ id = id,
+ schoolName = name ?: "",
+ logoUrl = logoUrl ?: "",
+ backgroundUrl = backgroundImageUrl ?: "",
+ socialMedia = socialMedias.map { it.toDomain() }
+ )
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/dto/stage/StageResponse.kt b/android/festago/data/src/main/java/com/festago/festago/data/dto/stage/StageResponse.kt
new file mode 100644
index 000000000..f3fc230bb
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/dto/stage/StageResponse.kt
@@ -0,0 +1,19 @@
+package com.festago.festago.data.dto.stage
+
+import com.festago.festago.data.dto.artist.ArtistResponse
+import com.festago.festago.domain.model.stage.Stage
+import kotlinx.serialization.Serializable
+import java.time.LocalDateTime
+
+@Serializable
+data class StageResponse(
+ val id: Long,
+ val startDateTime: String,
+ val artists: List,
+) {
+ fun toDomain() = Stage(
+ id = id,
+ startDateTime = LocalDateTime.parse(startDateTime),
+ artists = artists.map { it.toDomain() },
+ )
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/dto/user/RefreshRequest.kt b/android/festago/data/src/main/java/com/festago/festago/data/dto/user/RefreshRequest.kt
new file mode 100644
index 000000000..7987ec816
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/dto/user/RefreshRequest.kt
@@ -0,0 +1,8 @@
+package com.festago.festago.data.dto.user
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+class RefreshRequest(
+ val refreshToken: String,
+)
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/dto/user/RefreshResponse.kt b/android/festago/data/src/main/java/com/festago/festago/data/dto/user/RefreshResponse.kt
new file mode 100644
index 000000000..99415e01c
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/dto/user/RefreshResponse.kt
@@ -0,0 +1,9 @@
+package com.festago.festago.data.dto.user
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class RefreshResponse(
+ val accessToken: TokenResponse,
+ val refreshToken: TokenResponse,
+)
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/dto/user/SignInRequest.kt b/android/festago/data/src/main/java/com/festago/festago/data/dto/user/SignInRequest.kt
new file mode 100644
index 000000000..3bfa725a5
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/dto/user/SignInRequest.kt
@@ -0,0 +1,9 @@
+package com.festago.festago.data.dto.user
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+class SignInRequest(
+ val socialType: String,
+ val idToken: String,
+)
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/dto/user/SignInResponse.kt b/android/festago/data/src/main/java/com/festago/festago/data/dto/user/SignInResponse.kt
new file mode 100644
index 000000000..dfdf7b582
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/dto/user/SignInResponse.kt
@@ -0,0 +1,11 @@
+package com.festago.festago.data.dto.user
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class SignInResponse(
+ val nickname: String,
+ val profileImageUrl: String,
+ val accessToken: TokenResponse,
+ val refreshToken: TokenResponse,
+)
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/dto/user/SignOutRequest.kt b/android/festago/data/src/main/java/com/festago/festago/data/dto/user/SignOutRequest.kt
new file mode 100644
index 000000000..79b7c9979
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/dto/user/SignOutRequest.kt
@@ -0,0 +1,8 @@
+package com.festago.festago.data.dto.user
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class SignOutRequest(
+ val refreshToken: String,
+)
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/dto/user/TokenResponse.kt b/android/festago/data/src/main/java/com/festago/festago/data/dto/user/TokenResponse.kt
new file mode 100644
index 000000000..b02033d36
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/dto/user/TokenResponse.kt
@@ -0,0 +1,22 @@
+package com.festago.festago.data.dto.user
+
+import com.festago.festago.data.model.TokenEntity
+import com.festago.festago.domain.model.user.Token
+import kotlinx.serialization.Serializable
+import java.time.LocalDateTime
+
+@Serializable
+data class TokenResponse(
+ val token: String,
+ val expiredAt: String,
+) {
+ fun toDomain() = Token(
+ token = token,
+ expiredAt = LocalDateTime.parse(expiredAt),
+ )
+
+ fun toEntity() = TokenEntity(
+ token = token,
+ expiredAt = expiredAt,
+ )
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/model/RecentSearchQueryEntity.kt b/android/festago/data/src/main/java/com/festago/festago/data/model/RecentSearchQueryEntity.kt
new file mode 100644
index 000000000..2076f4bbe
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/model/RecentSearchQueryEntity.kt
@@ -0,0 +1,18 @@
+package com.festago.festago.data.model
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import com.festago.festago.domain.model.recentsearch.RecentSearchQuery
+
+@Entity(
+ tableName = "recentSearchQueries",
+)
+data class RecentSearchQueryEntity(
+ @PrimaryKey
+ val query: String,
+ @ColumnInfo(name = "created_at")
+ val createdAt: Long,
+) {
+ fun toDomain() = RecentSearchQuery(query = query, queriedDate = createdAt)
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/model/TokenEntity.kt b/android/festago/data/src/main/java/com/festago/festago/data/model/TokenEntity.kt
new file mode 100644
index 000000000..598f2e426
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/model/TokenEntity.kt
@@ -0,0 +1,14 @@
+package com.festago.festago.data.model
+
+import com.festago.festago.domain.model.user.Token
+import java.time.LocalDateTime
+
+data class TokenEntity(
+ val token: String,
+ val expiredAt: String,
+) {
+ fun toDomain() = Token(
+ token = token,
+ expiredAt = LocalDateTime.parse(expiredAt),
+ )
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/model/UserInfoEntity.kt b/android/festago/data/src/main/java/com/festago/festago/data/model/UserInfoEntity.kt
new file mode 100644
index 000000000..f1cb00503
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/model/UserInfoEntity.kt
@@ -0,0 +1,13 @@
+package com.festago.festago.data.model
+
+import com.festago.festago.domain.model.user.UserInfo
+
+data class UserInfoEntity(
+ val nickname: String,
+ val profileImageUrl: String,
+) {
+ fun toDomain() = UserInfo(
+ nickname = nickname,
+ profileImageUrl = profileImageUrl,
+ )
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/repository/DefaultArtistRepository.kt b/android/festago/data/src/main/java/com/festago/festago/data/repository/DefaultArtistRepository.kt
new file mode 100644
index 000000000..4fffcb6b8
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/repository/DefaultArtistRepository.kt
@@ -0,0 +1,40 @@
+package com.festago.festago.data.repository
+
+import com.festago.festago.data.service.ArtistRetrofitService
+import com.festago.festago.data.util.onSuccessOrCatch
+import com.festago.festago.data.util.runCatchingResponse
+import com.festago.festago.domain.model.artist.ArtistDetail
+import com.festago.festago.domain.model.festival.FestivalsPage
+import com.festago.festago.domain.repository.ArtistRepository
+import kotlinx.coroutines.delay
+import java.time.LocalDate
+import javax.inject.Inject
+
+class DefaultArtistRepository @Inject constructor(
+ private val artistRetrofitService: ArtistRetrofitService,
+) : ArtistRepository {
+
+ override suspend fun loadArtistDetail(id: Long, delayTimeMillis: Long): Result {
+ delay(delayTimeMillis)
+ return runCatchingResponse { artistRetrofitService.getArtistDetail(id) }
+ .onSuccessOrCatch { it.toDomain() }
+ }
+
+ override suspend fun loadArtistFestivals(
+ id: Long,
+ size: Int?,
+ lastFestivalId: Long?,
+ lastStartDate: LocalDate?,
+ isPast: Boolean?,
+ ): Result {
+ return runCatchingResponse {
+ artistRetrofitService.getArtistFestivals(
+ artistId = id,
+ size = size,
+ lastFestivalId = lastFestivalId,
+ lastStartDate = lastStartDate,
+ isPast = isPast,
+ )
+ }.onSuccessOrCatch { it.toDomain() }
+ }
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/repository/DefaultBookmarkRepository.kt b/android/festago/data/src/main/java/com/festago/festago/data/repository/DefaultBookmarkRepository.kt
new file mode 100644
index 000000000..5a0ab4d13
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/repository/DefaultBookmarkRepository.kt
@@ -0,0 +1,134 @@
+package com.festago.festago.data.repository
+
+import com.festago.festago.data.datasource.bookmark.BookmarkDataSource
+import com.festago.festago.data.service.BookmarkRetrofitService
+import com.festago.festago.data.util.onSuccessOrCatch
+import com.festago.festago.data.util.runCatchingResponse
+import com.festago.festago.domain.model.bookmark.ArtistBookmark
+import com.festago.festago.domain.model.bookmark.BookmarkType
+import com.festago.festago.domain.model.bookmark.FestivalBookmark
+import com.festago.festago.domain.model.bookmark.FestivalBookmarkOrder
+import com.festago.festago.domain.model.bookmark.SchoolBookmark
+import com.festago.festago.domain.repository.BookmarkRepository
+import javax.inject.Inject
+
+class DefaultBookmarkRepository @Inject constructor(
+ private val bookmarkRetrofitService: BookmarkRetrofitService,
+ private val bookmarkDataSource: BookmarkDataSource,
+) : BookmarkRepository {
+ override suspend fun addFestivalBookmark(festivalId: Long): Result {
+ return runCatchingResponse {
+ bookmarkRetrofitService.addBookmark(festivalId, BookmarkType.FESTIVAL)
+ }.onSuccessOrCatch {
+ bookmarkDataSource.addBookmark(festivalId, BookmarkType.FESTIVAL)
+ }
+ }
+
+ override suspend fun getFestivalBookmarks(
+ festivalIds: List,
+ festivalBookmarkOrder: FestivalBookmarkOrder,
+ ): Result> {
+ return runCatchingResponse {
+ bookmarkRetrofitService.getFestivalBookmarks(
+ festivalIds = festivalIds,
+ festivalBookmarkOrder = festivalBookmarkOrder,
+ )
+ }.onSuccessOrCatch { response ->
+ response.map { festival -> festival.toDomain() }.also { festivalBookmarks ->
+ bookmarkDataSource.setBookmarks(
+ BookmarkType.FESTIVAL,
+ festivalBookmarks.map { it.festival.id },
+ )
+ }
+ }
+ }
+
+ override suspend fun getFestivalBookmarkIds(): Result> {
+ return runCatchingResponse {
+ bookmarkRetrofitService.getFestivalBookmarkIds()
+ }.onSuccessOrCatch { it.also { festivalIds -> storeFestivalBookmarks(festivalIds) } }
+ }
+
+ override suspend fun deleteFestivalBookmark(festivalId: Long): Result {
+ val result = runCatchingResponse {
+ bookmarkRetrofitService.deleteBookmark(festivalId, BookmarkType.FESTIVAL)
+ }
+ result.onSuccess { bookmarkDataSource.deleteBookmark(festivalId, BookmarkType.FESTIVAL) }
+ result.onFailure { if (it.message?.contains("204") == true) return Result.success(Unit) }
+ return result
+ }
+
+ override suspend fun addSchoolBookmark(schoolId: Long): Result {
+ return runCatchingResponse {
+ bookmarkRetrofitService.addBookmark(schoolId, BookmarkType.SCHOOL)
+ }.onSuccessOrCatch {
+ bookmarkDataSource.addBookmark(schoolId, BookmarkType.SCHOOL)
+ }
+ }
+
+ override suspend fun getSchoolBookmarks(): Result> {
+ return runCatchingResponse {
+ bookmarkRetrofitService.getSchoolBookmarks()
+ }.onSuccessOrCatch { response ->
+ response.map { schools -> schools.toDomain() }
+ .also { schoolBookmarks -> storeSchoolBookmarks(schoolBookmarks) }
+ }
+ }
+
+ override suspend fun deleteSchoolBookmark(schoolId: Long): Result {
+ val result = runCatchingResponse {
+ bookmarkRetrofitService.deleteBookmark(schoolId, BookmarkType.SCHOOL)
+ }
+ result.onSuccess { bookmarkDataSource.deleteBookmark(schoolId, BookmarkType.SCHOOL) }
+ result.onFailure { if (it.message?.contains("204") == true) return Result.success(Unit) }
+ return result
+ }
+
+ override suspend fun addArtistBookmark(artistId: Long): Result {
+ return runCatchingResponse {
+ bookmarkRetrofitService.addBookmark(artistId, BookmarkType.ARTIST)
+ }.onSuccessOrCatch { bookmarkDataSource.addBookmark(artistId, BookmarkType.ARTIST) }
+ }
+
+ override suspend fun getArtistBookmarks(): Result> {
+ return runCatchingResponse {
+ bookmarkRetrofitService.getArtistBookmarks()
+ }.onSuccessOrCatch {
+ it.map { artist -> artist.toDomain() }
+ .also { artistBookmarks -> storeArtistBookmarks(artistBookmarks) }
+ }.onFailure {
+ it.printStackTrace()
+ }
+ }
+
+ override suspend fun deleteArtistBookmark(artistId: Long): Result {
+ val result = runCatchingResponse {
+ bookmarkRetrofitService.deleteBookmark(artistId, BookmarkType.ARTIST)
+ }
+ result.onSuccess { bookmarkDataSource.deleteBookmark(artistId, BookmarkType.ARTIST) }
+ result.onFailure { if (it.message?.contains("204") == true) return Result.success(Unit) }
+ return result
+ }
+
+ override fun isBookmarked(id: Long, type: BookmarkType): Boolean {
+ return bookmarkDataSource.isBookmarked(id, type)
+ }
+
+ private fun storeSchoolBookmarks(schoolBookmarks: List) {
+ bookmarkDataSource.setBookmarks(
+ BookmarkType.SCHOOL,
+ schoolBookmarks.map { it.school.id },
+ )
+ }
+
+ private fun storeArtistBookmarks(artistBookmarks: List) {
+ bookmarkDataSource.setBookmarks(
+ BookmarkType.ARTIST,
+ artistBookmarks.map { it.artist.id },
+ )
+ }
+
+ private fun storeFestivalBookmarks(festivalBookmarks: List) {
+ bookmarkDataSource.setBookmarks(BookmarkType.FESTIVAL, festivalBookmarks)
+ }
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/repository/DefaultFestivalRepository.kt b/android/festago/data/src/main/java/com/festago/festago/data/repository/DefaultFestivalRepository.kt
new file mode 100644
index 000000000..4f4902122
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/repository/DefaultFestivalRepository.kt
@@ -0,0 +1,53 @@
+package com.festago.festago.data.repository
+
+import com.festago.festago.data.service.FestivalRetrofitService
+import com.festago.festago.data.util.onSuccessOrCatch
+import com.festago.festago.data.util.runCatchingResponse
+import com.festago.festago.domain.model.festival.FestivalDetail
+import com.festago.festago.domain.model.festival.FestivalFilter
+import com.festago.festago.domain.model.festival.FestivalsPage
+import com.festago.festago.domain.model.festival.PopularFestivals
+import com.festago.festago.domain.model.festival.SchoolRegion
+import com.festago.festago.domain.repository.FestivalRepository
+import kotlinx.coroutines.delay
+import java.time.LocalDate
+import javax.inject.Inject
+
+class DefaultFestivalRepository @Inject constructor(
+ private val festivalRetrofitService: FestivalRetrofitService,
+) : FestivalRepository {
+
+ override suspend fun loadPopularFestivals(): Result {
+ return runCatchingResponse {
+ festivalRetrofitService.getPopularFestivals()
+ }.onSuccessOrCatch { it.toDomain() }
+ }
+
+ override suspend fun loadFestivals(
+ schoolRegion: SchoolRegion?,
+ festivalFilter: FestivalFilter?,
+ lastFestivalId: Long?,
+ lastStartDate: LocalDate?,
+ size: Int?,
+ ): Result {
+ return runCatchingResponse {
+ festivalRetrofitService.getFestivals(
+ region = schoolRegion?.name,
+ filter = festivalFilter?.name,
+ lastFestivalId = lastFestivalId,
+ lastStartDate = lastStartDate,
+ size = size,
+ )
+ }.onSuccessOrCatch { it.toDomain() }
+ }
+
+ override suspend fun loadFestivalDetail(
+ id: Long,
+ delayTimeMillis: Long,
+ ): Result {
+ delay(delayTimeMillis)
+ return runCatchingResponse {
+ festivalRetrofitService.getFestivalDetail(id)
+ }.onSuccessOrCatch { it.toDomain() }
+ }
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/repository/DefaultRecentSearchRepository.kt b/android/festago/data/src/main/java/com/festago/festago/data/repository/DefaultRecentSearchRepository.kt
new file mode 100644
index 000000000..f6506e01c
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/repository/DefaultRecentSearchRepository.kt
@@ -0,0 +1,39 @@
+package com.festago.festago.data.repository
+
+import com.festago.festago.data.dao.RecentSearchQueryDao
+import com.festago.festago.data.model.RecentSearchQueryEntity
+import com.festago.festago.domain.model.recentsearch.RecentSearchQuery
+import com.festago.festago.domain.repository.RecentSearchRepository
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+
+class DefaultRecentSearchRepository @Inject constructor(
+ private val recentSearchQueryDao: RecentSearchQueryDao,
+) : RecentSearchRepository {
+
+ override suspend fun insertOrReplaceRecentSearch(searchQuery: String) {
+ recentSearchQueryDao.insertOrReplaceRecentSearchQuery(
+ RecentSearchQueryEntity(
+ query = searchQuery,
+ createdAt = System.currentTimeMillis(),
+ ),
+ )
+ }
+
+ override suspend fun deleteRecentSearch(searchQuery: String) {
+ recentSearchQueryDao.deleteRecentSearchQuery(
+ RecentSearchQueryEntity(
+ query = searchQuery,
+ createdAt = System.currentTimeMillis(),
+ ),
+ )
+ }
+
+ override suspend fun getRecentSearchQueries(limit: Int): List {
+ return recentSearchQueryDao.getRecentSearchQueryEntities(limit).map { recentSearchQueries ->
+ recentSearchQueries.toDomain()
+ }
+ }
+
+ override suspend fun clearRecentSearches() = recentSearchQueryDao.clearRecentSearchQueries()
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/repository/DefaultSchoolRepository.kt b/android/festago/data/src/main/java/com/festago/festago/data/repository/DefaultSchoolRepository.kt
new file mode 100644
index 000000000..0ee26cf46
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/repository/DefaultSchoolRepository.kt
@@ -0,0 +1,40 @@
+package com.festago.festago.data.repository
+
+import com.festago.festago.data.service.SchoolRetrofitService
+import com.festago.festago.data.util.onSuccessOrCatch
+import com.festago.festago.data.util.runCatchingResponse
+import com.festago.festago.domain.model.festival.FestivalsPage
+import com.festago.festago.domain.model.school.SchoolInfo
+import com.festago.festago.domain.repository.SchoolRepository
+import kotlinx.coroutines.delay
+import java.time.LocalDate
+import javax.inject.Inject
+
+class DefaultSchoolRepository @Inject constructor(
+ private val schoolRetrofitService: SchoolRetrofitService,
+) : SchoolRepository {
+ override suspend fun loadSchoolInfo(schoolId: Long, delayTimeMillis: Long): Result {
+ delay(delayTimeMillis)
+ return runCatchingResponse {
+ schoolRetrofitService.getSchool(schoolId)
+ }.onSuccessOrCatch { it.toDomain() }
+ }
+
+ override suspend fun loadSchoolFestivals(
+ schoolId: Long,
+ size: Int?,
+ isPast: Boolean?,
+ lastFestivalId: Int?,
+ lastStartDate: LocalDate?,
+ ): Result {
+ return runCatchingResponse {
+ schoolRetrofitService.getSchoolFestivals(
+ schoolId = schoolId,
+ size = size,
+ isPast = isPast,
+ lastFestivalId = lastFestivalId,
+ lastStartDate = lastStartDate,
+ )
+ }.onSuccessOrCatch { it.toDomain() }
+ }
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/repository/DefaultSearchRepository.kt b/android/festago/data/src/main/java/com/festago/festago/data/repository/DefaultSearchRepository.kt
new file mode 100644
index 000000000..0dbc1db36
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/repository/DefaultSearchRepository.kt
@@ -0,0 +1,40 @@
+package com.festago.festago.data.repository
+
+import com.festago.festago.data.service.SearchRetrofitService
+import com.festago.festago.data.util.onSuccessOrCatch
+import com.festago.festago.data.util.runCatchingResponse
+import com.festago.festago.domain.model.search.ArtistSearch
+import com.festago.festago.domain.model.search.FestivalSearch
+import com.festago.festago.domain.model.search.SchoolSearch
+import com.festago.festago.domain.repository.SearchRepository
+import javax.inject.Inject
+
+class DefaultSearchRepository @Inject constructor(
+ private val searchRetrofitService: SearchRetrofitService,
+) : SearchRepository {
+
+ override suspend fun searchFestivals(searchQuery: String): Result> {
+ return runCatchingResponse { searchRetrofitService.searchFestivals(searchQuery) }.onSuccessOrCatch { festivalResponses ->
+ festivalResponses.map { it.toDomain() }
+ }
+ }
+
+ override suspend fun searchArtists(searchQuery: String): Result> {
+ return runCatchingResponse {
+ searchRetrofitService.searchArtists(searchQuery)
+ }.onSuccessOrCatch { artistSearchResponses -> artistSearchResponses.map { it.toDomain() } }
+ }
+
+ override suspend fun searchSchools(searchQuery: String): Result> {
+ if (searchQuery.length <= MIN_SCHOOL_SEARCH_QUERY_LENGTH) {
+ return Result.success(emptyList())
+ }
+ return runCatchingResponse {
+ searchRetrofitService.searchSchools(searchQuery)
+ }.onSuccessOrCatch { schoolSearchResponses -> schoolSearchResponses.map { it.toDomain() } }
+ }
+
+ companion object {
+ const val MIN_SCHOOL_SEARCH_QUERY_LENGTH = 1
+ }
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/repository/DefaultUserRepository.kt b/android/festago/data/src/main/java/com/festago/festago/data/repository/DefaultUserRepository.kt
new file mode 100644
index 000000000..7b286f7f4
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/repository/DefaultUserRepository.kt
@@ -0,0 +1,131 @@
+package com.festago.festago.data.repository
+
+import android.content.Context
+import android.content.SharedPreferences
+import com.festago.festago.common.kakao.KakaoAuthorization
+import com.festago.festago.data.datasource.token.TokenDataSource
+import com.festago.festago.data.datasource.userinfo.UserInfoDataSource
+import com.festago.festago.data.dto.user.RefreshRequest
+import com.festago.festago.data.dto.user.SignInRequest
+import com.festago.festago.data.dto.user.SignOutRequest
+import com.festago.festago.data.model.UserInfoEntity
+import com.festago.festago.data.service.AuthRetrofitService
+import com.festago.festago.data.util.onSuccessOrCatch
+import com.festago.festago.data.util.runCatchingResponse
+import com.festago.festago.domain.model.user.Token
+import com.festago.festago.domain.model.user.UserInfo
+import com.festago.festago.domain.repository.UserRepository
+import dagger.hilt.android.qualifiers.ApplicationContext
+import javax.inject.Inject
+
+class DefaultUserRepository @Inject constructor(
+ private val authRetrofitService: AuthRetrofitService,
+ private val tokenDataSource: TokenDataSource,
+ private val kakaoAuthorization: KakaoAuthorization,
+ private val userInfoDataSource: UserInfoDataSource,
+ @ApplicationContext context: Context,
+) : UserRepository {
+
+ private val authPref: SharedPreferences by lazy {
+ context.getSharedPreferences(AUTH_PREF, Context.MODE_PRIVATE)
+ }
+
+ override suspend fun isSigned() = getRefreshToken().isSuccess
+
+ override suspend fun isSignRejected() = authPref.getBoolean(IS_SIGN_REJECTED, false)
+
+ override suspend fun getAccessToken(): Result {
+ val token = tokenDataSource.accessToken?.toDomain()
+ ?: return Result.failure(NullPointerException("Access token is null"))
+
+ if (!token.isExpired()) {
+ return Result.success(token)
+ }
+
+ return try {
+ val refreshToken = getRefreshToken().getOrThrow()
+ refresh(refreshToken).getOrThrow()
+ Result.success(tokenDataSource.accessToken?.toDomain()!!)
+ } catch (e: Exception) {
+ Result.failure(e)
+ }
+ }
+
+ override suspend fun getRefreshToken(): Result {
+ val refreshToken = tokenDataSource.refreshToken?.toDomain()
+ ?: return Result.failure(NullPointerException("Refresh token is null"))
+
+ if (refreshToken.isExpired()) {
+ return Result.failure(Exception("Refresh token is expired"))
+ }
+
+ return Result.success(refreshToken)
+ }
+
+ override suspend fun signIn(idToken: String): Result {
+ return runCatchingResponse {
+ authRetrofitService.signIn(SignInRequest(SOCIAL_TYPE, idToken))
+ }.onSuccessOrCatch { signInResponse ->
+ userInfoDataSource.userInfo =
+ UserInfoEntity(signInResponse.nickname, signInResponse.profileImageUrl)
+ tokenDataSource.accessToken = signInResponse.accessToken.toEntity()
+ tokenDataSource.refreshToken = signInResponse.refreshToken.toEntity()
+ }
+ }
+
+ override suspend fun rejectSignIn() {
+ if (isSigned() || isSignRejected()) return
+ authPref.edit().putBoolean(IS_SIGN_REJECTED, true).apply()
+ }
+
+ override suspend fun signOut(): Result {
+ return runCatchingResponse {
+ authRetrofitService.signOut(
+ AUTHORIZATION_TOKEN_FORMAT.format(getAccessToken().getOrThrow().token),
+ SignOutRequest(getRefreshToken().getOrThrow().token),
+ )
+ }.onSuccessOrCatch {
+ kakaoAuthorization.signOut()
+ clearToken()
+ }
+ }
+
+ override suspend fun deleteAccount(): Result {
+ return runCatchingResponse {
+ authRetrofitService.deleteAccount(
+ AUTHORIZATION_TOKEN_FORMAT.format(getAccessToken().getOrThrow().token),
+ )
+ }.onSuccessOrCatch {
+ kakaoAuthorization.deleteAccount()
+ clearToken()
+ }
+ }
+
+ private suspend fun refresh(refreshToken: Token): Result {
+ return runCatchingResponse {
+ val refreshRequest = RefreshRequest(refreshToken.token)
+ clearToken()
+ authRetrofitService.refresh(refreshRequest)
+ }.onSuccessOrCatch { refreshTokenResponse ->
+ tokenDataSource.accessToken = refreshTokenResponse.accessToken.toEntity()
+ tokenDataSource.refreshToken = refreshTokenResponse.refreshToken.toEntity()
+ }
+ }
+
+ override suspend fun getUserInfo(): Result {
+ return userInfoDataSource.userInfo?.toDomain()?.let { Result.success(it) }
+ ?: Result.failure(NullPointerException("User info is null"))
+ }
+
+ override suspend fun clearToken() {
+ tokenDataSource.accessToken = null
+ tokenDataSource.refreshToken = null
+ }
+
+ companion object {
+ private const val SOCIAL_TYPE = "KAKAO"
+ private const val AUTH_PREF = "auth_pref"
+ private const val IS_SIGN_REJECTED = "is_sign_rejected"
+ private const val AUTHORIZATION_TOKEN_FORMAT = "Bearer %s"
+ }
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/repository/FakeArtistRepository.kt b/android/festago/data/src/main/java/com/festago/festago/data/repository/FakeArtistRepository.kt
new file mode 100644
index 000000000..e704cb514
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/repository/FakeArtistRepository.kt
@@ -0,0 +1,105 @@
+package com.festago.festago.data.repository
+
+import com.festago.festago.domain.model.artist.Artist
+import com.festago.festago.domain.model.artist.ArtistDetail
+import com.festago.festago.domain.model.festival.Festival
+import com.festago.festago.domain.model.festival.FestivalsPage
+import com.festago.festago.domain.model.school.School
+import com.festago.festago.domain.model.social.SocialMedia
+import com.festago.festago.domain.model.social.SocialMediaType
+import com.festago.festago.domain.repository.ArtistRepository
+import java.time.LocalDate
+import javax.inject.Inject
+
+class FakeArtistRepository @Inject constructor() : ArtistRepository {
+ var index = 0
+
+ override suspend fun loadArtistDetail(id: Long, delayTimeMillis: Long): Result =
+ Result.success(
+ ArtistDetail(
+ 1,
+ "뉴진스${index++}",
+ "https://static.wikia.nocookie.net/witchers/images/d/d9/New_Jeans_Cover.png/revision/latest?cb=20220801091438",
+ "https://static.wikia.nocookie.net/witchers/images/d/d9/New_Jeans_Cover.png/revision/latest?cb=20220801091438",
+ listOf(
+ SocialMedia(
+ SocialMediaType.INSTAGRAM,
+ "공식 인스타그램",
+ "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Instagram_logo_2016.svg/264px-Instagram_logo_2016.svg.png",
+ "https://www.instagram.com/newjeans_official/",
+ ),
+ SocialMedia(
+ SocialMediaType.INSTAGRAM,
+ "공식 엑스",
+ "https://upload.wikimedia.org/wikipedia/commons/thumb/c/ce/X_logo_2023.svg/531px-X_logo_2023.svg.png",
+ "https://twitter.com/NewJeans_ADOR",
+ ),
+ ),
+ ),
+ )
+
+ override suspend fun loadArtistFestivals(
+ id: Long,
+ size: Int?,
+ lastFestivalId: Long?,
+ lastStartDate: LocalDate?,
+ isPast: Boolean?,
+ ): Result =
+ Result.success(
+ FestivalsPage(
+ isLastPage = false,
+ festivals = (0..10).flatMap {
+ listOf(
+ Festival(
+ 1,
+ "예시 페스티벌 1",
+ LocalDate.parse("2024-05-01"),
+ LocalDate.parse("2024-05-03"),
+ "https://source.unsplash.com/random/300×${300}",
+ School(
+ 1,
+ "예시 학교",
+ "https://source.unsplash.com/random/300×${300 + index++}",
+ ),
+ listOf(
+ Artist(
+ 101,
+ "뉴진스뉴진스",
+ "https://static.wikia.nocookie.net/witchers/images/d/d9/New_Jeans_Cover.png/revision/latest?cb=20220801091438",
+ ),
+ Artist(
+ 102,
+ "아티스트 B",
+ "https://source.unsplash.com/random/300×${300 + index++}",
+ ),
+ ),
+ ),
+ Festival(
+ 2,
+ "예시 페스티벌 2",
+ LocalDate.parse("2024-06-10"),
+ LocalDate.parse("2024-06-12"),
+ "https://source.unsplash.com/random/300×${300 + index++}",
+ School(
+ 1,
+ "예시 학교",
+ "https://source.unsplash.com/random/300×${300 + index++}",
+ ),
+ listOf(
+ Artist(
+ 101,
+ "뉴진스뉴진스뉴진스뉴진스뉴진스",
+ "https://static.wikia.nocookie.net/witchers/images/d/d9/New_Jeans_Cover.png/revision/latest?cb=20220801091438",
+ ),
+ Artist(
+ 102,
+ "아티스트 B",
+ "https://source.unsplash.com/random/300×${300 + index++}",
+ ),
+ ),
+ ),
+ )
+ },
+ ),
+ )
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/repository/FakeBookmarkRepository.kt b/android/festago/data/src/main/java/com/festago/festago/data/repository/FakeBookmarkRepository.kt
new file mode 100644
index 000000000..9a09cd792
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/repository/FakeBookmarkRepository.kt
@@ -0,0 +1,119 @@
+package com.festago.festago.data.repository
+
+import com.festago.festago.domain.model.bookmark.ArtistBookmark
+import com.festago.festago.domain.model.bookmark.ArtistBookmarkInfo
+import com.festago.festago.domain.model.bookmark.BookmarkType
+import com.festago.festago.domain.model.bookmark.FestivalBookmark
+import com.festago.festago.domain.model.bookmark.FestivalBookmarkOrder
+import com.festago.festago.domain.model.bookmark.SchoolBookmark
+import com.festago.festago.domain.model.bookmark.SchoolBookmarkInfo
+import com.festago.festago.domain.repository.BookmarkRepository
+import kotlinx.coroutines.delay
+import java.time.LocalDateTime
+import javax.inject.Inject
+
+class FakeBookmarkRepository @Inject constructor() : BookmarkRepository {
+ override suspend fun addFestivalBookmark(festivalId: Long): Result {
+ TODO("Not yet implemented")
+ }
+
+ override suspend fun getFestivalBookmarks(
+ festivalIds: List,
+ festivalBookmarkOrder: FestivalBookmarkOrder,
+ ): Result> {
+ delay(1000)
+ return Result.success(
+ listOf(
+ FestivalBookmark(
+ festival = FakeFestivals.plannedFestivals[0],
+ createdAt = LocalDateTime.now(),
+ ),
+ FestivalBookmark(
+ festival = FakeFestivals.plannedFestivals[1],
+ createdAt = LocalDateTime.now(),
+ ),
+ FestivalBookmark(
+ festival = FakeFestivals.plannedFestivals[2],
+ createdAt = LocalDateTime.now(),
+ ),
+ ),
+ )
+ }
+
+ override suspend fun getFestivalBookmarkIds(): Result> {
+ return Result.success(listOf(1, 2, 3))
+ }
+
+ override suspend fun deleteFestivalBookmark(festivalId: Long): Result {
+ TODO("Not yet implemented")
+ }
+
+ override suspend fun addSchoolBookmark(schoolId: Long): Result {
+ TODO("Not yet implemented")
+ }
+
+ override suspend fun getSchoolBookmarks(): Result> {
+ delay(1000)
+ return Result.success(
+ listOf(
+ SchoolBookmark(
+ school = SchoolBookmarkInfo(
+ id = 1,
+ name = "School 1",
+ logoUrl = "https://picsum.photos/200/300",
+ ),
+ createdAt = LocalDateTime.now(),
+ ),
+ SchoolBookmark(
+ school = SchoolBookmarkInfo(
+ id = 2,
+ name = "School 2",
+ logoUrl = "https://picsum.photos/200/300",
+ ),
+ createdAt = LocalDateTime.now(),
+ ),
+ SchoolBookmark(
+ school = SchoolBookmarkInfo(
+ id = 3,
+ name = "School 3",
+ logoUrl = "https://picsum.photos/200/300",
+ ),
+ createdAt = LocalDateTime.now(),
+ ),
+ ),
+ )
+ }
+
+ override suspend fun deleteSchoolBookmark(schoolId: Long): Result {
+ TODO("Not yet implemented")
+ }
+
+ override suspend fun addArtistBookmark(artistId: Long): Result {
+ TODO("Not yet implemented")
+ }
+
+ override suspend fun getArtistBookmarks(): Result> {
+ delay(1000)
+// return Result.failure(Exception("Failed to get artist bookmarks"))
+ return Result.success(
+ (0..20).map {
+ ArtistBookmark(
+ ArtistBookmarkInfo(
+ id = it.toLong(),
+ name = "Artist $it",
+ profileImageUrl = "https://picsum.photos/200/30$it",
+ ),
+ LocalDateTime.now(),
+ )
+ },
+ )
+ }
+
+ override suspend fun deleteArtistBookmark(artistId: Long): Result {
+ TODO("Not yet implemented")
+ }
+
+ override fun isBookmarked(id: Long, type: BookmarkType): Boolean {
+ return false
+ }
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/repository/FakeFestivalRepository.kt b/android/festago/data/src/main/java/com/festago/festago/data/repository/FakeFestivalRepository.kt
new file mode 100644
index 000000000..6f01a5954
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/repository/FakeFestivalRepository.kt
@@ -0,0 +1,110 @@
+package com.festago.festago.data.repository
+
+import com.festago.festago.domain.model.artist.Artist
+import com.festago.festago.domain.model.festival.Festival
+import com.festago.festago.domain.model.festival.FestivalDetail
+import com.festago.festago.domain.model.festival.FestivalFilter
+import com.festago.festago.domain.model.festival.FestivalsPage
+import com.festago.festago.domain.model.festival.PopularFestivals
+import com.festago.festago.domain.model.festival.SchoolRegion
+import com.festago.festago.domain.model.school.School
+import com.festago.festago.domain.repository.FestivalRepository
+import java.time.LocalDate
+import javax.inject.Inject
+
+class FakeFestivalRepository @Inject constructor() : FestivalRepository {
+
+ override suspend fun loadPopularFestivals(): Result {
+ return Result.success(
+ PopularFestivals(
+ title = "인기 축제 목록",
+ festivals = FakeFestivals.popularFestivals,
+ ),
+ )
+ }
+
+ override suspend fun loadFestivals(
+ schoolRegion: SchoolRegion?,
+ festivalFilter: FestivalFilter?,
+ lastFestivalId: Long?,
+ lastStartDate: LocalDate?,
+ size: Int?,
+ ): Result {
+ val notNullSize = size ?: DEFAULT_SIZE
+ val notNullLastFestivalId = lastFestivalId ?: DEFAULT_LAST_FESTIVAL_ID
+
+ if (festivalFilter == FestivalFilter.PLANNED) {
+ return Result.success(
+ FestivalsPage(isLastPage = true, festivals = FakeFestivals.plannedFestivals),
+ )
+ }
+
+ if (notNullLastFestivalId + notNullSize < LAST_ITEM_ID) {
+ return Result.success(
+ FestivalsPage(
+ isLastPage = false,
+ festivals = getFestivals((notNullLastFestivalId + 1)..(notNullLastFestivalId + notNullSize)),
+ ),
+ )
+ }
+ return Result.success(
+ FestivalsPage(
+ isLastPage = true,
+ festivals = getFestivals((notNullLastFestivalId + 1)..LAST_ITEM_ID),
+ ),
+ )
+ }
+
+ private fun getFestivals(idRange: LongRange): List {
+ return (idRange).map { id ->
+ Festival(
+ id = id,
+ name = "뉴진스 콘서트 $id",
+ startDate = LocalDate.MIN,
+ endDate = LocalDate.MAX,
+ imageUrl = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAoHCBcWFRgWFRYZGBgZHBweHRwcHBwaHBwkHRoaIRwaHhocIS4lHB4rIRwcJjgmKy8xNTU1GiQ7QDszPy40NTEBDAwMEA8QHxISHjcsJCs0NDQ/PTQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NP/AABEIALcBEwMBIgACEQEDEQH/xAAcAAACAwEBAQEAAAAAAAAAAAAFBgMEBwACAQj/xABEEAACAAQDBQUGAwYFAwQDAAABAgADESEEEjEFQVFhcQYigZGhEzJCscHwUmLRFCNygpLhB6KywtIzU/EVY7PyFiQ0/8QAGQEAAwEBAQAAAAAAAAAAAAAAAQIDBAAF/8QAKREAAwACAgIBAwQCAwAAAAAAAAECESEDEjFBUQQTIjJhcaGB8CNCkf/aAAwDAQACEQMRAD8AVxtFh+7qWVtALkMOFNYkxUgsoZGIK3HjS8XpmykTKaOHDk5wwX3b91Retd9d++Ks1yliFA3G4NLUBO/T1jGpZG9Pb2CBjDmIfutqeDUO4fCeY/tBlMaJpBykhQLAEljwAgPiVVr2J51AF+lzEuGmNJBdbm6WJGoJBHDQdYok0BpVjYY2pPVFKFQxcEUvoaWtpcn+mKEl+8agCvU7qam+4GPaoZjgOwUAX8NaeNYqSSWL5b1JVRwoRT6RXAuNYCEvFIjK4LZiXqLZaiWAL0qPdpTS8Wnnh5C5SSrOutyFpoSNTaAb4dhMAmpmSgGZTdLnvA9DWhB3cIKyZTSc0slGALZSDUMCpowobG4PKEdKSlS6lP8A1FnCrRG0qWPkPsxXxErum1XYAVPARPsNWmUVRU6DiSxJ8qAx219sYfDEhV/apu+pyyUO8Cl5nyikz22SUtgPa7Ay1FRUsK8bA+PCK8vETFp7FyDQVAagNrg3glM7TTzQqyywVFElqEUW3bz4xG23pjikzJNXhMRW9aBgfGKdVjyWmal5TPK7UNGcgs9DUWUC/wANflfSBq49iNTQmpGtAdaeMWpqISGlgpxQktT86ObsugINxzF4H4mVQ1GulNK9OMI110ylfUctab8Fqa4cLQUFSda1/Ma6dOUfcEwqaXqQFANyKWrwNSbRVrUBEFWF91uVTaLOFlqmfvUcKQDQhVJoKk0rXvU3U1pvhW2lhE+rp5PWL7ikrqRfQ0B1J5XA8RFJsYcmUWBued6U9I94aSVcM5qtDowNbZcp13ct0c+Bd1zIoIzEAA3FbgX4X84PsouJudbJsFPohbeFP1/SLm1u1bz1RAuREQDKGpUqBS4+EEVpv3wADtTJS/DpHTZeWnEwUvIJqoyl7C2JZHspZQcuVSKk11GZTSo58IqqL5EvrU/XpHhzlloALsY9JOZFotATwAqfvlAUi0+zzgjnJQ053PGLmy5dVcHgfkInl7dnyaKroCbmkqTblXJUniTBaV2kV+7iZEuYv4lUI45gi3gKdYr0yvIrl4wA8Xh+8aWNAdaViDDzGulTVteMMe0NjIU/acK5eWPeU3dKajiQK6G4F7i8AUIDFhwidS5AnrBN7c3VdafO2U+fpHuSjCaFUFu6BZWuSBu11BiEzPiFjqYqM7uS5JAFhr9IRTnbKJfjgc5UicUWWolKwFw8xA2tfcBqPGJjsvGpdpauOCsvpWkZ+kmkX8DtKbKNZcx0INaKxAbkV0PiIdcHH7RRc3IvD8DOdpZXCTJby3O57A9DFidiSFY5BYH4uXSLeA2gmPleynhc7A5WAAuOHBhw0PlVamO6Z8O9ypoCTelRpxsa9DyiPN9Kp/KfBo4vq201XkIYfFF60AFKbz+kWUVjvXyJ+sVdnYU5agqK3vWvL0v4xeVCCFLLU1Isd1K7+cZbSTaRr4rblNs+ZW4r/Sf+UdE2RuK/0n/lHRMr2L2y8KHIaaMtAfepehrVToylb1vvgVi8VKMxyjiU1R7M2VRlrUsRvOmh1pBLF4kTkRVAFUV3OuWu4V0P3xgTtQoRLTIGoc6FgAQbhu8vvITuNbr5a5TpJ/J49uU2l6/soYbFBhWhAv8AzcfA3ED5IfEOsnDpncnUaW1N7BR+I2i7twnKqL7z604bwOv3rDllTZGDByq2JmD1G7jkSviettfHCxlmZLOwM3ZCThlD4/GFWPwI2UHiBUFn8AIH4rauzFPcTEORowZx/rcfKFTaWNec7TJjl3bUn0A4AcBaKJirQ2PkZMXi8PNbMk2ah/DMAAPLMlfUeMXsJShRRnotXuN4F1YGhAFOdzCZF3B4oratt440P36xK+PstaHnqnscNnzAkl8lnnEoN+VFGZ6dcwHQNC7tHZxZqJfQUi1Lx9M5qKZDlF7ZqZtfARf7FAzZ7M1wtPlCS3MlFKbLGB7JOZQL2YaQvbQkNLcowow+wY2tUGWkJPbfYodC6++gqOY3r+kHa2O5TWhGkEOMh1N1O8H++ke9npnqGzFkrZSFNhrmP3YwNlzTYjURcd8kzOtO8K06g13aboatolOE8tZLUhTMYS5Mu7HRSanhbz1ra+6GBOysmUK4zEpLOvs0YZh1NyT0B6xBKxTSZOde67rcixVTYIp+EsQSSL5VFKVhQxEwsSTvv/eDCSR1Zex6EvZGntHPP999EpH0dnMJN/8A5cSQ+oGYMfKiuIz2LMpbAjWKJ/KEw14YU2pgJmGmEz1zFhRXF1Ygcaa8jQ2gNlLHMYbtlbUM1PYYnvo/dDH3lO6p60o2oPovYqQZTtJa5VqA8RuPkR5xOkltHPPl+QjsnZ8yeVkShSgBdyD3AScq131FGtx5Q54TsKiULEMRx/SLnYbCokhbgu9XatM1WuARyWgpyhuKwPJaZSRjvaXYhlvmOkLrON0bbtjZ6TVKuKg+Y6RkPaHYDyJlFBdHYBCBepNApG4/OCqa0C59ok7KbUeViFVAXVzlZBTvcKVNMw3eI3x929IEme6KKKe8otodwpu1p0EGcHKlYGQJk5ZbTwbKD32zZe7UrVcqk5qUFOOeFnam0TOEt2NXC5WO85TY132MO9LDI4yfcBhXxExZSGm8ncBvJjT9m9mJSSwrLnPEwmf4fr33bfYfX6xqUkWiDeWaZlKRA7T9m1RS8paU1A9aQlKt42fHzpYqruoPDU+QjKNvYYS5zZPcapFiOdL9aeEGaa0Lcp7R72VMKuUBpmOZCNzLcU6geYWLXaDHh3lYigOZRnXS6Ehx5FfAiAsvEUKsNVII8DX9YI4rZhmTnRGAABdahjXNk3KDuC+UUqvx2TU5ei5L2i5VVlkG1bChqb3JtHmVOmo4ZgC17V42vc/YgbhiUVkYUdWynkBw+90XvanMWJrXnpbwjDSSeMF8PqsN5/pFyY7kkk6+EdFP2jDRa+UdFM/x/wCEel/LGZJqSkehJNAVBvUgGgqBQ3PKPuPwLGYk8igmLULWympNBwrmB6kmKeKwLIxAIaihnGlNMxUcAT4CPkvHTCgQFCijMuYkEAe8Ad5ArY8I5JacidnlxSw/X+Cm8xTilJ0V06e8tfD9I7tNtg4vEO6+4gypyArlPUm/jygHjpxGe9yAfPNEOCm0Q86ny/8ArGnOFgMolbCrULxzekV2wdvvgTFue9HT71C0iuiTJhZJaM1K1oNLUhu2juu8FZ5AvePKyu8Rx062gjh+z89zcBa0sTfyj2dizker5VAbUkU1pA7IZQ/gEBzcHgRDP2FxuRplFq3dIuBx4wuY2WBNYBgwJsRoaiPmz2AmJmNFJAY8id/LjCtZQ0vFGt4TbTu1GC/ysGp1irtqQ7vpm0oDXLc6kbwIn2VsZJVWAF72rv4V3coKY56Ijjp1hcfJfwZJtvY02V+8dRlZjXL7qkk0HjFeQM4TkaeRNo07tZLV8FMAFapmHVe8PlGY7KFacjbxFvkYPolUpUg5tdKoiV3VJ6gAf5csD02cCpPG31+kWe0OJpMKjccvgBQeoEeZd5LCjhgoIqLGnvUI5QnZpDdU2DjgxTXcT8o94zDBNOA+VYrzGbMdaU+d4Iz3LoMqOxCpelqgX1g92DqiLYk4PmlNq2h690+VQ38sQ7Vnl3lt8ZQI3MqzCvjYxUkIyzVyggg1pobCtIvHCs2IDKKoroTcVFwa0rUi+4QewvXKHLAY4rkV5VCSQrJUlcpp3qC3nvhunzWyAkkAjWBmCkD3gO8313wXdhnCHSlKfOAkXFqbteSjBWM2rCoJzafi1050iSZLWaupZaggmh0011g5P2alczCv3p0gbjHVBRRQQvgOMrZmfacO2JZWYuTlCipOoAoATYlqkgWqxihitnzJVBMTLXS6tpqKqSAbixh0w2CEyYz93NU0qK2Glet4G9sMShdJSgZhdqaLVaAfM+A4wytt4I1xpJsg7HO4L5HRLipa503bqRpWzMQzqVYgsBWo0I4iMp7MYQPNoaVW9DodQaiNT7PbOWSjBbd07yfe69I5rY0r8SniZplVMuXnajNwrSlgd7GunIwq9plnTZXtJiBctGsTUCtKEHfRuMaTJlqy0IgX2nkKZLqBQZWHH4T/AGjsYGxnRjSm/pDTs92E7DMpoWQKedBQg+UKyjd92rBmc59jKZDRlLLUaipP0MO1lNGdaeT7tLFg4nEBT3XKgEfkygkdcrece5HfYXoLaXPQCK2IwGRQ+ZaqqsVvW593SxpEiT8orlIVvdJHA36xHkjA8VmsvwFGwCjV/Mk/WPseJGGYqCJrUItcf8o6M3+TXr4CLMtEVAQGLZqnMaAgtfduty1iPbWGyOCoopTNTmKgkcK0BpziPAJQM53Cn948Tl7pYsSGpSpJpcBhff8ApFozlI87sqzlfAHQKxmEgHI6A1v3RbyNT/SIi2giBysoELSlCSbmtaVvS8Wl2Qy5XeYB7dSVVUZ8wahCE2yuLGlDSg1ipjnBcMvBai9mCgNruqPWNPVpjqk0RY9jnU/kQ+YBhs7L7DE3DsxZgHdjY00oK8/GFjaqZStP+3K9EWNG7FUGGQdfnHawUhZbO2VsII600UU/ipvPEx67QbHVnBYWJDLrQMvIa+PODqvRqgV5VpArauLmNlV1UUNajToPCFyh8GY9psIJU+3xAOeuY1+QgW6UcjmfnBLtXiM+Jf8AKAnkKn1Y+UVp5UOzEmvcIG42qa/e+H9EH+o0TsrtTPJCMalABXiPhPl8o94+bJJKs7veuQGoruFoR+zuPZHJGm8cjw6GHjC5XBKTMldQANeh0MSrOcGrjcvbKuM2lnlOmQoApFG1pThuhN7MS801VPFD5NT5MYOdpZySkKIxd3PeJNTzJgR2WtPT7+IR28E6w60W3wLTsSVU0JLmtK6m0NeC7PzQ477FQBYgU/MTx5dYAbKmZMWhO80PiBT1Maej2FIE7Q+MAHbOyE7gVVDEXNBC9itgTQe47La4AFM3L8vrDNtjGrnQKbjdQ18osYmeAleUNhCpNmbHClcUFe5owJ49wwKfFlJj/mVR45Fv6mD7PmxaN+IOfRqfOFnHrWa28AgmnAKtY5LOmCn12jQ8BtJDKQu+WtKEGhrBvZ4QtmE0ua1oSNYRuyU/2paVoUoy1vXcajqfWHNNl5SGcJbTKKf3gNNMeXLnLewniZ9oW9pTtYkx+PCamFjaW0mylwLDcfivp4wHs7thEeP21NlpkSRlO56FgAa0NKUDU4neLQrrKcsCwapvVgannU69Y0fAykdGnFw3ABAcxNwe93cvdIJtqAbwH7SYnP7IZACFZ1YWOU0BXLUgDNpSgsRGhcSS0ZHyVT2LGzMWZU5X3VoehMauk9mRWRwgYd6txy8YyTESSHUDfQjxNvlDPsLaCTCMO1QVpkqTQkC+lK77c+URpey3HSz1Y84XGSpfvzszU+JhH3a80Mh6H5RXwuxiO8wRV1oq68yaRS7QYpUQ3udBCNvBe+qf4sznDyx7cIfxkfOLuFQmVl0ImeIqoH0igrET0b86n1qYuj3Z68H+rxaTK1sI7UlvLkuj0rXKNzWyrfj3Qp1pHbMCzcMoagAGU8stq13HfXnA6VmaWwZ3fuoBmZmpmOgqbeEeez05iry1pUiorpwbTwif1K7SmvRT6dqXh+yWZsuaCQBUDfpWOj5I23kUJV+7bX+8dEv+T4QccX7jGuFqhQg+8F5mlTu3W3QL2lLCIaVvxO8ilelYNzZ6gihNSwII7tDlvvqNYH7ewtJCvUkMXAPJaX53rDcT7NMzSkm0iFJzOqmUQwyMPeHdIQhSTXulQRwsQICYrKQAprYZvysQMy133BvzihImPlKIzZXIqo+Lh/4hlTs46Skcg5mrmWlOYI5iNdPSDHG3loEbRfMks78gB/lZl/2iHPsPjwZIWt1tCdi07lPwk+TbvMHziTs/PdMxQ0IIt1/8GJUtFY/UafiZr6o2UbyAGbwrYQq48OhaY7zGy1PeoKmlhpvjv/zcSjkeWWalypFvOBW1e0LYgVC5VFTe50heuS33cS5WBWZyxLHUksepN/nE2N+A8UX0t9Iiy92v3qIndKylP4WZfPvD0r5RUyFvs5/1eRFDw5Vh1bAqw0vxhc7BS808gioyGvmLxpv/AKctCT3SNTu603R32HS7JjrmU6YgbU2TRGIG4wG2HMCTZZP42U+IBHqPWNF2lIX2DubjKSLU++NIyx3oKj4XVh99aQjip0xu0vaGTbCMk/u/iSniAR6rD1gdpFpKvLysSAbm3W2sKWPlLiJKzBfMlD1Sv6tEXYvaTSnaW/fl69CTqBwNjTnE8YK52mMOL2jPrVllHo1Sf8sedo4s+zvY0ghiMZIoSqqDyW/yhYx81nYWogPnHeF5HqlT0sFGSP8A9iv4E/2wuYl8rzDvOYCnPTdwHKGU9yXMmH3mrl8P1ovgTCfiAaLXfUw05I8jWMHYLGPKcTEbK438a6gjeDDlhO0eJmqASoqNVB+pMIpEN3ZcVQV3Ej1hqEh7CCYMsasSTzgd2mTKiqN5htlywBC32rTuV6/KES2UrwxdwG1ZkpHRCMrUIqAcjAg5lBBFaCnjXjWfZjlnzOSzFWufE09dIGJpF3ZT0YHkT/laLJvwZ8LySqAWklt2YHdZRX9YEymYUcEggihGoOsENoqVSXXUiv8AlFfUnyjxKkdxB+Jj9+kdWPB2N5D2zu1OJdchZbWrlv8AOlYtthWc5nJYneYDdncGxJaljaHbD4e0Qrzo0T42Iu1cKVmVAsq35VqBHlSSZ54lD5sf1hln4AsJrnRiafwoKeVcxgDLQDPvqZd+N1MPDyJSxs84Y9w/xyR8v1gTh5rSplVsQSv0++kF0FJCnjNB8FCj6GC3aDs5mkidLFwO+OPExbp2kl26tClOVcxzChqa1F9d8dFuSkxhUOL11pWtTWtuMfIhgpsbMNhX7tHUhwVApu18Cb+UGu2uEEvByk+IW8wS3qRFLsvhWbEd+hVAW+iinMkecMG1ML+1Y6XIN0lrmf0JHiAg/mjuCPZKV+OSj/h32TVUGImqSx9wEbt7deENj4RZlWyAjQXPnrBaancCLaoAtuG+n3viVZQAoBpGxVgVpmW9p+zOQGYiNp3wL1HED8QoDTfSELCqZc0ruIN93EHyj9FzJQIhS7RdlJM8FsoR799beY0Pz5x1SrOVOfJiAbO5PEk/ODuEwJeTlT3mpXpS/wCnjFfauxHw03I4tRirDRrH1HCDmxCAKRC5c+S0YoWto4bIMvCg9XJ+kdhbl0PxCo6rf5Zh4xe7TrRyN+Yf6f1rFFxldHGuUMOuUfWO/kVoY/8ADdaYh7V7h5alf0jQcc9EJmMElrc6kngCaekJfYNAJzsNHVSOmtPCtPCGDasxsQURAfZknM3HhQHX+/hGmWpnJGk3WEdjdrI8mYoRwoWzMBlPKxjKVNbcVp4jT1jSMRs3LIZMrFySoYGmZdQKaWrSkU9ldg+77TETEVEqSqHMba5m0WlN1YlSdPRSfxTyL2E2s8oLKUWYA33HU26QQ7GIWnPmGqk+ZFvSAmIwoeZnQkgzSAN4FQRbdYw/dl9jtKYs4PeFVPEH7vGek8F5YUbZ44RTxmCAFhDIqg3iA4XOaAeMKpb0hnWNsy/bSEhg4soZh4K27gLecVdkbHGKTJnKTJeaoItRmqLcKU049Iee3OxV/Z8wqGU0JGpDmhB43C2hV7FgviUDnKVTKCLFgKUB50oOgEaInFJUQuuybQMxnZLFSz7hmIPiTvafl970hj7PbOyS0DWYipHAm5EaQ0sBe4brqBr1odb/AFgXiZQe5UBxS441+R05Wg8kT6Z3E37KEvC2hN7czMrpLG9XY+AIUedfSNGwUtaXqSNQbUjLe287Pj2pouRB4AE+rGE+00ssauRN4QtyTY+PyMGtj4EvMyUr8JpzsfrAjADvCtxUV6VFYf8A/D/BF3eYdS5+/OsPxzmidPCAPbLZTy6OwqgNFI0AI0PA1HjWBeASuThc04bo27amyEnIyOoIYUPP747oy/auwHwzit0oQrU8cp4MLdfMB+SF5QJv0w92bwg/Zkal718zT76QyYPZpYXGUHjr5RV7Jplw8uv4QfO8FtpTzkyIaM9qjUDeRwP1IhHwznIVytLAv7VkI6uAP3ctT0dlGv8AApsOJ6CqTNl0yHi6D/KkaHtjDhMK6qLZQo8SB9TGfYle6h4uD5ZRHOUtIMtvyVZiUlSl41Y+LgD6xqGzcOvsXD2Wx0rranM13RnKIGzfk9mo5mtTTxaNP2UlSpYWCgKDvNT3yPGg5dYrxvGSdrJnmJ7Guzsy90E2FdBuGkdGq/sojoP4fAM38i92ekBQgcAO9Ham4IDTzNT/ACiCfY3DZjOxJ1muQv8ACD+tv5RAmVNLHFMvwIUXx7v+35w57Hw3s5MtPwqPPU+sZ50i9JTiUWUFhXpH1zSOY/OIye8eVvqYZCM6Y26KWIMTs9i3E0EQThcDlFJEoWe0ezFxEtkIGbVG4MNPA6eMZ1sqdlcA8dOm6NXxC3I8oyTaIyY2cptR2I5Z+9/uh7lU02dFNJoq9oTWc3T/AG1+sUphrLQ8KqfX9YtbZNXB4j/bT6RTwneVl8R9+EZ7WKaKy9D52Aw59jn4uwHIUH1r5w2fsuXTQCgHCAfYyWVkSxxBb+pjDQTAqm9fA8ylsozEDFBuUFj1H9zXwgRtvGZcMw0Dk+NbKqjeSQT0g5MUsQgtm1PAbz4CE/tviQzpJlmgljvdWpQc2Cj1PCGjKTBe2il2L2eJmIL65O9TdUVC/L0jWEwwKBdKAU5Qs9htj+xw4LLRnOY11p8I5WJ84bVh+uJwSdZrJSl4Y1ykUpv3HpFxUC6R7JjzAmUjqpsXO07d0KTRfeY9PdryBvCP2EwqvOBLAkL6k2r5Q+doJBeXMUaupUeIP0rCB2PnCXigoBDMWR1PAaOOG9acucNSw0GXpmm4mRYOp/S45ag8Io4k565bEfZA5RLOGQmptUGm405bjFNwSMy0zC9POx5RK2WhFScWZC6mjqaHw0Pl9YzLbM+uMdmFDnrfoprz0jWZ0tUbPucUIjOe3GA7wnoKD3G5AnunzJHiIVXrAKj2LGCFD5DzjZOwGGC4ZGp71/Mk09YxqW1uZP6RvPZ6VkkSkG5QPJan1i3H4bIV6Cy38L+sAMfhlnq8s6OD4VHdPUWMFcXiAqlRq3oN5gergX38IvKJ0wX2ZxgeWFoVMvuMOagQTkjO5bcLDw1Pia/0CAex8Ree28TXHlQKPl5wy7Pk0UQtLCOllPtMn7in5l/1CM52kcrIPwhT/U6mNI7Tf9KnEj5GMv21OBnP+UIvrb5iI0i0+C1svDnI5/8Ad+UxQPSNW2eot/AIzrBJ+5H5phbzmAD5xoOBfugDUqPADfBnwBl+OjzkMdBALHZfD5jNU3H7pDzOYs3qTD6IRuxUwMHb8c8+gJH0h3LRFLRW3lnmZFZ294cT88sTO0VM3er0+6Q8om2SuKsFGg+Z/tEIu7HcLRKgNC29q08f7RGzBRQX4w6FYNxa3jNf8RMJSZLnLYkZW5090+VfKNNnEndCl2twftpTKKFgKr1Fx+njFmu04EWnkzfHv3UNYq7OmUcc/s+lYmntmlKfu1j8ol7ObMbETgi1oBmY8Bpv3nTz4RmtN0sey0vCNL7MKRKTkAPKDhffEeCwWVQFIoAB0intGaQci+8TTpxPlfygPjrtgpNy5LLYtUlzJzaKDTnTcOrUH8sL3Z3ZrzpvtHA98u4GgJay9RT/ADHhBfbiKstJABYM6AjWgDZjXxAHVoYtlYIS0AoATdqcTcxbrgm6yi2qUEVm2igYi9BSrWoKmgreovxET4mXmQrWlQRxijIWW4KhVKq1LWWq8B0I6xwmAhJnK4qjBhxBrEjaQMw+DWW6lAQDVHFT1Vtd3e/q5QTeAEo4pO74E+VIy+bhsmMHstQwbW5zu2bwFdOEalid0ZltruY9GJIAdctN4JAI6XPrBpaT/c6X5Q+TRnYV3W9LmI2ojVax4daRJMQoUJNc3epwG+PkyRmVnOpPpC8k5WUUmurSYOnPm18OUL/aTDF5ExQL5SR1XvD1EMmSKuKlVHKMi0y72jI9mS882Wn4nXyqK+lY3jD4hUlB3NAqk+ZoB9IyDs9sspjihH/TLHqPhPkwMahj5ebDa2UAn+UkHpxryjXx6hsyVukjlxGYM9QTrT5DkIrY7aqJLeYRdad3mTRR4n6xVwrZaI5pWysNGruPPlv3RWx8gBWRzUmuU614eIrCL6h5RV8Cwyt2dd3mtRSBRcwNK1Fe+RpwjQJKUEL+xJS+8BcgVPGghll6RenoypALtS9EB4MD6GMgxczM7cXdR1y2P0jWu2JIksQND8oyAD95QfCD53r98ojZohaHXCN+6QcSv/yKYfsBYGvj+kZ/hF7srkfv6RoGASoFdPnBnwLXkt0Y3zEcuEdFio5R0HIBJ/w/nBkoPhm+dZbesaDGU9hMRleQNzz5lfCWoWv9bRq5ETXgZ+SNorusTsYgcwyFOxWJVRVjusoufIQDxm0XuEQKOLXPkLCCU4a21gViREquk8IvETjIJxDu/vux8aDyEQIoUNYa/SLjiA+1cXkBJ8uPKH4axabYOVNw0hF2koDTVGgdiPEm3hWGzsNsnIhmOO89KclFcp6mpPSkBdk7LadMo5qCcz+Pwg+kaNJQKtOEG9NZEhaJhizLFdRwP0gZsxvbT2cml6KN4UC551pr0gb2j2kUWi3YmgHz8hFzs7iwyrSo0rUV0NSKjpBi/ljPjxtIZsG9HZGFSACacDcE01grhp4YQBlzD7WYy1HdQA01s1QK67oLYKYlKg3OsU8rILZX7QtSSb0qQOd9wj5gMOqIFrUG9RoTQeekWsYFZcr3BPy4c49yhpSgFKU3ADQffCOzoRaeSqMUBOKVpYW0vqCOPDx6wRd4gEtT3iBXcd4HLhESTDUg2/trby847yBn2cYWtq7ER5qT3cKJdyKXanuAHRaHWGCbMpCj232tklSEFi80u3NEygjoSR/TBp4Q0T7ZdkT3mTte4i01rUnfXgBm8zBxJ4pQAkC0IuJx5lTZdK5XAVgKcTQ+fzhqkY45RkUDrf8ASDVylsFTVVo9FCp92i6Cu6KeLm7oi2jMY0ZnY5SDStBbXui2lYgmX3xhuk60aolqdlHBy1XElzZnUL/ST60b0hgxLN7FwDSjAnpYnwrfwgGUBYBtK+XA13dYP4EmhVr1Gp38jzp5xr4X2jBm5V1vIJzqqhGAKtYDgeA5fKKM4k2bUGx16ffKLuKk0JQXTVeVDp4VFDwIivPF4xcqc1hmqGnOUENjYind4QzyptoSMHJYuSpuKQwYZ3pSlfn5GNkV2lNmO5xTRX7Xzf3VOJ+kZdIkd+w3keh/UQ/9oWZ2CcB9+nzgJsvZlHzH4QT9/e6Ep5rRaJxOWWfZ5UqN2nhDPs7ablRSWD1enyUwB2nMKIBa9BbUE1vXzi/2dvLHj8zEq5XL6oZcapdmGv8A1Kb/ANtP62/4x0eKR0J96hvtSInZLEFWwwPwzx5MZan1PpGy1jB9iTSGSmqzFbzK/VR5xu7HlT5RefBnfk8M0V3eJniBxDyIyGYLQOxKxdmroef0MVZsR5Visl+J5QJnrCt2olM4QJqXp5g/pDlPl2gLPk5piD8xP+Uwipp5RXCawz1sXACWgGp3k6k8YvYmdQR0xsopAXauKIWg1Nh+sGqflgUgnFyTPmVDlcthoRzND92g3seSyNRiKi9QKDhfzr4QKkogADoacQCQOdRdesGcAoCswYld1TmpTW5hYWaKPwMiZx8KsOIN/I/rHucnAU6QK2JtKW6Kocq4AzKSTc666iulIJTHNbkHpHKsMSkeTMcaNUc4nTHUFGFOYiJW4xwpFp5GSc5LjYkFajf1Ou+sffaVECpszKe6aV8o9JiqClB4RWblk6l+iae7aLq1r310jM+3E3Ni3TNmWUFljqK5z1zlvSNL2fMDTk5En+kE/MRku3Afbux1LknxmMfrA5a9I7jT9hDaU6okMTegOvDLDlgHqohKY1Cj8OYf5j9KQ0bHm1QdIlyvwysJttBDE6GBmAnVXKdVJXwBt6UgnN0halz8k5x+YeqiM7+S0/AXmLBTAMWWtzS1qVHIg2IgYrVEeRiXl95GI48/Axbi5Or34E5OPsv3C+MwpYFhUMg5Co4EVPnAjLmuIC7c2/OMxURwpAsQAMxNaoa20oRFTZe23R8rjNqeBF72036Q3P1vFInxJzlMbZEhl7wtBaXiDS4DehgRK22jDKN/GL0ieraGIzVT4ZVzNeUVMXLPeYklntodNdfACJsNh0RbjMTwB+touGhj4cODFJ5cPLQtTlYTFTtBPOYLoNSBx3V9fOCnZiZWWRwY/IH6xaxGxkf3l9THzB7L9lUoSQdVJ+RjNbbrsWlJT1ClY6KP7RxBEdAyHqzP8BLAmKdRmX/UP0jd4wDA4gg11AYW3noPD7rG8y8UrAGoFeNj4xq43rZg2z6wiCZEj4hfxL/UIqTsYg1dfCp+UWTQGmVsUTYdYhBqOlogxu1UBtmNtykfOkUMHtdWmZKEBtCaa7h4wnLUteSvFNJ+AjM0gRMtMU8K/KCeIakB8Q14hksfMTOrWFnETC75joLDpx8YNTlLDKPGKwwtKeP36xojh7Tlkq5etYR4w4YXRgw3qbMOh/WJMXiO4RRlFL0p43ibA4daFiNanSsRYvAlla4UEWH2d8ZvBoTySdntoywhlvLzjMSCwBrUDUcYaZ0t/hTyI/WErs8n71EIvmr1AuflGly0jQuKXszVy0ngBksNUPp+sd7XkawfaSI8HDjhDfakT7lCxiJo4x4SZBzFYFG1AgXidmlbqfAwj4qXgZckvyT7OngPXkfUQjdo9nPMc5FrRmGoHxc+kMTMy8REOcCEpvGGUWPRU2Vgcq0dBXwMFFkIBYAdLfKIP2iO9tWEb1hjIsvMtCjj3K4l+By355RDK0wARTbBCYDUXr/4h+OO2RarrhnjBYm0e8ROqIoyJDqcpU1HKC+CwJIJcail+epjp4adYGrkSnIo4zDlmzhS1R3lHvEDQr+ddRFvDFSoeZ30FlnIO8n5ZiC460p0gphZGWYVO4hfKsEJ2w6uXkuZUyl2UAq3J0Nm6xvmJx1fgx1yPOSrh9m51DS2SYvFSPlE4wrrubxB+cU5uDdCWm4Z6/8AewjFWPMpvPMxPh9qUNEx5Qj4MRJFR1cUha+iT3LGn6lryi5KxbrYg+UXpWOB1sYHvtByL7QwoHEICfItAstKLg/tGIxcwfDKUoniBu8Ym/onjz/Qy+qWdobVxEev2iAeAwWKJLTAktT7qCrOP4mrT08oszpcxNRWMt8Fz+/8GieaaCP7QI6AvtW/C3kY+RH7d/DK95+Sp2Z2SrTQ7AdyhHNm92vSjHqBD8mkfY6KMhx/pRDMgfiTHyOhGUQHxUCZ2sdHQjKILSNtqQFeobStLHy0jy00uaDTjH2OjRxynjJC21nBNLkXPKgj5Nl+9/D84+R0eg/0v+DH/wBiRUooQGm80t5nf0gdi8PJDAMMz7q1J8zaOjo8yj0EXOzWFVp2cD3VNK6itqfOHZFjo6NfF+ky8v6iSkfGEdHQxMrzFitPSOjopIjKMyVXdAfG4QC4FI6Ohmk0BN5AGGmOwqSNTFpVblHR0I+GPgouWvkmkYYse9pBjCyKR8jotHHMrRLktt7L6yRaoiR5IAEdHRxwCx2HyzlI+LXqBT9ILYZfvxjo6OAEJK7uEe5mDVrMoPIgH51jo6EbGRANiSK19jKr/An/ABizLkKtlAHICg8hSOjoV02FJH32d6UG71iHHyRbrT0tHR0D2EH+wBjo6Oiwh//Z",
+ school = School(id = 1L, name = "고려대", imageUrl = ""),
+ artists = listOf(
+ Artist(
+ id = 6L,
+ name = "뉴진스",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ Artist(
+ id = 1L,
+ name = "BTS",
+ imageUrl = "https://i.namu.wiki/i/gpgJvt_C2vKJS4VA4K_Vm57Y5WoS83ofshxhJlQaT4P9Tu0N96vZ2OcdeAN7ZtRAM26UyyQs3sualkKk6i_SrRMvwVKrU015XJqzJ7wKRbOub_oUAxPSFre_8D5De3oy-fCxL0uZ-HGvsWxIX57yrw.webp",
+ ),
+ Artist(
+ id = 2L,
+ name = "싸이",
+ imageUrl = "https://i.namu.wiki/i/VH58lI8f-y8QSoxFH9IAjjCobySN0lflZ4rMy6Un7qawUwAyi9UfeseZWCzxH-lQeZk7q_eUyTHGlZBAPqSLWliIKWYDLaAgomVtOyAQg60aCpF3oNTBOgUe_hig3rbHW-YAgoj95Fww3MCToyM6MA.webp",
+ ),
+ Artist(
+ id = 10L,
+ name = "마마무",
+ imageUrl = "https://i.namu.wiki/i/Mre8tXnE40mB9_UwXIwASMEAUSVhHvyjJxXq-lQo40C3bLWYfxXBeai8t6TugyomPjFgxL3VfDA2zn65HlzqPXgTKlvdRl1gJ6PGZLxYYk8Uhk8L6va7zm_etSK5UzVLE56fUATqUCq-6tRQXigmYQ.webp",
+ ),
+ Artist(
+ id = 11L,
+ name = "블랙핑크",
+ imageUrl = "https://i.namu.wiki/i/VZxRYO8_CXa2QbOSZgttDq5ue5QEu_Fbk1Lwo3qpasLAfS802YExcnmVmDhCq3ONF0ExzhACz_YkZbxOGmIfjuPDZnFo7i0pWaT05NluHRHGfp9NqsAT6WBNb0k5KecOyDvakXk0VH2fUo4ojSwC6g.webp",
+ ),
+ ),
+ )
+ }
+ }
+
+ override suspend fun loadFestivalDetail(
+ id: Long,
+ delayTimeMillis: Long,
+ ): Result {
+ return Result.success(FakeFestivals.festivalDetail)
+ }
+
+ companion object {
+ private const val LAST_ITEM_ID = 27L
+ private const val DEFAULT_SIZE = 10
+ private const val DEFAULT_LAST_FESTIVAL_ID = -1L
+ }
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/repository/FakeFestivals.kt b/android/festago/data/src/main/java/com/festago/festago/data/repository/FakeFestivals.kt
new file mode 100644
index 000000000..02d73c3a3
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/repository/FakeFestivals.kt
@@ -0,0 +1,523 @@
+package com.festago.festago.data.repository
+
+import com.festago.festago.domain.model.artist.Artist
+import com.festago.festago.domain.model.festival.Festival
+import com.festago.festago.domain.model.festival.FestivalDetail
+import com.festago.festago.domain.model.school.School
+import com.festago.festago.domain.model.social.SocialMedia
+import com.festago.festago.domain.model.social.SocialMediaType
+import com.festago.festago.domain.model.stage.Stage
+import java.time.LocalDate
+import java.time.LocalDateTime
+
+object FakeFestivals {
+
+ val festivalDetail = FestivalDetail(
+ id = 1L,
+ name = "부경대 대동제",
+ startDate = LocalDate.now().plusDays(7L),
+ endDate = LocalDate.now().plusDays(10L),
+ posterImageUrl = "https://mblogthumb-phinf.pstatic.net/MjAyMzA1MjNfMTMx/MDAxNjg0ODIwNzY5NzQ5.MuYItN1HCOQUcADB6B7ua0SO9Au_QNNk01-6yZkcTH0g.wxSjluY-Glq20JIojs7OuScLQWh6c_sQsoW5xXqiM7Ag.JPEG.chummilmil99/SE-126908ba-0f82-4903-91c5-695db78a98e9.jpg?type=w800",
+ school = School(id = 2L, name = "부경대학교", imageUrl = ""),
+ socialMedias = listOf(
+ SocialMedia(
+ type = SocialMediaType.INSTAGRAM,
+ name = "총학생회 인스타그램",
+ logoUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Instagram_logo_2016.svg/2048px-Instagram_logo_2016.svg.png",
+ url = "https://www.instagram.com/25th_solution/",
+ ),
+ SocialMedia(
+ type = SocialMediaType.FACEBOOK,
+ name = "총학생회 페이스북",
+ logoUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/5/51/Facebook_f_logo_%282019%29.svg/1200px-Facebook_f_logo_%282019%29.svg.png",
+ url = "https://www.facebook.com/23rdemotion/",
+ ),
+ ),
+ stages = listOf(
+ Stage(
+ id = 1L,
+ startDateTime = LocalDateTime.now().plusDays(0L),
+ artists = listOf(
+ Artist(
+ id = 1L,
+ name = "뉴진스뉴진스",
+ imageUrl = "https://i.namu.wiki/i/gpgJvt_C2vKJS4VA4K_Vm57Y5WoS83ofshxhJlQaT4P9Tu0N96vZ2OcdeAN7ZtRAM26UyyQs3sualkKk6i_SrRMvwVKrU015XJqzJ7wKRbOub_oUAxPSFre_8D5De3oy-fCxL0uZ-HGvsWxIX57yrw.webp",
+ ),
+ Artist(
+ id = 2L,
+ name = "싸이",
+ imageUrl = "https://i.namu.wiki/i/VH58lI8f-y8QSoxFH9IAjjCobySN0lflZ4rMy6Un7qawUwAyi9UfeseZWCzxH-lQeZk7q_eUyTHGlZBAPqSLWliIKWYDLaAgomVtOyAQg60aCpF3oNTBOgUe_hig3rbHW-YAgoj95Fww3MCToyM6MA.webp",
+ ),
+ Artist(
+ id = 10L,
+ name = "마마무",
+ imageUrl = "https://i.namu.wiki/i/Mre8tXnE40mB9_UwXIwASMEAUSVhHvyjJxXq-lQo40C3bLWYfxXBeai8t6TugyomPjFgxL3VfDA2zn65HlzqPXgTKlvdRl1gJ6PGZLxYYk8Uhk8L6va7zm_etSK5UzVLE56fUATqUCq-6tRQXigmYQ.webp",
+ ),
+ Artist(
+ id = 11L,
+ name = "블랙핑크",
+ imageUrl = "https://i.namu.wiki/i/VZxRYO8_CXa2QbOSZgttDq5ue5QEu_Fbk1Lwo3qpasLAfS802YExcnmVmDhCq3ONF0ExzhACz_YkZbxOGmIfjuPDZnFo7i0pWaT05NluHRHGfp9NqsAT6WBNb0k5KecOyDvakXk0VH2fUo4ojSwC6g.webp",
+ ),
+ Artist(
+ id = 4L,
+ name = "AKMU",
+ imageUrl = "https://i.namu.wiki/i/7yRF8Yrk9kdQxzETNO8TQp9jJpQENVUGbj-4YwB-xdVmJWoTAY7MgVA6G72Z-xmunPG0Zd3WTN_EsTwsx7oNFIO-yl0nHmaIU-ZRCpyhzVE5L9y8Sb9gkAKVt_jZBtgvVrOjw1UQq32gQsYaoS1jsg.webp",
+ ),
+ ),
+ ),
+ Stage(
+ id = 2L,
+ startDateTime = LocalDateTime.now().plusDays(1L),
+ artists = listOf(
+ Artist(
+ id = 3L,
+ name = "뉴진스뉴진스",
+ imageUrl = "https://i.namu.wiki/i/-GuxB5nI9Q-a5W_nAJEapwdUzCLyFShWJfmUfZk04cW_fFC485TRD6UlzGQCBnFpJegXBaa4WO-PThNom_7wlosOiXgb-k3-wgUr3PkyX89PU3RCschmgQ0FmS1ClOK3ph4ztAd55YWWlhk7Gm114w.webp",
+ ),
+ Artist(
+ id = 5L,
+ name = "뉴진스",
+ imageUrl = "https://i.namu.wiki/i/GdMUzQlsrAXyF5zlgqRR0lYvAGnFghBbLxqTZK_mzLvV0LYPNQdaak1ezYtKqSNBA7UaINkrMNqncRkxThI8j2IEk2qcXJ3bLqIllRexenai641g-uvxCxFcDa9doCy0kTnMLEp5gad8Ze2fLDDBvg.webp",
+ ),
+ Artist(
+ id = 6L,
+ name = "비비",
+ imageUrl = "https://i.namu.wiki/i/JlXBTAah7fOILgmvAQf5bW4yWbS082qw6XtV36g4a-2g5TrwTRaUf95r1YnEYi6dt_rf3o9YuRN2qVl0pdgIW5d6-DeYg67KwaSrqu3_MkUwQItlsrSLqDjm1G0jW-Z5mzQ2aOTU4ZvyE1hpSokIOA.webp",
+ ),
+ ),
+ ),
+ Stage(
+ id = 3L,
+ startDateTime = LocalDateTime.now().plusDays(2L),
+ artists = listOf(
+ Artist(
+ id = 7L,
+ name = "TWS",
+ imageUrl = "https://i.namu.wiki/i/rVwKhMepUc-b-hRa2Nc6mIJRO0eTfgxyAEwVS5XfADNRhQhYJdSg8ke3o6VZd3rLyNasMlGjuXJWqHDoD_Z24o3dBzkaf7gqhCc89XoCKOiII4P-eilx46XHOOTfd2eaonCVNQevsAVl0l5WIWaI5Q.webp",
+ ),
+ Artist(
+ id = 8L,
+ name = "소녀시대",
+ imageUrl = "https://i.namu.wiki/i/snftu-N6Op26hU4HITlraWW6Q_WiSXqhRX2NOhQadzI81RPC7054_mi-evsqRTdRe9nKcBEF-Ugji4vtWunmtiEY1v319tHhIVestCkcSJ0MZF6KbKOScoDjOypW7WPa58goYA-vX5D8baIa2UYFZg.webp",
+ ),
+ Artist(
+ id = 9L,
+ name = "르세라핌",
+ imageUrl = "https://i.namu.wiki/i/Zbm1DseL0fjSd9H2uLrfL9SpBLPYQe7j4S9BPI2wdTw9G_Gykifyw-Nil8yVZglxxW-CRQt15b-tMdrvfuUiSW9mm2ZEBf8sQQQgp9wZmZhe8neg_5A6ehJ6hYLATAqvnOw157aODDq4qU1J-kv-bA.webp",
+ ),
+ ),
+ ),
+ Stage(
+ id = 4L,
+ startDateTime = LocalDateTime.now().plusDays(3L),
+ artists = listOf(
+ Artist(
+ id = 7L,
+ name = "TWS",
+ imageUrl = "https://i.namu.wiki/i/rVwKhMepUc-b-hRa2Nc6mIJRO0eTfgxyAEwVS5XfADNRhQhYJdSg8ke3o6VZd3rLyNasMlGjuXJWqHDoD_Z24o3dBzkaf7gqhCc89XoCKOiII4P-eilx46XHOOTfd2eaonCVNQevsAVl0l5WIWaI5Q.webp",
+ ),
+ Artist(
+ id = 8L,
+ name = "소녀시대",
+ imageUrl = "https://i.namu.wiki/i/snftu-N6Op26hU4HITlraWW6Q_WiSXqhRX2NOhQadzI81RPC7054_mi-evsqRTdRe9nKcBEF-Ugji4vtWunmtiEY1v319tHhIVestCkcSJ0MZF6KbKOScoDjOypW7WPa58goYA-vX5D8baIa2UYFZg.webp",
+ ),
+ Artist(
+ id = 9L,
+ name = "르세라핌",
+ imageUrl = "https://i.namu.wiki/i/Zbm1DseL0fjSd9H2uLrfL9SpBLPYQe7j4S9BPI2wdTw9G_Gykifyw-Nil8yVZglxxW-CRQt15b-tMdrvfuUiSW9mm2ZEBf8sQQQgp9wZmZhe8neg_5A6ehJ6hYLATAqvnOw157aODDq4qU1J-kv-bA.webp",
+ ),
+ ),
+ ),
+ Stage(
+ id = 5L,
+ startDateTime = LocalDateTime.now().plusDays(4L),
+ artists = listOf(
+ Artist(
+ id = 7L,
+ name = "TWS",
+ imageUrl = "https://i.namu.wiki/i/rVwKhMepUc-b-hRa2Nc6mIJRO0eTfgxyAEwVS5XfADNRhQhYJdSg8ke3o6VZd3rLyNasMlGjuXJWqHDoD_Z24o3dBzkaf7gqhCc89XoCKOiII4P-eilx46XHOOTfd2eaonCVNQevsAVl0l5WIWaI5Q.webp",
+ ),
+ Artist(
+ id = 8L,
+ name = "소녀시대",
+ imageUrl = "https://i.namu.wiki/i/snftu-N6Op26hU4HITlraWW6Q_WiSXqhRX2NOhQadzI81RPC7054_mi-evsqRTdRe9nKcBEF-Ugji4vtWunmtiEY1v319tHhIVestCkcSJ0MZF6KbKOScoDjOypW7WPa58goYA-vX5D8baIa2UYFZg.webp",
+ ),
+ Artist(
+ id = 9L,
+ name = "르세라핌",
+ imageUrl = "https://i.namu.wiki/i/Zbm1DseL0fjSd9H2uLrfL9SpBLPYQe7j4S9BPI2wdTw9G_Gykifyw-Nil8yVZglxxW-CRQt15b-tMdrvfuUiSW9mm2ZEBf8sQQQgp9wZmZhe8neg_5A6ehJ6hYLATAqvnOw157aODDq4qU1J-kv-bA.webp",
+ ),
+ ),
+ ),
+ ),
+ )
+
+ val progressFestivals = listOf(
+ Festival(
+ id = 1,
+ name = "뉴진스 콘서트",
+ startDate = LocalDate.MIN,
+ endDate = LocalDate.MAX,
+ imageUrl = "data:image/jpeg;base64,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",
+ school = School(id = 1L, name = "고려대", imageUrl = ""),
+ artists = listOf(
+ Artist(
+ id = 6L,
+ name = "뉴진스뉴진스",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ ),
+ ),
+ Festival(
+ id = 2,
+ name = "아이브 콘서트",
+ startDate = LocalDate.MIN,
+ endDate = LocalDate.MAX,
+ imageUrl = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAoHCBQUEhgSFBQYGBgYGBsaGBoYGBgaGhoaHBsZGR0YGhkcIi0kGx0pIBsZJTclKS4wNDQ0GyM5PzkyPi0yNDABCwsLEA8QHhISHjIpJCk2NDgyMjIyMjIyMjIyMjUyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMv/AABEIALcBEwMBIgACEQEDEQH/xAAcAAAABwEBAAAAAAAAAAAAAAAAAQIDBQYHBAj/xABHEAACAQIDBAcFBAgEAwkAAAABAhEAAwQSIQUGMUETIlFhcYGRBzKhscEUQlLRI2JygpKywvAVJHPhM2PxFiU0Q2Sis8PS/8QAGQEAAwEBAQAAAAAAAAAAAAAAAAECAwQF/8QAJhEAAgICAgEEAgMBAAAAAAAAAAECEQMhEjFBBCIyUWGBE3HwI//aAAwDAQACEQMRAD8AuKrTgFEopVdjMA6UKKjqQBR0Qo6QAo6KjoKBRlo40kzGlNISTDDwP+1NIB4Ak68Oz61UN78Firl9WsLcKC2AcjEDNmfkCNYire7gCeyqPvZti6mIVbVx0Xo1JAMCcz6+kV0+lUnP21+xrst+xkdcNaW5IcW0D5jJzZRMnmZqr74Y67bxCqlxkHRqYViBOZ9YHPQVaNkXGbD2mYksbaFieJJUEk1Ud9BOKH+mv8z1Xp1eZ3+Qj2WNrz/4d0mZs/2bNmk5s2Sc09s1E7oY249xg7u4CEwzE65l11rtvXwNnZIOuGA5fgFRe5hi637B/mWrUV/HN15KXxZPbx4hkRArFSzEypIMAd3iKr+wcZdGMRLlx2Rw6wzsROUsDBPHq/GpDeS7NxV/CvzP+1cps9HiUMQUdD5ELPwJp44pYqa20yER++WLvW8WypduKrIjAK7ADTKYAPatW/GYuMGboME21IPewAB9TVX3/s/prb/iQr/C0/112bQv/wDdNvX3ltp/Cw//AAaJQUoY3+aGmP7IxNy4LrF3OW00SxgMeB8dDVNwuPxtxslu7edomA7Tpx51c92BOHu3PxLHopJ+dQ27FnJjx2FHI9BWkOMXPS0VkVu0DZNvaIv2+k6fJnXPmZiuWRM68Kv1c7YqDBGvIDUx2nspKXbk6rp3RPxNcOWTm7aSJo6qQaJbk9x7DxpVYAFRGjoGmITRGlURpkiTSTSjRUwEGkOKcNJYUwGMtCnMtCnYDwo6FHUgGKOiFHSAMUKFHSAFGKKjoAVSHHPs/s0ukXOB8DQUKIrPd80/zY/01j+J60OqHvev+b/cX5tXX6J/9P0Cey47GEYaz/pJ/ItVPfFZxQ/01/merfssfoLQ/wCWn8gqrb1JOJ/cX5tT9M/+z/Y49ke2z8SLec5+jyg+/wBXLGnVzcI5VI7opFx/2D/MtT18f5KP+Sv8oqJ3WWLj/sf1LWryuWOVlp2mN7U6+JK96p8h85p3b4y4gkc1U/T6U1hevige25m8pLV2byp+kRu1I9CfzpXUlH8EVsa30sdJYt3Byb4Ms/QVCYq7OzbNvmLr/DOf6xVrZBcwag/hX4HLVR2haKKts8Fd2/iCD+k1Xp5WlF+GDjqyy7sWcuB/a6Q/Nf6arl3BXHIFrNnExlbKY56yKuODt5MIqdlrXxKyfiajtgENcDcwCD6cazjkacpL7LjuLTI/d/ZuLXEBrwuBMrTmfMJjTTMatqJHOfSfhT0UVc2XK8kraX6M1obZabS518vaJHrBp4010fXDdgP9/Os0Oxw0mnKRUiCojR0DTEJNJNLNJNUISaSaUaSaACihQoUwHKMUKMUgDoxRUoUgCo6jdsbdw+EUNfuZZ4AAsx8FGtR1rfTBNbNw3Ig+4R1yCYDBeemsDXz0pFKL+iyClU1YvLcRbiMGVgCrKZBB5ginaBApvEtC05VA3z39+z3ThrNoOyf8R2JyqSJygDiQCJM91K0nbGuy/wBVXb2x7t2+bltAVyqJzKNRM8TUNsL2lLcvJZxFtEDwBcRmIVjAAdSNATznTnzI0G2ePjWmPI4vlEbQnB2yttFPFUUHxCgGoLbey7ly9nRJGVRMqNRPae+rHVX9oe2mwuDbozD3D0aEcQIl27urpPIsKI5HGXJBHsLam8mFtWega4WfILZCKWAeACpYaEgiIBNN7pYlLhuPbYNClSODBpESp1A048ONZJgbVxwgQ5mZuE6zp8hFSuy9o3MFjFuEzDQ4BBzITDAj4jvApRzNJr7NFFpGn4DZ1y3cDumgmdQeIjke+uzamEe4FCiSszqBoYg6+FSeYMsgyCAQe0HUH0pFnj3jTyP5Vo8rbvyie9nPhMK4w5tsIaGAEjnJGo76j8Vsl7lsBlhxpxGoqY2hjUsWnvXDCIpY9unIDmTwHeaxza2+mMvPmW41tSYFu3IAHKXHWdu/4CpjkknYk2bNft/o2RfwkD0gVWtnbNxFtgTb04HrLw9apWxd+buFuKt641xCYdWJZlB+8jGTI7Jj51rdi8rotxGDKyhlI4EESCPKnHK4JpU7EpNEKdm3BdRxOUOpPW5Ag8JqeoUKicm6sJS5DGJbQDmTp5a0dpTzoXbyLqzKo/WIHzpaMCAQQQeBBkHwNTeqDwHSaVTWIu5BMSeQpWCViqBrgt4xjOdY7IPLvFKwuODsUJE8o5+XGaUZpjljktnWaSaUaI1ZmJNJpRpJpgFQoUKAHBShSRSqQB0CQBJ0A4mgKh97L5t4O4wMaAT3EwfhpSbo0xx5SUfsyrejb32nGsoAZA+VdJYxpp8vXtqz7P8AZ+zWukuPkd1IyQTlnUEsTx4fGuHcrZlu9iemCBQltVQyG/SgKGdv1tQfOtDs2byW2BuK7z1YlRzgHMWg9/wrl5Nnc7jp/wCRVfZ1ce1exOBuHrJlcCNOOViPGUNX+qRsZH/xhnuBVb7IVYBg3W6RSJIAnqjsFXet4NtbOPMqkMY7FLatPdb3URnPgoLH5VgyNdxVxmWWuXHJZV/ExLH4k1uW2rJuYa7bCl86MmUcTnGXy48az7cTd/EWLnSm0BlOQ5+qy6SzZCsniBxGnxU51pDx4+Wyu7c3au4VFa8JVhqdIVjPVJHAxWubr4g3MHZuMZZ0Qse05RJqG33a4+HvW+ilOjYl9IBUFp48o7OYqT3L02fhxMxbUadwiiErux5YcUmTlZ17WEDNhbZaAelkSByTKTPAZonumtFrDvaJjHvY64NSts9Gg7AupgdpYn4U5dGUOy9bB3VwyW7V6E1QFic2aW45XzAKNezlVc3x3dt2QMXbZQGbqrLsWnMT1mYjtOgHLxqy7DR7eDtpe6U9RSr2hcZhIEoyp1jBniI4VQt9NsPcvrhsrqlrX9IZdmInO3ZodF9ewYrs7JNcTXNgXQ2Cwz8jZtz3dRfkakCp0I4jjVN9lm0WuYV7D6i02Ve5WEgeHHyirmog11J6OO6dFc9oub/D3ABMvbzR+HOs/GKp42b0mGD27JzKYEgwTEFZUHUMpHl31qeLw63LbW2Ehh8eIPiCAfKooY3o7nRZG4CAqrqeESWAjQHhwNZTN8O7RlGP3SvtctA2jba64VpgqJEloHMCSQJBjkeqNL9n+ZcI2HZsxw965ZzDmFIYemaI5RXLvpibiWi9mOktguCQGyhecHSeykey0j7G/WLOb7s5bjmZbZnvkQZ7ZpY7bYsqSSLpUDvjt4YLCNdEF2IS2DwLmdSOYABPlHOp4ms99rGGa4uEtggB7zKZ/E2RV+bVb6MYq2VnAbKv40NfGa51GLu5km5BhR3A6wNBFde523bmFvdHcJ6MtldT90zGeDwI59onuq+bES5bwot27Qt5BlRTAJ7zq2pPMk1nG+NprOJJvKFN5Q/VOYA+6ROUcwCTA96so97OqUdM2auHaN1VgsQBrxrm3UxbXcFZuNq2TKx7ShKSfHLPnTu3cGtyyQ06a9UwfI8jpWklaOaGpbOLEbRVAsIz5/dygmfCPrpXM+AK31ug5QCGIMz2EDWOcU7gscqZLVpGZGEoVkzwBDMYCwZnUnXhSdquWuLH3HQuB3mI+vlWF0drjosE0RrmwV2Rl5j5cK6jXVGVqzglHi6EGiNKNJNUSJihR0KYCxShRBqUGpACqL7RdrLlGFUyT1njloYB+fpXZtP2g4azce0EuOyOUYqEC5hxAJaTBkcOVZtt7bIv3muqpUMTGYydf+tROLa0dHppRhLlLx0Se4nSLiyUXNKHMO3KRpPAHX1FXTa227Vhema5cldeiKdaToBmZZVZ5zFQ24+2Vaw6W7NlcVb+/kUPct9oAAlhwPD7p1JNT+0sIb9r7NjgzsMrzZ6s5pgfhMRxbs865pRcWdSyKW0is+z/ABdy/tJ7znVkdnHd1VUDuA0rVKyjBH/CsYCQz23TqORBdDl9LimAR2jlIrUsDfF62ty2ZRhKnkfDz0rbG1Rh6mDtSXVCMapa2ygkEj7pyniOY1FVrdi1iLVhrd4NnZuqbjkk/qSSTpHLTWrrYWAZrk2hg7BBuXLaSqMWdkUkIOs2vZpPlU5I29E4siiqZQd/tv8ARYU4WQb1wHMFObKhOrMeU8APyq07pWDbwOHQ8RaSfEiT8TUXvFulaxtm3cwrojBcyOo6jq/WhgPnxFT+xcJct2Ldp1WURVOVpBIABIkDTSqgqWycsuXQ9j8ULVl7xVmW2jOwWJIUSYkgcBWN7vImN210rt0aNce8qkyWyHOEkc4EnuU1tz2RcttbYdV1ZWHcwKn5153tLct3wtsk3EuZEy8S4bKIHedI760UUzNOjbtpbZXDuAbDsjkBGQZlZiJiB7v1186h7QdiWblhscVFi8HEGWPSqFhVK8Ec6DgfcA4HTRMNhMttFfLmGUsB7gbScoMkCeHHlUXvzgen2diLY1ITOsccyEOAPHLHnWXBmryL6M49le2eixRwzgEYgwH4FXRWIHYQdR4xWuuIPhoawHdLBvex1m0hysHVs3YLZzk+MLW6bWw6XSthxKXG64BKyiqXIJUgwWCgjmDHOqgyJJB4faVq4X6O4r5DDlDmVTxylhpPdMimcdZa4Ue1dyH78Kr5khiMvY0ka6iJ0NcOI2dbt2mt2ycPYthnY22KQfeLFxqAOMcT4aGD3V2muNzoi3YTKzB8g97PqCnaQSZHHh2VMnJ3S0axjGNNvZaLmxke21os3XEM4Yhz2nN/YprDLg8HhjetuosDIpdSbg4i2CWWZMnU9+tN7NxOEdMQj3VdLbFLyXGXIpA11bTKRoeUqe+sl3mxVkYrEDB3WNi4QzKmZLZbQsMmgZQwkGI1EcJqscWuzPI03p2bhhr6XFD23V0b3WQhlPgRVI9rzAYO02aHGIUp26I8ny0+FUDdzb13CXke25CF16ReKusgGV4ZomDxHbTm++8Yx91biqyIgyojEE6kln00BMKPIa08mkTBW7L9u/vMMTbRulto6iLquSDI+8AGGZTxFVP2iYjp71vkoQwx0zSRLAHloI7fDU2X2b7vquEXFMAzXmJCkA5QpKqR3mCfMVG+1LAvntXVtuUCvnZVYquqQWYCF7Neyud3Z18k40WH2aYrPgujJ61tyD4NDA+Bk1Zse0W2HMiAO2sV3X3huYK41xVDqy5WQmA0aqZgwRJ9TWi7I2y+Mt9N0bopbKC2UqSPeykch3gVq5e3Rgoe4Xgtj3Fc3FuOikyyrBDGQCesCFPeONTD7PC22I7ZJPEn61J4Z7YQKXThr1l/OixGKsBMpuWx3F0BJ5Djqal47L/lfRBJcKvI5aH51KLczKGHA8+yDqPn6VzYHC9JOaNBxGgPYfSacd7gY2+jAUaZs2Y6k/dA0HPU0Y1KtCm43s6TSDS81EXNdJyiKFKzUKYChVG3x346Bnw2HANxdHc+7bJ+6o+8w7eA7zMXkVlG9e5OLOKuXMOnSJdcuIdAysxllYMRzJg9lS3Q0UhrnZr/AH203bOY9g7ateI9neNWw15smZRmNsNL5RJOo6sgcgdfHSqzhoOhHIxHMkjjWcpM2xRi3sVg75tXkugk5GVtCVMAyQGGokSPOvQlhJth7ZV0uICDoHcEFh3ElWOuledV7a3n2eXzd2ZhyT7hZD4I7KoH7uWjjrYnOm+PRz74YfCfYHfEOFAl7JEFxcKyFQT1i3MTEEkxEiT3KxCvs/DMoMG2BHesofiDWA7RxLvcYuzMoZ8oJJCgsSQoPu69lah7INt57T4FjrbBe3+wzddfJjP7/dS4pA5tqmaUNAT6edUz2n4+5bwLi2Oq7pbdv1TJZR4xlJ7DHhcHML3f3pVZ9otjNsu4I1U228+kSfmaaWyCX3cxq38JZvKAM6LKjgrAZWUeBBHlUk7xpzqoey6/OA6Pnbuuv8UP/WatmmfvI+UUNUwHBwqgbP3Rc7Xv4u5K20udJb5Z3dQ8j9VCx15kAcjV/Nc+IukLMhRHvHlzOnrqezhVxsTIjfDb4wWFa4YNw9W0v4n4yY+6BqfTmK6t39uWcZZF60e5lPvI0SVYdvfwI4VkuAweI2zjXzXn6K2WIdh7iEnKqqIGZoE8PdJPCKTa+1bExqm4M1ttGCnqXrYPETwdZkTBB7m1dAaPe3QtfbrONsqqBWc3ECgAnK2V1/DLe8BxnlrMJ7SdqX8NisNcsPlKJdcyJUxlBDDmCNPPiKveBxSXbaXbbZlYBlPaCJHhxrNfbRcGfCqOOW6e+CbYHlofShaY7squ3d+MXjU6G4URCZZbalQxHDMSxJE6xMTFWXc7Phdj4zGIxDPCowElSpySJ0MFyZ4ad1ZsiT5Vqaqf+zA94yeKj/ncD+qIgnsBNJAzL1XjJ146njHzbX50l6F760aa6nsp/gX5O3B2c9xLevWdF0EnrMBIHnWmYTczZuGQPjLgdombj9Gg7AFBE+c1TPZ7huk2jYXkjM58ERiD/Flq1+2F7ZSxZCg3CzPm0lUUZSo7AxYH9ys8hcHWi47MbCZRbsKiISCvRqoRsw0cEaNw468INVNPtd3abu190w9i4EyalbmgPRhZhiw1Zj7oNU3cfbS4bFh7zMbYtXEIYkwMmYBRyJZFUR2io7H7fxN24t1rhDJOXIAiqWOduqoAJJiSeMa1Lg0jWMlZq97YWyb1zojatrcjNktMyNl5NkQgRr2V1bdwa4TZF63h5QWkLISZbV8zEntOZh51QdwsRdubStPqxy3M0cFtlZ8lDZQB3gVrO3cF9owt6wDrctOoPYxHV+MURVETlujza0HU6k8TT+EcK6uORB9K5QDXRg8M9xwltSzHgB9ewdpOgrSyKN/3Ru9JhEf9UJPbk6pPfzqWYRbbvb8q492cMLWCsWwR1baSV4M0asPEyfOu7EsIjnM/P+/KmiX9nKaSaUaSaskKhQoUAOijoqOkBxbZuZMLef8ADZuH0RjXni2SIivQ+17SvYuW3JCujIxWAYZSpieetYvtvdi7hzmWbianMqmVAj3wJy8eMx4UqsadEGK2n2UBhs3UadK5XvGk/wDuDelYsmug1J0AGsnkBXovd3Zow2EtYfmiAMRzc9Zz5sWNKXQ0efdu4XosRetEe5duL5BjB9INObC2xewN9cRb4kcGGjoTBHhK8RwIrY9ubjYTFO911dbjwWdGI1AAnKZXkOVV3eDc23ctJZQhbllQqO0w6iTlcDvJMgaEntpOLl0NSS7LjgN4UxCqqI6s6hgcpK5SJLBxpwPOOVdG9tstgcQAJi0zR+wM/wDTUJu1i7WBwiYbF4q0HScsvl6pJIADQSBJExT+0N+NmZHttiQ2ZGUhEuOIII4hY+NJ6YkQvsoxcnEWuEBHHnnVo9E9av8AbHXJ5AQB58fhWFbpbx/YMQbrozhrRQqrBZJKMDJ5DKfWtD3M33+3Yh7LW1txbzIM5YtlIBEwBwIMR21Uu2NFtu3glwzwZQ08gQY18QR/BVJ383vt2P8ALC10wuI2YZ2QBD1Yletr1uEaDjqate2BDcdWAAHcCdfjWF714zpsZdaZVWyL4J1dPE5j5018ReSQ2XvrfwqNbw1qxaVmLmFdzMAas7sTAAoY3ffF3svTLh7uQyufD22gnmAarQoE0Ds1bcLfG5fa5burbzKqsgRcgImCYGmhy8O2qf7R8XcuY9jcULlRVSJgpqwIn9ZmnvFRGw9onD4hLw4KYcdqnRh6ajvArV958Vh7eCa7etW7pBi0HVWl2nLBPAcz3A030LyYzhiIOorQtsb3YW5slcHaZxcAtqZQoGgguZAygHrad9Z1lkyfy+A4V0i4YAhSB2j48aSsboZZhESJmmu6nHWeXpU/uXsdMRfPSEFUXMUIkPMrBM6QSDzopthaLD7IMFmxV3EEaW0CDxc/MBD6057WSFxllgwlrJBHZDmCe4kn0qz7Lv4HZWHIa5lLEu2Ydd2UKuVFHIaad81ku8G2HxmIfEPpOiL+BQTC/Ek95NTJXoadbI5zy+tLJhIPMz5AfnSMtBhTpis072N4ckYi+QIlLanmIDO48NU9K1BdSB/elYx7Mt5LWFe5ZvuER4ZWM5QwEMCRwkZdf1e+pffH2iWzZbD4NizOCrXAGUIp0IQmCWPCeA5VHSG9sznbEDE3oiOmuRHCM7RHdTC4l1VlUkBhDRoSPwnu7qZFA0/AHpTZuIRrKOhzIygpB0IIEflTYxgZ2UGTMk8ieEDuFYhudtG4mJt2ukbo2LLkLNkllJkLMA5gNa1bZfv+VbRppszlp0T1JNKpJqQCoUJoUwHBRzRCk3eBikBG7VxIPUB8a6thWh0ZfmzR5D/eahxhmLwRzqy4C1ltqvZM+Mk1c9RoUdsYxGw8LcuLcuWLbOrBlfIMwKmQcw1Oo4GpJeNEOFBRrWBYTjWobbVrUOPA/T61L23zKrd0HxGh+NR+3ri28O9x2VVVSZYgCRqBJ5k6RVQdMUkY/vriEOPQMoYKqKwmJElon96mMVZTLaZUAPU1gcGRn8eY4/lVdxWMe5cN12lmbMT2Hjp2Ck9M50zNAiBmOkd1Upq7Hx0W9MPafD3WdVACMQ0CVYCQVPbMDv4VXd39rPhMQmIQSUOo5Mp0ZfME+Bg8qjsxPEnzJohSnPk7ocY8T0TtfEq+EXFWzKZOkB4EqVzDz4V57B0nmdTWqbrbTz7AxFsmTZt3k8ipdfgwjwPZWToeVJPwFDopLNRspoZaYAAqV2nt179ixYbhZUgn8Te6pPggA8SajDTS8SKAHFpa0gUoimQKFWf2f/8Ai3/0X/nt1V0q07gL/mXbn0ZUDtLOn5VS7Bl9x+59rHqr3XuIUkJkZAIMSSGUzqO0cKh73smT7mMYftWg3ycVpGFtZVVewD8/rTris3LdlLoyp/ZRcHu4tD2ZrbL8Qxio3FezLHLqpsv2BXYE/wASAfGtnJpTUuTGeesTu7iMIt18Vhyo6IhCYZczOiAhlJGYBiRryNVwVtXtfcLgVHNrqKPCGcz/AACsWqa2Ug6FCjpiHMHdNu6lwaZXVvRga3fZFvrFvKsCYaV6A2A02lb8QB9RWkHpkz7RKmiozSTTJCoUKFMBwUoUgUoVIHNtLEizZuXoHUR3/hUtHwrz7htqX7dxr1u7cR2JZnVmUkkySY46z61t2/LkbOxJGhyR5FlB+BNYMBUS7LgWux7Q9pJ/54Yfr27Z+IUGnbntJ2kRpdRe8Wrc/EGqeaFIZOPvhtAz/m7okkwrBRLEkwFAA1J4VFYzaF27rdu3LhHDO7PHhmJiuanLVlnkIpbKpZsomFXUsewDtpFCFNGtEFNKVDFCJCoCjUa60ZSmkFls3DxZC42wQSlzB3mOhIVraMVY9ghmE9pFVS0hYhVBZjAAAJJPYAOJqw7mC709xLamLti9YZ4OVc9slczAEDrKvrUrsrdLG4O/bvkIyjjkbXKdCQGA4cdOMUrd0hpeWcFncnaLLmGFaO97an+EvIqDxuHe05S6hRhxVhB/3HeK9D7MxIuWww5io/H4BOnF0opZhlJKgnSSNfM0v5GaLEm6MDVCRIViBzCkj1ArnBGbQ16D20xFgqDBYZRHIt1frWZ7Z9njoyfZrgcOGMPCMCuXTMNGme6mpN7JlBLSKcKNjXTj9nXrDi3etMjHgCJzfssJDeRNdrbr40Ibn2W5l/ZBb+AHN8K05Iz4v6ItBpV69lGGD4t2PBEDeeaB9fSqVftNbOW4jIex1Kn0atE9jluXxL8sttfi5/KqvQqNRt8SaVQQaUaishiGpT0luPnS3FAGde2Uf5O0f/UL/wDHcrHBW4e1mzm2YW/BcRvUlP6qw8UFIMUdCurZ+DN12UaZbdy4fC3bZ48yoHnQI42r0fg8OLdtUH3VA9AK887OAN+0G903EnwzCa9GsaqIpCTSTRmiJqyAUKFCmAsUYpNKFSBG7x7MXFYW5YZmUMAZWJ6pDga8pAqm4P2ZWWUFr9yY5BAPiK0HEe437LfI0MJ7o8KyyG2JJpmX7Y9ntq0pZL76Cesin5RTOM3OtC0ioWDlkDMSTyOaF4cavm3Wkqva3y1+lRl4SVH60+gNOCuLbKnXJRQvYu62EtW1HQo7DUu6hmJPiNPAaUjEYC07vbyKqlChCALo5lgI4cB61NJchJqIw7zmf8TE/QfKlhjci83tjohMX7PbCMlwXbnREgOCVzgkmCGyxl4CInXjVgs+zvZ5T3bh049I0/DT4U7evlrfRngamNgYrPbAPvDqt4jQ1WROOzLHUkZztX2fKuIVLV0qhBJzjOw14LESI7amsNuHgrVtnuB7rATLsVA05KkfGatW1Lf6RG7yPX/pXLtm5+jCfjIXy5/CayTbdHRwildENsqzbtW1tqmUCCIMQec9s8DNdOK2srPkM6aTx9RxFcmcpox6vJuzub8/XvhtoHrvoJ4iQOXCvRxYo2zllJyVMtm7+KKXGtnRW6yeHMevLwqw4xJWRxGoqgbIxwdQR1XRs0T28Y7j2VecNiAyjvFcWeHGTR0Y3yV/RE7Uu5nRBy6x8tB8SKYu3TKE8FcejdX5kelIvD9K58AO7mfnSLuqkd2njyrWEPZ/ZjknWT+iZv4VbhQMoYSGggEAqZB15zGtSpAC1w7L6yh+7Sl7WxQt2ye7Qdp5CuajeT2QO1HW5dggEJqZiJ5fnT26z5WfKAFY69UAtlkZtPE+tQT2HuEAtCSWeOLseU/h5eVT2z7gST3aV2RhxjRyznbJrEbctW56TMoBAkKz8SANFBPE9lOpt3CSAcRaUngHdUb+FoPwqq4s5ww7Rp48j6104DFLds666ag+kEGufKuNGmKCkmWhMVbY6XEPg6n60MXjbdtczuIHMAt/KDVX3bwtu21wW0VAXkhQAJgawPKureFx0eX8RC+pArNTst4knsi97dpWcdgbtjDszu2QqcjqpKurRncAcjWQYnY+Itvka0xYAHqAuIPesitcmudXy30aYzKR6EEfM10ThUbMYe6XEzTY+7eJxTMltIyxmLnIBPDjr8KuWwNycThnuXLvRkHD3UUKxJLOuUcVAiJ9auuA/wCI3gPqfrQ21iiLbBeMQPGufk3o6P40jDk2Tig4C2LpYHTKjNqDyIEHxFehFaQCeYqs7LEXFA5aelWaunhxORysBojQJoiaYgqFJmhQA6KUKbU0oUmAnFNFt/2T8oorDwvlTePP6MjtIHxn6VH38Vlt5Qdawyd0dWCNps49o3M1yOwfP+zXG7dZfP6UM0szTMn5afnRhMxgcSCB8D9K341jM+V5jp2riejt6cSNK58OuVFHYAD4xRY5GKiRw08O004iyYqcCqy/VPpBzXbsV8txo5wfofpTGJw5SJ50Ww7y3C1xZyhmSSIkqYJHdMie41WauJlgvkWDaTjIp/WX4kCoHal9Wuog+6GY/wAv1pjefbgsolskS91BJIAUZ1JJPKADXPbcPcZwQ0gagg/EeVYYlckdeRKMH/uzoImo7E7OmSpjuIlfLmvkY7qkkQmpHAYAOCTXby47OBNormAwGoDLBzSGVp+Oh8oqY2njWtWwymCBp391dSYcJdK90+v/AEqA3vRrkWrZ1eFH7xj5TXLmm5SO/wBPqNsGy8e1y2Lj8XJbTsmB46AV3LeHf6GmtmYYLbt2xrlVV9ABVlxGEXo+GoFbppJI45tOTYrZLhbKxyEVH7b62X8M6/SoNduizihh3HVdQyNyBJIII7NJnvNWQ9cawQRXP1OzprlC0RA0oZqN0gkU9bwTsJiuq0cTVHOTTGAfK7iNCTw7zNP3EIMGndnYUXGY8CDrHMQKw9Srijp9LKpO/oRsG9kvsjH3hm+JX6CunazZuGsMD8ajdr4TLcVlOUgAAjx5jnq3xNdYtObbZtDHLh41y1TTOp1KznJri2isqrTGVgZ8dPmRXUrTTd4aa93zr0Jq4M8+D4zT/I7svaBF50PNVI/hiuvaDdXzHzFFawSrdRgPfSD+4dP567trWAtsnurhito9Cck00R+yhNwVZKrex2/SeVWSu2XZ5iCJpBNGaQxpDBmoU3moU6EOo1OqaFCpYyn7c33w9q+bGR3ZTlaIVQT3nU+lM4PeSxiwwtK63FgEMBwJjMpBI+RoqFYte79nRGTS19HQvdUJvNt25g3ssiqQyuSrTGhWCCDIPH1oqFb5fizCHyK7iN9sY89ZcplsuRIA7AYnT1rRtiMbmRiIJVSR2EgEihQrPF5KyO6ssmIwiuNawXC7wYnD3HNm6yqXY5TDLqSfcaRPfR0KnL4DGc+09sXsUw6VgewAACe3Sp3cjDg4m7c/AuUfvEifRT60KFTi7Rc3ZrOysOCkkcakrNsKIFChW0uzJETiW/zD9yqPhP1qsXsRnxncivcPgq5QPV58qFCsH2ehD4ImdipLIDyA+VWd1lSO6hQrol2eeUHb+yhcJK6XLal0PbkMlT3Gal9gYwPZB7h8RQoVlP5M6sfxR3YCyGuEnkBU0qihQql0YT+TOTGYNWBMa1D7IOW869qg/MflQoUS+LDH8gtvJ1Qe/wD3+lKw7zb17KFCsH0daHdj4NSgc6yW+DEfSubbuHAOmkihQrqh4OKfyf8AY5hHzCyf1X/+uurbf/CoUK5fJ2MPC7PRDmHGu0mhQrqZwiGNNO1ChVIBjNQoUKsR/9k=",
+ school = School(id = 1L, name = "연세대", imageUrl = ""),
+ artists = listOf(
+ Artist(
+ id = 1L,
+ name = "뉴진스뉴진스",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ Artist(
+ id = 2L,
+ name = "르세라핌",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ Artist(
+ id = 3L,
+ name = "스트레이키즈",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ Artist(
+ id = 4L,
+ name = "볼빨간사춘기",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ Artist(
+ id = 5L,
+ name = "다이나믹 듀오",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ ),
+ ),
+ Festival(
+ id = 3,
+ name = "아이들 콘서트",
+ startDate = LocalDate.MIN,
+ endDate = LocalDate.MAX,
+ imageUrl = "data:image/jpeg;base64,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",
+ school = School(id = 1L, name = "연세대", imageUrl = ""),
+ artists = listOf(
+ Artist(
+ id = 1L,
+ name = "아이들",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ ),
+ ),
+ Festival(
+ id = 4,
+ name = "뉴진스 콘서트",
+ startDate = LocalDate.MIN,
+ endDate = LocalDate.MAX,
+ imageUrl = "data:image/jpeg;base64,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",
+ school = School(id = 1L, name = "부경대", imageUrl = ""),
+ artists = listOf(
+ Artist(
+ id = 6L,
+ name = "뉴진스",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ ),
+ ),
+ Festival(
+ id = 5,
+ name = "아이브 콘서트",
+ startDate = LocalDate.MIN,
+ endDate = LocalDate.MAX,
+ imageUrl = "data:image/jpeg;base64,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",
+ school = School(id = 1L, name = "연세대", imageUrl = ""),
+ artists = listOf(
+ Artist(
+ id = 1L,
+ name = "아이브",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ Artist(
+ id = 2L,
+ name = "르세라핌",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ Artist(
+ id = 3L,
+ name = "스트레이키즈",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ Artist(
+ id = 4L,
+ name = "볼빨간사춘기",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ Artist(
+ id = 5L,
+ name = "다이나믹 듀오",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ ),
+ ),
+ )
+
+ val plannedFestivals = listOf(
+ Festival(
+ id = 30,
+ name = "뉴진스 콘서트",
+ startDate = LocalDate.now().plusDays(1L),
+ endDate = LocalDate.MAX,
+ imageUrl = "data:image/jpeg;base64,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",
+ school = School(id = 1L, name = "고려대", imageUrl = ""),
+ artists = listOf(
+ Artist(
+ id = 1L,
+ name = "뉴진스뉴진스",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ ),
+ ),
+ Festival(
+ id = 31,
+ name = "아이브 콘서트",
+ startDate = LocalDate.now().plusDays(3L),
+ endDate = LocalDate.MAX,
+ imageUrl = "data:image/jpeg;base64,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",
+ school = School(id = 1L, name = "연세대", imageUrl = ""),
+ artists = listOf(
+ Artist(
+ id = 1L,
+ name = "아이브 아아아아아아아아아아아아아",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ Artist(
+ id = 2L,
+ name = "르세라핌 르르르르르르르르르르르르르",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ Artist(
+ id = 3L,
+ name = "스트레이키즈",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ Artist(
+ id = 4L,
+ name = "볼빨간사춘기",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ Artist(
+ id = 5L,
+ name = "다이나믹 듀오",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ ),
+ ),
+ Festival(
+ id = 32,
+ name = "아이들 콘서트",
+ startDate = LocalDate.now().plusDays(5L),
+ endDate = LocalDate.MAX,
+ imageUrl = "data:image/jpeg;base64,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",
+ school = School(id = 1L, name = "연세대", imageUrl = ""),
+ artists = listOf(
+ Artist(
+ id = 1L,
+ name = "아이들",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ ),
+ ),
+ Festival(
+ id = 33,
+ name = "뉴진스 콘서트",
+ startDate = LocalDate.now().plusDays(20L),
+ endDate = LocalDate.MAX,
+ imageUrl = "data:image/jpeg;base64,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",
+ school = School(id = 1L, name = "부경대", imageUrl = ""),
+ artists = listOf(
+ Artist(
+ id = 6L,
+ name = "뉴진스",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ ),
+ ),
+ Festival(
+ id = 34,
+ name = "아이브 콘서트",
+ startDate = LocalDate.now().plusDays(40L),
+ endDate = LocalDate.MAX,
+ imageUrl = "data:image/jpeg;base64,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",
+ school = School(id = 1L, name = "연세대", imageUrl = ""),
+ artists = listOf(
+ Artist(
+ id = 1L,
+ name = "아이브",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ Artist(
+ id = 2L,
+ name = "르세라핌",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ Artist(
+ id = 3L,
+ name = "스트레이키즈",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ Artist(
+ id = 4L,
+ name = "볼빨간사춘기",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ Artist(
+ id = 5L,
+ name = "다이나믹 듀오",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ ),
+ ),
+ )
+
+ val popularFestivals = listOf(
+ Festival(
+ id = 1,
+ name = "뉴진스 콘서트",
+ startDate = LocalDate.now().minusDays(5L),
+ endDate = LocalDate.now().minusDays(3L),
+ imageUrl = "data:image/jpeg;base64,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",
+ school = School(id = 1L, name = "고려대", imageUrl = ""),
+ artists = listOf(
+ Artist(
+ id = 6L,
+ name = "뉴진스뉴진스뉴진스뉴진스뉴진스",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ ),
+ ),
+ Festival(
+ id = 2,
+ name = "아이브 콘서트",
+ startDate = LocalDate.now().minusDays(2L),
+ endDate = LocalDate.now().plusDays(1L),
+ imageUrl = "data:image/jpeg;base64,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",
+ school = School(id = 1L, name = "연세대", imageUrl = ""),
+ artists = listOf(
+ Artist(
+ id = 1L,
+ name = "아이브",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ Artist(
+ id = 2L,
+ name = "르세라핌",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ Artist(
+ id = 3L,
+ name = "스트레이키즈",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ Artist(
+ id = 4L,
+ name = "볼빨간사춘기",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ Artist(
+ id = 5L,
+ name = "다이나믹 듀오",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ ),
+ ),
+ Festival(
+ id = 3,
+ name = "아이들 콘서트",
+ startDate = LocalDate.now().plusDays(5L),
+ endDate = LocalDate.now().plusDays(6L),
+ imageUrl = "data:image/jpeg;base64,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",
+ school = School(id = 1L, name = "연세대", imageUrl = ""),
+ artists = listOf(
+ Artist(
+ id = 1L,
+ name = "아이들",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ ),
+ ),
+ Festival(
+ id = 4,
+ name = "뉴진스 콘서트",
+ startDate = LocalDate.MIN,
+ endDate = LocalDate.MAX,
+ imageUrl = "data:image/jpeg;base64,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",
+ school = School(id = 1L, name = "부경대", imageUrl = ""),
+ artists = listOf(
+ Artist(
+ id = 6L,
+ name = "뉴진스",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ Artist(
+ id = 3L,
+ name = "스트레이키즈",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ Artist(
+ id = 4L,
+ name = "볼빨간사춘기",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ Artist(
+ id = 5L,
+ name = "다이나믹 듀오",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ ),
+ ),
+ Festival(
+ id = 5,
+ name = "아이브 콘서트",
+ startDate = LocalDate.now().plusDays(10L),
+ endDate = LocalDate.now().plusDays(11L),
+ imageUrl = "data:image/jpeg;base64,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",
+ school = School(id = 1L, name = "연세대", imageUrl = ""),
+ artists = listOf(
+ Artist(
+ id = 1L,
+ name = "아이브",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ Artist(
+ id = 2L,
+ name = "르세라핌",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ Artist(
+ id = 3L,
+ name = "스트레이키즈",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ Artist(
+ id = 4L,
+ name = "볼빨간사춘기",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ Artist(
+ id = 5L,
+ name = "다이나믹 듀오",
+ imageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ ),
+ ),
+ ),
+ )
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/repository/FakeSchool.kt b/android/festago/data/src/main/java/com/festago/festago/data/repository/FakeSchool.kt
new file mode 100644
index 000000000..f26d51ad3
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/repository/FakeSchool.kt
@@ -0,0 +1,28 @@
+package com.festago.festago.data.repository
+
+import com.festago.festago.domain.model.school.SchoolInfo
+import com.festago.festago.domain.model.social.SocialMedia
+import com.festago.festago.domain.model.social.SocialMediaType
+
+object FakeSchool {
+ val googleSchool = SchoolInfo(
+ id = 1,
+ schoolName = "구글대학교",
+ logoUrl = "https://cdn1.iconfinder.com/data/icons/logos-brands-in-colors/544/Google__G__Logo-512.png",
+ backgroundUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/2/2f/Google_2015_logo.svg/1200px-Google_2015_logo.svg.png",
+ socialMedia = listOf(
+ SocialMedia(
+ type = SocialMediaType.INSTAGRAM,
+ name = "구글대학교 인스타",
+ logoUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Instagram_logo_2016.svg/2048px-Instagram_logo_2016.svg.png",
+ url = "https://www.instagram.com/",
+ ),
+ SocialMedia(
+ type = SocialMediaType.INSTAGRAM,
+ name = "구글대학교 X",
+ logoUrl = "https://about.x.com/content/dam/about-twitter/x/brand-toolkit/logo-black.png.twimg.1920.png",
+ url = "https://twitter.com/?lang=en",
+ )
+ )
+ )
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/repository/FakeSchoolRepository.kt b/android/festago/data/src/main/java/com/festago/festago/data/repository/FakeSchoolRepository.kt
new file mode 100644
index 000000000..a1e841a94
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/repository/FakeSchoolRepository.kt
@@ -0,0 +1,28 @@
+package com.festago.festago.data.repository
+
+import com.festago.festago.domain.model.festival.FestivalsPage
+import com.festago.festago.domain.model.school.SchoolInfo
+import com.festago.festago.domain.repository.SchoolRepository
+import java.time.LocalDate
+import javax.inject.Inject
+
+class FakeSchoolRepository @Inject constructor() : SchoolRepository {
+ override suspend fun loadSchoolInfo(schoolId: Long, delayTimeMillis: Long): Result {
+ return Result.success(FakeSchool.googleSchool)
+ }
+
+ override suspend fun loadSchoolFestivals(
+ schoolId: Long,
+ size: Int?,
+ isPast: Boolean?,
+ lastFestivalId: Int?,
+ lastStartDate: LocalDate?,
+ ): Result {
+ return Result.success(
+ FestivalsPage(
+ isLastPage = true,
+ festivals = FakeFestivals.progressFestivals,
+ ),
+ )
+ }
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/repository/FakeSearchRepository.kt b/android/festago/data/src/main/java/com/festago/festago/data/repository/FakeSearchRepository.kt
new file mode 100644
index 000000000..a337335e9
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/repository/FakeSearchRepository.kt
@@ -0,0 +1,94 @@
+package com.festago.festago.data.repository
+
+import com.festago.festago.domain.model.search.ArtistSearch
+import com.festago.festago.domain.model.search.FestivalSearch
+import com.festago.festago.domain.model.search.SchoolSearch
+import com.festago.festago.domain.repository.SearchRepository
+import kotlinx.coroutines.delay
+import java.time.LocalDate
+import javax.inject.Inject
+
+class FakeSearchRepository @Inject constructor() : SearchRepository {
+ private var times = 0
+ override suspend fun searchFestivals(searchQuery: String): Result> {
+ delay(1000)
+ times++
+ if (times % 2 == 0) {
+ return Result.success(listOf())
+ }
+ return Result.success(listOf())
+ }
+
+ override suspend fun searchArtists(searchQuery: String): Result> {
+ delay(1000)
+ if (times % 5 == 0) {
+ return Result.failure(Exception())
+ }
+ return Result.success(
+ listOf(
+ ArtistSearch(
+ id = 6L,
+ name = "뉴진스뉴진스뉴진스뉴진스뉴진스",
+ profileImageUrl = "https://cdn.mediatoday.co.kr/news/photo/202311/313885_438531_4716.jpg",
+ todayStage = 2,
+ upcomingStage = 1,
+ ),
+ ArtistSearch(
+ id = 1L,
+ name = "BTS",
+ profileImageUrl = "https://i.namu.wiki/i/gpgJvt_C2vKJS4VA4K_Vm57Y5WoS83ofshxhJlQaT4P9Tu0N96vZ2OcdeAN7ZtRAM26UyyQs3sualkKk6i_SrRMvwVKrU015XJqzJ7wKRbOub_oUAxPSFre_8D5De3oy-fCxL0uZ-HGvsWxIX57yrw.webp",
+ todayStage = 2,
+ upcomingStage = 2,
+ ),
+ ArtistSearch(
+ id = 2L,
+ name = "싸이",
+ profileImageUrl = "https://i.namu.wiki/i/VH58lI8f-y8QSoxFH9IAjjCobySN0lflZ4rMy6Un7qawUwAyi9UfeseZWCzxH-lQeZk7q_eUyTHGlZBAPqSLWliIKWYDLaAgomVtOyAQg60aCpF3oNTBOgUe_hig3rbHW-YAgoj95Fww3MCToyM6MA.webp",
+ todayStage = 2,
+ upcomingStage = 3,
+ ),
+ ArtistSearch(
+ id = 10L,
+ name = "마마무",
+ profileImageUrl = "https://i.namu.wiki/i/Mre8tXnE40mB9_UwXIwASMEAUSVhHvyjJxXq-lQo40C3bLWYfxXBeai8t6TugyomPjFgxL3VfDA2zn65HlzqPXgTKlvdRl1gJ6PGZLxYYk8Uhk8L6va7zm_etSK5UzVLE56fUATqUCq-6tRQXigmYQ.webp",
+ todayStage = 2,
+ upcomingStage = 4,
+ ),
+ ArtistSearch(
+ id = 11L,
+ name = "블랙핑크",
+ profileImageUrl = "https://i.namu.wiki/i/VZxRYO8_CXa2QbOSZgttDq5ue5QEu_Fbk1Lwo3qpasLAfS802YExcnmVmDhCq3ONF0ExzhACz_YkZbxOGmIfjuPDZnFo7i0pWaT05NluHRHGfp9NqsAT6WBNb0k5KecOyDvakXk0VH2fUo4ojSwC6g.webp",
+ todayStage = 1,
+ upcomingStage = 5,
+ ),
+ ),
+ )
+ }
+
+ override suspend fun searchSchools(searchQuery: String): Result> {
+ delay(1000)
+ return Result.success(
+ listOf(
+ SchoolSearch(
+ id = 1L,
+ name = "부경대학교",
+ logoUrl = "htts://www.pknu.ac.kr/images/front/sub/univ_logo00.png",
+ upcomingFestivalStartDate = LocalDate.now().plusDays(10L),
+ ),
+ SchoolSearch(
+ id = 2L,
+ name = "서울대학교",
+ logoUrl = "https://blog.kakaocdn.net/dn/CYoCP/btrSeivmaxD/e7JaOZVPI3Je55nAJaHDMK/img.png",
+ upcomingFestivalStartDate = LocalDate.now().plusDays(3L),
+ ),
+ SchoolSearch(
+ id = 3L,
+ name = "서울과학기술대학교",
+ logoUrl = "https://www.seoultech.ac.kr/site/www/images/intro/img_ui01_01.gif",
+ upcomingFestivalStartDate = null,
+ ),
+
+ ),
+ )
+ }
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/repository/FakeUserRepository.kt b/android/festago/data/src/main/java/com/festago/festago/data/repository/FakeUserRepository.kt
new file mode 100644
index 000000000..3a8b86fcb
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/repository/FakeUserRepository.kt
@@ -0,0 +1,48 @@
+package com.festago.festago.data.repository
+
+import com.festago.festago.domain.model.user.Token
+import com.festago.festago.domain.model.user.UserInfo
+import com.festago.festago.domain.repository.UserRepository
+import java.time.LocalDateTime
+import javax.inject.Inject
+
+class FakeUserRepository @Inject constructor() : UserRepository {
+
+ override suspend fun isSignRejected(): Boolean {
+ return false
+ }
+
+ override suspend fun isSigned(): Boolean {
+ return true
+ }
+
+ override suspend fun getRefreshToken(): Result {
+ return Result.success(Token("", LocalDateTime.now()))
+ }
+
+ override suspend fun getAccessToken(): Result {
+ return Result.success(Token("", LocalDateTime.now()))
+ }
+
+ override suspend fun signIn(idToken: String): Result {
+ return Result.success(Unit)
+ }
+
+ override suspend fun signOut(): Result {
+ return Result.success(Unit)
+ }
+
+ override suspend fun rejectSignIn() {
+ // handle reject sign in
+ }
+
+ override suspend fun deleteAccount(): Result {
+ return Result.success(Unit)
+ }
+
+ override suspend fun getUserInfo(): Result {
+ return Result.success(UserInfo("", ""))
+ }
+
+ override suspend fun clearToken() = Unit
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/retrofit/AuthInterceptor.kt b/android/festago/data/src/main/java/com/festago/festago/data/retrofit/AuthInterceptor.kt
new file mode 100644
index 000000000..fd362b2f5
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/retrofit/AuthInterceptor.kt
@@ -0,0 +1,34 @@
+package com.festago.festago.data.retrofit
+
+import com.festago.festago.domain.repository.UserRepository
+import kotlinx.coroutines.runBlocking
+import okhttp3.Interceptor
+import okhttp3.Request
+import okhttp3.Response
+
+class AuthInterceptor(private val userRepository: UserRepository) : Interceptor {
+
+ override fun intercept(chain: Interceptor.Chain): Response {
+ return runBlocking {
+ chain.proceed(request = getNewRequest(chain)).also {
+ if (it.code == 401)
+ userRepository.clearToken()
+ }
+ }
+ }
+
+ private suspend fun getNewRequest(chain: Interceptor.Chain): Request =
+ chain.request()
+ .newBuilder()
+ .addHeader(
+ HEADER_AUTHORIZATION,
+ AUTHORIZATION_TOKEN_FORMAT.format(
+ userRepository.getAccessToken().getOrNull()?.token ?: "TokenIsNull",
+ ),
+ ).build()
+
+ companion object {
+ private const val HEADER_AUTHORIZATION = "Authorization"
+ private const val AUTHORIZATION_TOKEN_FORMAT = "Bearer %s"
+ }
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/service/ArtistRetrofitService.kt b/android/festago/data/src/main/java/com/festago/festago/data/service/ArtistRetrofitService.kt
new file mode 100644
index 000000000..d8fc53a16
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/service/ArtistRetrofitService.kt
@@ -0,0 +1,26 @@
+package com.festago.festago.data.service
+
+import com.festago.festago.data.dto.artist.ArtistDetailResponse
+import com.festago.festago.data.dto.festival.FestivalsResponse
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Path
+import retrofit2.http.Query
+import java.time.LocalDate
+
+interface ArtistRetrofitService {
+
+ @GET("api/v1/artists/{artistId}/festivals")
+ suspend fun getArtistFestivals(
+ @Path("artistId") artistId: Long,
+ @Query("size") size: Int?,
+ @Query("lastFestivalId") lastFestivalId: Long?,
+ @Query("lastStartDate") lastStartDate: LocalDate?,
+ @Query("isPast") isPast: Boolean?,
+ ): Response
+
+ @GET("api/v1/artists/{artistId}")
+ suspend fun getArtistDetail(
+ @Path("artistId") artistId: Long,
+ ): Response
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/service/AuthRetrofitService.kt b/android/festago/data/src/main/java/com/festago/festago/data/service/AuthRetrofitService.kt
new file mode 100644
index 000000000..8d0333b86
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/service/AuthRetrofitService.kt
@@ -0,0 +1,35 @@
+package com.festago.festago.data.service
+
+import com.festago.festago.data.dto.user.RefreshRequest
+import com.festago.festago.data.dto.user.RefreshResponse
+import com.festago.festago.data.dto.user.SignInRequest
+import com.festago.festago.data.dto.user.SignInResponse
+import com.festago.festago.data.dto.user.SignOutRequest
+import retrofit2.Response
+import retrofit2.http.Body
+import retrofit2.http.DELETE
+import retrofit2.http.Header
+import retrofit2.http.POST
+
+interface AuthRetrofitService {
+ @POST("api/v1/auth/login/open-id")
+ suspend fun signIn(
+ @Body signInRequest: SignInRequest,
+ ): Response
+
+ @POST("api/v1/auth/refresh")
+ suspend fun refresh(
+ @Body refreshRequest: RefreshRequest,
+ ): Response
+
+ @POST("api/v1/auth/logout")
+ suspend fun signOut(
+ @Header("Authorization") token: String,
+ @Body signOutRequest: SignOutRequest,
+ ): Response
+
+ @DELETE("api/v1/auth")
+ suspend fun deleteAccount(
+ @Header("Authorization") token: String,
+ ): Response
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/service/BookmarkRetrofitService.kt b/android/festago/data/src/main/java/com/festago/festago/data/service/BookmarkRetrofitService.kt
new file mode 100644
index 000000000..482b71089
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/service/BookmarkRetrofitService.kt
@@ -0,0 +1,41 @@
+package com.festago.festago.data.service
+
+import com.festago.festago.data.dto.bookmark.ArtistBookmarkResponse
+import com.festago.festago.data.dto.bookmark.FestivalBookmarkResponse
+import com.festago.festago.data.dto.bookmark.SchoolBookmarkResponse
+import com.festago.festago.domain.model.bookmark.BookmarkType
+import com.festago.festago.domain.model.bookmark.FestivalBookmarkOrder
+import retrofit2.Response
+import retrofit2.http.DELETE
+import retrofit2.http.GET
+import retrofit2.http.PUT
+import retrofit2.http.Query
+
+interface BookmarkRetrofitService {
+ @PUT("api/v1/bookmarks")
+ suspend fun addBookmark(
+ @Query("resourceId") resourceId: Long,
+ @Query("bookmarkType") bookmarkType: BookmarkType,
+ ): Response
+
+ @DELETE("api/v1/bookmarks")
+ suspend fun deleteBookmark(
+ @Query("resourceId") resourceId: Long,
+ @Query("bookmarkType") bookmarkType: BookmarkType,
+ ): Response
+
+ @GET("api/v1/bookmarks/schools")
+ suspend fun getSchoolBookmarks(): Response>
+
+ @GET("api/v1/bookmarks/festivals/ids")
+ suspend fun getFestivalBookmarkIds(): Response>
+
+ @GET("api/v1/bookmarks/festivals")
+ suspend fun getFestivalBookmarks(
+ @Query("festivalIds") festivalIds: List,
+ @Query("festivalBookmarkOrder") festivalBookmarkOrder: FestivalBookmarkOrder,
+ ): Response>
+
+ @GET("api/v1/bookmarks/artists")
+ suspend fun getArtistBookmarks(): Response>
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/service/FestivalRetrofitService.kt b/android/festago/data/src/main/java/com/festago/festago/data/service/FestivalRetrofitService.kt
new file mode 100644
index 000000000..52881d3d7
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/service/FestivalRetrofitService.kt
@@ -0,0 +1,29 @@
+package com.festago.festago.data.service
+
+import com.festago.festago.data.dto.festival.FestivalDetailResponse
+import com.festago.festago.data.dto.festival.FestivalsResponse
+import com.festago.festago.data.dto.festival.PopularFestivalsResponse
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Path
+import retrofit2.http.Query
+import java.time.LocalDate
+
+interface FestivalRetrofitService {
+ @GET("api/v1/popular/festivals")
+ suspend fun getPopularFestivals(): Response
+
+ @GET("api/v1/festivals")
+ suspend fun getFestivals(
+ @Query("region") region: String?,
+ @Query("filter") filter: String?,
+ @Query("lastFestivalId") lastFestivalId: Long?,
+ @Query("lastStartDate") lastStartDate: LocalDate?,
+ @Query("size") size: Int?,
+ ): Response
+
+ @GET("api/v1/festivals/{festivalId}")
+ suspend fun getFestivalDetail(
+ @Path("festivalId") festivalId: Long,
+ ): Response
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/service/SchoolRetrofitService.kt b/android/festago/data/src/main/java/com/festago/festago/data/service/SchoolRetrofitService.kt
new file mode 100644
index 000000000..6115210d6
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/service/SchoolRetrofitService.kt
@@ -0,0 +1,25 @@
+package com.festago.festago.data.service
+
+import com.festago.festago.data.dto.schooldetail.SchoolFestivalsResponse
+import com.festago.festago.data.dto.schooldetail.SchoolInfoResponse
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Path
+import retrofit2.http.Query
+import java.time.LocalDate
+
+interface SchoolRetrofitService {
+ @GET("api/v1/schools/{schoolId}")
+ suspend fun getSchool(
+ @Path("schoolId") schoolId: Long,
+ ): Response
+
+ @GET("api/v1/schools/{schoolId}/festivals")
+ suspend fun getSchoolFestivals(
+ @Path("schoolId") schoolId: Long,
+ @Query("size") size: Int?,
+ @Query("isPast") isPast: Boolean?,
+ @Query("lastFestivalId") lastFestivalId: Int?,
+ @Query("lastStartDate") lastStartDate: LocalDate?,
+ ): Response
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/service/SearchRetrofitService.kt b/android/festago/data/src/main/java/com/festago/festago/data/service/SearchRetrofitService.kt
new file mode 100644
index 000000000..6f525f989
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/service/SearchRetrofitService.kt
@@ -0,0 +1,25 @@
+package com.festago.festago.data.service
+
+import com.festago.festago.data.dto.artist.ArtistSearchResponse
+import com.festago.festago.data.dto.festival.FestivalSearchResponse
+import com.festago.festago.data.dto.school.SchoolSearchResponse
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Query
+
+interface SearchRetrofitService {
+ @GET("api/v1/search/festivals")
+ suspend fun searchFestivals(
+ @Query("keyword") keyword: String,
+ ): Response>
+
+ @GET("api/v1/search/artists")
+ suspend fun searchArtists(
+ @Query("keyword") keyword: String,
+ ): Response>
+
+ @GET("api/v1/search/schools")
+ suspend fun searchSchools(
+ @Query("keyword") keyword: String,
+ ): Response>
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/util/ResponseExt.kt b/android/festago/data/src/main/java/com/festago/festago/data/util/ResponseExt.kt
new file mode 100644
index 000000000..e234ac33c
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/util/ResponseExt.kt
@@ -0,0 +1,53 @@
+package com.festago.festago.data.util
+
+import com.festago.festago.domain.exception.BookmarkLimitExceededException
+import com.festago.festago.domain.exception.NetworkException
+import com.festago.festago.domain.exception.UnauthorizedException
+import retrofit2.Response
+import java.net.UnknownHostException
+
+suspend fun runCatchingResponse(
+ block: suspend () -> Response,
+): Result {
+ try {
+ val response = block()
+ if (response.isSuccessful && response.body() != null) {
+ return Result.success(response.body()!!)
+ }
+
+ handleUnauthorizedException(response)
+
+ handleBadRequestException(response)
+
+ return Result.failure(
+ Throwable(
+ "{" +
+ "code: ${response.code()}," +
+ "message: ${response.message()}, " +
+ "body: ${response.errorBody()?.string()}" +
+ "}",
+ ),
+ )
+ } catch (e: Exception) {
+ if (e is UnknownHostException) {
+ return Result.failure(NetworkException())
+ }
+ return Result.failure(e)
+ }
+}
+
+private fun handleUnauthorizedException(response: Response) {
+ if (response.code() == 401) {
+ throw UnauthorizedException()
+ }
+}
+
+private fun handleBadRequestException(response: Response) {
+ if (response.code() == 400) {
+ response.errorBody()?.string()?.let {
+ if (it.contains("BOOKMARK_LIMIT_EXCEEDED")) {
+ throw BookmarkLimitExceededException()
+ }
+ }
+ }
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/util/ResultExt.kt b/android/festago/data/src/main/java/com/festago/festago/data/util/ResultExt.kt
new file mode 100644
index 000000000..bc058d668
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/util/ResultExt.kt
@@ -0,0 +1,12 @@
+package com.festago.festago.data.util
+
+suspend fun Result.onSuccessOrCatch(block: suspend (T) -> R): Result {
+ return try {
+ onSuccess { return Result.success(block(it)) }
+ onFailure { return Result.failure(it) }
+
+ throw Throwable("This line should not be reached")
+ } catch (e: Exception) {
+ Result.failure(e)
+ }
+}
diff --git a/android/festago/data/src/main/java/com/festago/festago/data/util/SharedPrefExt.kt b/android/festago/data/src/main/java/com/festago/festago/data/util/SharedPrefExt.kt
new file mode 100644
index 000000000..4bf934298
--- /dev/null
+++ b/android/festago/data/src/main/java/com/festago/festago/data/util/SharedPrefExt.kt
@@ -0,0 +1,14 @@
+package com.festago.festago.data.util
+
+import android.content.SharedPreferences
+import com.google.gson.GsonBuilder
+
+inline fun SharedPreferences.putObject(key: String, value: T?) {
+ val jsonString = GsonBuilder().create().toJson(value)
+ edit().putString(key, jsonString).apply()
+}
+
+inline fun SharedPreferences.getObject(key: String, default: T?): T? {
+ val value = getString(key, null) ?: return default
+ return GsonBuilder().create().fromJson(value, T::class.java)
+}
diff --git a/android/festago/data/src/test/java/com/festago/festago/data/.gitkeep b/android/festago/data/src/test/java/com/festago/festago/data/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/android/festago/docs/README.md b/android/festago/docs/README.md
deleted file mode 100644
index 22c574328..000000000
--- a/android/festago/docs/README.md
+++ /dev/null
@@ -1,16 +0,0 @@
-- [x] QR 정보를 받아온다.
-
-```Gherkin
- GIVEN 티켓 ID 를 가지고 있다.
- WHEN QR 정보를 요청한다.
- THEN QR 정보를 받는다.
-```
-
-- [x] QR을 생성한다.
- - 유효 시간이 얼마나 남았는지 보여준다.
-
-```Gherkin
- GIVEN QR 정보를 받아온 상태이다.
- WHEN QR 을 생성한다.
- THEN 생성된 QR 이 화면에 노출된다.
-```
diff --git a/android/festago/domain-legacy/.gitignore b/android/festago/domain-legacy/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/android/festago/domain-legacy/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/android/festago/domain-legacy/build.gradle.kts b/android/festago/domain-legacy/build.gradle.kts
new file mode 100644
index 000000000..e7815286b
--- /dev/null
+++ b/android/festago/domain-legacy/build.gradle.kts
@@ -0,0 +1,27 @@
+plugins {
+ kotlin("jvm")
+ id("org.jlleitschuh.gradle.ktlint")
+}
+
+dependencies {
+ testImplementation("org.junit.jupiter", "junit-jupiter", "5.8.2")
+ testImplementation("org.assertj", "assertj-core", "3.22.0")
+ testImplementation("io.kotest", "kotest-runner-junit5", "5.2.3")
+
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.6.4")
+}
+
+kotlin.jvmToolchain(17)
+
+tasks {
+ compileKotlin {
+ kotlinOptions.jvmTarget = "17"
+ }
+ compileTestKotlin {
+ kotlinOptions.jvmTarget = "17"
+ }
+ test {
+ useJUnitPlatform()
+ }
+}
diff --git a/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/ErrorCode.kt b/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/ErrorCode.kt
new file mode 100644
index 000000000..55baeae7c
--- /dev/null
+++ b/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/ErrorCode.kt
@@ -0,0 +1,9 @@
+package com.festago.festago.model
+
+sealed class ErrorCode : Throwable() {
+
+ class NEED_STUDENT_VERIFICATION : ErrorCode()
+ class RESERVE_TICKET_OVER_AMOUNT : ErrorCode()
+ class TICKET_SOLD_OUT : ErrorCode()
+ class UNKNOWN : ErrorCode()
+}
diff --git a/android/festago/domain/src/main/java/com/festago/festago/model/Festival.kt b/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/Festival.kt
similarity index 100%
rename from android/festago/domain/src/main/java/com/festago/festago/model/Festival.kt
rename to android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/Festival.kt
diff --git a/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/FestivalFilter.kt b/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/FestivalFilter.kt
new file mode 100644
index 000000000..4f881328f
--- /dev/null
+++ b/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/FestivalFilter.kt
@@ -0,0 +1,5 @@
+package com.festago.festago.model
+
+enum class FestivalFilter {
+ ALL, PROGRESS, PLANNED, END
+}
diff --git a/android/festago/domain/src/main/java/com/festago/festago/model/MemberTicketFestival.kt b/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/MemberTicketFestival.kt
similarity index 100%
rename from android/festago/domain/src/main/java/com/festago/festago/model/MemberTicketFestival.kt
rename to android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/MemberTicketFestival.kt
diff --git a/android/festago/domain/src/main/java/com/festago/festago/model/Reservation.kt b/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/Reservation.kt
similarity index 100%
rename from android/festago/domain/src/main/java/com/festago/festago/model/Reservation.kt
rename to android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/Reservation.kt
diff --git a/android/festago/domain/src/main/java/com/festago/festago/model/ReservationStage.kt b/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/ReservationStage.kt
similarity index 100%
rename from android/festago/domain/src/main/java/com/festago/festago/model/ReservationStage.kt
rename to android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/ReservationStage.kt
diff --git a/android/festago/domain/src/main/java/com/festago/festago/model/ReservationTicket.kt b/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/ReservationTicket.kt
similarity index 100%
rename from android/festago/domain/src/main/java/com/festago/festago/model/ReservationTicket.kt
rename to android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/ReservationTicket.kt
diff --git a/android/festago/domain/src/main/java/com/festago/festago/model/ReservationTickets.kt b/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/ReservationTickets.kt
similarity index 100%
rename from android/festago/domain/src/main/java/com/festago/festago/model/ReservationTickets.kt
rename to android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/ReservationTickets.kt
diff --git a/android/festago/domain/src/main/java/com/festago/festago/model/ReservedTicket.kt b/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/ReservedTicket.kt
similarity index 100%
rename from android/festago/domain/src/main/java/com/festago/festago/model/ReservedTicket.kt
rename to android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/ReservedTicket.kt
diff --git a/android/festago/domain/src/main/java/com/festago/festago/model/School.kt b/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/School.kt
similarity index 100%
rename from android/festago/domain/src/main/java/com/festago/festago/model/School.kt
rename to android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/School.kt
diff --git a/android/festago/domain/src/main/java/com/festago/festago/model/Stage.kt b/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/Stage.kt
similarity index 100%
rename from android/festago/domain/src/main/java/com/festago/festago/model/Stage.kt
rename to android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/Stage.kt
diff --git a/android/festago/domain/src/main/java/com/festago/festago/model/StudentVerificationCode.kt b/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/StudentVerificationCode.kt
similarity index 100%
rename from android/festago/domain/src/main/java/com/festago/festago/model/StudentVerificationCode.kt
rename to android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/StudentVerificationCode.kt
diff --git a/android/festago/domain/src/main/java/com/festago/festago/model/TextValidator.kt b/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/TextValidator.kt
similarity index 100%
rename from android/festago/domain/src/main/java/com/festago/festago/model/TextValidator.kt
rename to android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/TextValidator.kt
diff --git a/android/festago/domain/src/main/java/com/festago/festago/model/Ticket.kt b/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/Ticket.kt
similarity index 100%
rename from android/festago/domain/src/main/java/com/festago/festago/model/Ticket.kt
rename to android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/Ticket.kt
diff --git a/android/festago/domain/src/main/java/com/festago/festago/model/TicketCode.kt b/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/TicketCode.kt
similarity index 100%
rename from android/festago/domain/src/main/java/com/festago/festago/model/TicketCode.kt
rename to android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/TicketCode.kt
diff --git a/android/festago/domain/src/main/java/com/festago/festago/model/TicketCondition.kt b/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/TicketCondition.kt
similarity index 100%
rename from android/festago/domain/src/main/java/com/festago/festago/model/TicketCondition.kt
rename to android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/TicketCondition.kt
diff --git a/android/festago/domain/src/main/java/com/festago/festago/model/TicketType.kt b/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/TicketType.kt
similarity index 100%
rename from android/festago/domain/src/main/java/com/festago/festago/model/TicketType.kt
rename to android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/TicketType.kt
diff --git a/android/festago/domain/src/main/java/com/festago/festago/model/UserProfile.kt b/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/UserProfile.kt
similarity index 100%
rename from android/festago/domain/src/main/java/com/festago/festago/model/UserProfile.kt
rename to android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/UserProfile.kt
diff --git a/android/festago/domain/src/main/java/com/festago/festago/model/timer/Timer.kt b/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/timer/Timer.kt
similarity index 100%
rename from android/festago/domain/src/main/java/com/festago/festago/model/timer/Timer.kt
rename to android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/timer/Timer.kt
diff --git a/android/festago/domain/src/main/java/com/festago/festago/model/timer/TimerListener.kt b/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/timer/TimerListener.kt
similarity index 100%
rename from android/festago/domain/src/main/java/com/festago/festago/model/timer/TimerListener.kt
rename to android/festago/domain-legacy/src/main/java/com/festago/festago/domain/model/timer/TimerListener.kt
diff --git a/android/festago/domain/src/main/java/com/festago/festago/repository/AuthRepository.kt b/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/repository/AuthRepository.kt
similarity index 100%
rename from android/festago/domain/src/main/java/com/festago/festago/repository/AuthRepository.kt
rename to android/festago/domain-legacy/src/main/java/com/festago/festago/domain/repository/AuthRepository.kt
diff --git a/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/repository/FestivalRepository.kt b/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/repository/FestivalRepository.kt
new file mode 100644
index 000000000..326077ec2
--- /dev/null
+++ b/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/repository/FestivalRepository.kt
@@ -0,0 +1,10 @@
+package com.festago.festago.repository
+
+import com.festago.festago.model.Festival
+import com.festago.festago.model.FestivalFilter
+import com.festago.festago.model.Reservation
+
+interface FestivalRepository {
+ suspend fun loadFestivals(festivalFilter: FestivalFilter): Result>
+ suspend fun loadFestivalDetail(festivalId: Long): Result
+}
diff --git a/android/festago/domain/src/main/java/com/festago/festago/repository/ReservationTicketRepository.kt b/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/repository/ReservationTicketRepository.kt
similarity index 100%
rename from android/festago/domain/src/main/java/com/festago/festago/repository/ReservationTicketRepository.kt
rename to android/festago/domain-legacy/src/main/java/com/festago/festago/domain/repository/ReservationTicketRepository.kt
diff --git a/android/festago/domain/src/main/java/com/festago/festago/repository/SchoolRepository.kt b/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/repository/SchoolRepository.kt
similarity index 100%
rename from android/festago/domain/src/main/java/com/festago/festago/repository/SchoolRepository.kt
rename to android/festago/domain-legacy/src/main/java/com/festago/festago/domain/repository/SchoolRepository.kt
diff --git a/android/festago/domain/src/main/java/com/festago/festago/repository/SocialAuthRepository.kt b/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/repository/SocialAuthRepository.kt
similarity index 100%
rename from android/festago/domain/src/main/java/com/festago/festago/repository/SocialAuthRepository.kt
rename to android/festago/domain-legacy/src/main/java/com/festago/festago/domain/repository/SocialAuthRepository.kt
diff --git a/android/festago/domain/src/main/java/com/festago/festago/repository/StudentVerificationRepository.kt b/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/repository/StudentVerificationRepository.kt
similarity index 100%
rename from android/festago/domain/src/main/java/com/festago/festago/repository/StudentVerificationRepository.kt
rename to android/festago/domain-legacy/src/main/java/com/festago/festago/domain/repository/StudentVerificationRepository.kt
diff --git a/android/festago/domain/src/main/java/com/festago/festago/repository/TicketRepository.kt b/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/repository/TicketRepository.kt
similarity index 100%
rename from android/festago/domain/src/main/java/com/festago/festago/repository/TicketRepository.kt
rename to android/festago/domain-legacy/src/main/java/com/festago/festago/domain/repository/TicketRepository.kt
diff --git a/android/festago/domain/src/main/java/com/festago/festago/repository/UserRepository.kt b/android/festago/domain-legacy/src/main/java/com/festago/festago/domain/repository/UserRepository.kt
similarity index 100%
rename from android/festago/domain/src/main/java/com/festago/festago/repository/UserRepository.kt
rename to android/festago/domain-legacy/src/main/java/com/festago/festago/domain/repository/UserRepository.kt
diff --git a/android/festago/domain/src/test/java/com/festago/festago/model/StudentVerificationCodeTest.kt b/android/festago/domain-legacy/src/test/java/com/festago/festago/model/StudentVerificationCodeTest.kt
similarity index 100%
rename from android/festago/domain/src/test/java/com/festago/festago/model/StudentVerificationCodeTest.kt
rename to android/festago/domain-legacy/src/test/java/com/festago/festago/model/StudentVerificationCodeTest.kt
diff --git a/android/festago/domain/src/test/java/com/festago/festago/model/TextValidatorTest.kt b/android/festago/domain-legacy/src/test/java/com/festago/festago/model/TextValidatorTest.kt
similarity index 100%
rename from android/festago/domain/src/test/java/com/festago/festago/model/TextValidatorTest.kt
rename to android/festago/domain-legacy/src/test/java/com/festago/festago/model/TextValidatorTest.kt
diff --git a/android/festago/domain/build.gradle.kts b/android/festago/domain/build.gradle.kts
index e3645b404..e7815286b 100644
--- a/android/festago/domain/build.gradle.kts
+++ b/android/festago/domain/build.gradle.kts
@@ -1,5 +1,6 @@
plugins {
kotlin("jvm")
+ id("org.jlleitschuh.gradle.ktlint")
}
dependencies {
@@ -11,6 +12,8 @@ dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.6.4")
}
+kotlin.jvmToolchain(17)
+
tasks {
compileKotlin {
kotlinOptions.jvmTarget = "17"
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/exception/BookmarkLimitExeededException.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/exception/BookmarkLimitExeededException.kt
new file mode 100644
index 000000000..94e30c942
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/exception/BookmarkLimitExeededException.kt
@@ -0,0 +1,5 @@
+package com.festago.festago.domain.exception
+
+class BookmarkLimitExceededException : Exception()
+
+fun Throwable.isBookmarkLimitExceeded() = this is BookmarkLimitExceededException
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/exception/NetworkException.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/exception/NetworkException.kt
new file mode 100644
index 000000000..c85c57236
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/exception/NetworkException.kt
@@ -0,0 +1,4 @@
+package com.festago.festago.domain.exception
+
+class NetworkException : Exception()
+fun Throwable.isNetworkError() = this is NetworkException
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/exception/UnauthorizedException.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/exception/UnauthorizedException.kt
new file mode 100644
index 000000000..62a7e4335
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/exception/UnauthorizedException.kt
@@ -0,0 +1,6 @@
+package com.festago.festago.domain.exception
+
+/* 에러 코드가 401 일때 예외 */
+class UnauthorizedException : Exception()
+
+fun Throwable.isUnauthorized() = this is UnauthorizedException
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/model/artist/Artist.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/model/artist/Artist.kt
new file mode 100644
index 000000000..de47ac6b3
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/model/artist/Artist.kt
@@ -0,0 +1,7 @@
+package com.festago.festago.domain.model.artist
+
+data class Artist(
+ val id: Long,
+ val name: String,
+ val imageUrl: String,
+)
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/model/artist/ArtistDetail.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/model/artist/ArtistDetail.kt
new file mode 100644
index 000000000..69e40b2ae
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/model/artist/ArtistDetail.kt
@@ -0,0 +1,11 @@
+package com.festago.festago.domain.model.artist
+
+import com.festago.festago.domain.model.social.SocialMedia
+
+data class ArtistDetail(
+ val id: Int,
+ val artistName: String,
+ val profileUrl: String,
+ val backgroundUrl: String,
+ val artistMedia: List,
+)
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/model/bookmark/ArtistBookmark.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/model/bookmark/ArtistBookmark.kt
new file mode 100644
index 000000000..d2202cbd5
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/model/bookmark/ArtistBookmark.kt
@@ -0,0 +1,8 @@
+package com.festago.festago.domain.model.bookmark
+
+import java.time.LocalDateTime
+
+data class ArtistBookmark(
+ val artist: ArtistBookmarkInfo,
+ val createdAt: LocalDateTime,
+)
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/model/bookmark/ArtistBookmarkInfo.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/model/bookmark/ArtistBookmarkInfo.kt
new file mode 100644
index 000000000..358315875
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/model/bookmark/ArtistBookmarkInfo.kt
@@ -0,0 +1,7 @@
+package com.festago.festago.domain.model.bookmark
+
+data class ArtistBookmarkInfo(
+ val id: Long,
+ val name: String,
+ val profileImageUrl: String
+)
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/model/bookmark/BookmarkType.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/model/bookmark/BookmarkType.kt
new file mode 100644
index 000000000..5f3a91298
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/model/bookmark/BookmarkType.kt
@@ -0,0 +1,7 @@
+package com.festago.festago.domain.model.bookmark
+
+enum class BookmarkType {
+ ARTIST,
+ FESTIVAL,
+ SCHOOL,
+}
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/model/bookmark/FestivalBookmark.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/model/bookmark/FestivalBookmark.kt
new file mode 100644
index 000000000..860ec91c5
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/model/bookmark/FestivalBookmark.kt
@@ -0,0 +1,9 @@
+package com.festago.festago.domain.model.bookmark
+
+import com.festago.festago.domain.model.festival.Festival
+import java.time.LocalDateTime
+
+class FestivalBookmark(
+ val festival: Festival,
+ val createdAt: LocalDateTime
+)
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/model/bookmark/FestivalBookmarkOrder.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/model/bookmark/FestivalBookmarkOrder.kt
new file mode 100644
index 000000000..892ff868f
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/model/bookmark/FestivalBookmarkOrder.kt
@@ -0,0 +1,6 @@
+package com.festago.festago.domain.model.bookmark
+
+enum class FestivalBookmarkOrder {
+ BOOKMARK,
+ FESTIVAL,
+}
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/model/bookmark/SchoolBookmark.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/model/bookmark/SchoolBookmark.kt
new file mode 100644
index 000000000..7956dbcb4
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/model/bookmark/SchoolBookmark.kt
@@ -0,0 +1,8 @@
+package com.festago.festago.domain.model.bookmark
+
+import java.time.LocalDateTime
+
+data class SchoolBookmark(
+ val school: SchoolBookmarkInfo,
+ val createdAt: LocalDateTime
+)
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/model/bookmark/SchoolBookmarkInfo.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/model/bookmark/SchoolBookmarkInfo.kt
new file mode 100644
index 000000000..6a6b04f9a
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/model/bookmark/SchoolBookmarkInfo.kt
@@ -0,0 +1,7 @@
+package com.festago.festago.domain.model.bookmark
+
+data class SchoolBookmarkInfo(
+ val id: Long,
+ val name: String,
+ val logoUrl: String
+)
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/model/festival/Festival.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/model/festival/Festival.kt
new file mode 100644
index 000000000..cdc882891
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/model/festival/Festival.kt
@@ -0,0 +1,15 @@
+package com.festago.festago.domain.model.festival
+
+import com.festago.festago.domain.model.artist.Artist
+import com.festago.festago.domain.model.school.School
+import java.time.LocalDate
+
+data class Festival(
+ val id: Long,
+ val name: String,
+ val startDate: LocalDate,
+ val endDate: LocalDate,
+ val imageUrl: String,
+ val school: School?,
+ val artists: List,
+)
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/model/festival/FestivalDetail.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/model/festival/FestivalDetail.kt
new file mode 100644
index 000000000..6036a8de3
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/model/festival/FestivalDetail.kt
@@ -0,0 +1,17 @@
+package com.festago.festago.domain.model.festival
+
+import com.festago.festago.domain.model.school.School
+import com.festago.festago.domain.model.social.SocialMedia
+import com.festago.festago.domain.model.stage.Stage
+import java.time.LocalDate
+
+data class FestivalDetail(
+ val id: Long,
+ val name: String,
+ val startDate: LocalDate,
+ val endDate: LocalDate,
+ val posterImageUrl: String,
+ val school: School,
+ val socialMedias: List,
+ val stages: List,
+)
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/model/festival/FestivalFilter.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/model/festival/FestivalFilter.kt
new file mode 100644
index 000000000..199c54a3f
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/model/festival/FestivalFilter.kt
@@ -0,0 +1,5 @@
+package com.festago.festago.domain.model.festival
+
+enum class FestivalFilter {
+ ALL, PROGRESS, PLANNED, END
+}
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/model/festival/FestivalsPage.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/model/festival/FestivalsPage.kt
new file mode 100644
index 000000000..e324e3641
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/model/festival/FestivalsPage.kt
@@ -0,0 +1,6 @@
+package com.festago.festago.domain.model.festival
+
+data class FestivalsPage(
+ val isLastPage: Boolean,
+ val festivals: List,
+)
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/model/festival/PopularFestivals.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/model/festival/PopularFestivals.kt
new file mode 100644
index 000000000..ea1d76663
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/model/festival/PopularFestivals.kt
@@ -0,0 +1,6 @@
+package com.festago.festago.domain.model.festival
+
+data class PopularFestivals(
+ val title: String,
+ val festivals: List,
+)
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/model/festival/SchoolRegion.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/model/festival/SchoolRegion.kt
new file mode 100644
index 000000000..d9b15877f
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/model/festival/SchoolRegion.kt
@@ -0,0 +1,5 @@
+package com.festago.festago.domain.model.festival
+
+enum class SchoolRegion {
+ 서울, 부산, 대구, 인천, 광주, 대전, 울산, 세종, 경기, 강원, 충북, 충남, 전북, 전남, 경북, 경남, 제주 // ktlint-disable enum-entry-name-case
+}
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/model/nonce/NonceGenerator.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/model/nonce/NonceGenerator.kt
new file mode 100644
index 000000000..62276febc
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/model/nonce/NonceGenerator.kt
@@ -0,0 +1,13 @@
+package com.festago.festago.domain.model.nonce
+
+class NonceGenerator {
+ fun generate() =
+ List((MIN_LENGTH..MAX_LENGTH).random()) { (MIN_CHAR..MAX_CHAR).random() }.joinToString("")
+
+ companion object {
+ private const val MIN_LENGTH = 3
+ private const val MAX_LENGTH = 6
+ private const val MIN_CHAR = 'a'
+ private const val MAX_CHAR = 'z'
+ }
+}
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/model/recentsearch/RecentSearchQuery.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/model/recentsearch/RecentSearchQuery.kt
new file mode 100644
index 000000000..197b84bf3
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/model/recentsearch/RecentSearchQuery.kt
@@ -0,0 +1,6 @@
+package com.festago.festago.domain.model.recentsearch
+
+data class RecentSearchQuery(
+ val query: String,
+ val queriedDate: Long,
+)
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/model/school/School.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/model/school/School.kt
new file mode 100644
index 000000000..11936b168
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/model/school/School.kt
@@ -0,0 +1,7 @@
+package com.festago.festago.domain.model.school
+
+data class School(
+ val id: Long,
+ val name: String,
+ val imageUrl: String,
+)
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/model/school/SchoolInfo.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/model/school/SchoolInfo.kt
new file mode 100644
index 000000000..fdbcf27ce
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/model/school/SchoolInfo.kt
@@ -0,0 +1,11 @@
+package com.festago.festago.domain.model.school
+
+import com.festago.festago.domain.model.social.SocialMedia
+
+data class SchoolInfo(
+ val id: Int,
+ val schoolName: String,
+ val logoUrl: String,
+ val backgroundUrl: String,
+ val socialMedia: List
+)
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/model/search/ArtistSearch.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/model/search/ArtistSearch.kt
new file mode 100644
index 000000000..fb472d087
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/model/search/ArtistSearch.kt
@@ -0,0 +1,9 @@
+package com.festago.festago.domain.model.search
+
+data class ArtistSearch(
+ val id: Long,
+ val name: String,
+ val profileImageUrl: String,
+ val todayStage: Int,
+ val upcomingStage: Int,
+)
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/model/search/FestivalSearch.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/model/search/FestivalSearch.kt
new file mode 100644
index 000000000..00d06acd2
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/model/search/FestivalSearch.kt
@@ -0,0 +1,13 @@
+package com.festago.festago.domain.model.search
+
+import com.festago.festago.domain.model.artist.Artist
+import java.time.LocalDate
+
+data class FestivalSearch(
+ val id: Long,
+ val name: String,
+ val startDate: LocalDate,
+ val endDate: LocalDate,
+ val imageUrl: String,
+ val artists: List,
+)
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/model/search/SchoolSearch.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/model/search/SchoolSearch.kt
new file mode 100644
index 000000000..dde84b81e
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/model/search/SchoolSearch.kt
@@ -0,0 +1,10 @@
+package com.festago.festago.domain.model.search
+
+import java.time.LocalDate
+
+data class SchoolSearch(
+ val id: Long,
+ val name: String,
+ val logoUrl: String,
+ val upcomingFestivalStartDate: LocalDate?,
+)
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/model/social/SocialMedia.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/model/social/SocialMedia.kt
new file mode 100644
index 000000000..22e9cbed0
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/model/social/SocialMedia.kt
@@ -0,0 +1,8 @@
+package com.festago.festago.domain.model.social
+
+data class SocialMedia(
+ val type: SocialMediaType,
+ val name: String,
+ val logoUrl: String,
+ val url: String
+)
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/model/social/SocialMediaType.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/model/social/SocialMediaType.kt
new file mode 100644
index 000000000..ace6732b4
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/model/social/SocialMediaType.kt
@@ -0,0 +1,9 @@
+package com.festago.festago.domain.model.social
+
+enum class SocialMediaType {
+ FACEBOOK,
+ INSTAGRAM,
+ YOUTUBE,
+ X,
+ NONE,
+}
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/model/stage/Stage.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/model/stage/Stage.kt
new file mode 100644
index 000000000..89ca13659
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/model/stage/Stage.kt
@@ -0,0 +1,10 @@
+package com.festago.festago.domain.model.stage
+
+import com.festago.festago.domain.model.artist.Artist
+import java.time.LocalDateTime
+
+class Stage(
+ val id: Long,
+ val startDateTime: LocalDateTime,
+ val artists: List,
+)
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/model/user/Token.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/model/user/Token.kt
new file mode 100644
index 000000000..58869af3d
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/model/user/Token.kt
@@ -0,0 +1,10 @@
+package com.festago.festago.domain.model.user
+
+import java.time.LocalDateTime
+
+data class Token(
+ val token: String,
+ val expiredAt: LocalDateTime,
+) {
+ fun isExpired() = LocalDateTime.now().isAfter(expiredAt)
+}
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/model/user/UserInfo.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/model/user/UserInfo.kt
new file mode 100644
index 000000000..43b7227ac
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/model/user/UserInfo.kt
@@ -0,0 +1,6 @@
+package com.festago.festago.domain.model.user
+
+data class UserInfo(
+ val nickname: String,
+ val profileImageUrl: String,
+)
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/repository/ArtistRepository.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/repository/ArtistRepository.kt
new file mode 100644
index 000000000..a4de27690
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/repository/ArtistRepository.kt
@@ -0,0 +1,17 @@
+package com.festago.festago.domain.repository
+
+import com.festago.festago.domain.model.artist.ArtistDetail
+import com.festago.festago.domain.model.festival.FestivalsPage
+import java.time.LocalDate
+
+interface ArtistRepository {
+ suspend fun loadArtistDetail(id: Long, delayTimeMillis: Long = 0L): Result
+
+ suspend fun loadArtistFestivals(
+ id: Long,
+ size: Int? = null,
+ lastFestivalId: Long? = null,
+ lastStartDate: LocalDate? = null,
+ isPast: Boolean? = null,
+ ): Result
+}
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/repository/BookmarkRepository.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/repository/BookmarkRepository.kt
new file mode 100644
index 000000000..0737c42a8
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/repository/BookmarkRepository.kt
@@ -0,0 +1,37 @@
+package com.festago.festago.domain.repository
+
+import com.festago.festago.domain.model.bookmark.ArtistBookmark
+import com.festago.festago.domain.model.bookmark.BookmarkType
+import com.festago.festago.domain.model.bookmark.FestivalBookmark
+import com.festago.festago.domain.model.bookmark.FestivalBookmarkOrder
+import com.festago.festago.domain.model.bookmark.SchoolBookmark
+
+interface BookmarkRepository {
+ // Festival Bookmark
+ suspend fun addFestivalBookmark(festivalId: Long): Result
+
+ suspend fun getFestivalBookmarks(
+ festivalIds: List,
+ festivalBookmarkOrder: FestivalBookmarkOrder,
+ ): Result>
+
+ suspend fun getFestivalBookmarkIds(): Result>
+
+ suspend fun deleteFestivalBookmark(festivalId: Long): Result
+
+ // School Bookmark
+ suspend fun addSchoolBookmark(schoolId: Long): Result
+
+ suspend fun getSchoolBookmarks(): Result>
+
+ suspend fun deleteSchoolBookmark(schoolId: Long): Result
+
+ // Artist Bookmark
+ suspend fun addArtistBookmark(artistId: Long): Result
+
+ suspend fun getArtistBookmarks(): Result>
+
+ suspend fun deleteArtistBookmark(artistId: Long): Result
+
+ fun isBookmarked(id: Long, type: BookmarkType): Boolean
+}
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/repository/FestivalRepository.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/repository/FestivalRepository.kt
new file mode 100644
index 000000000..cedf7aa74
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/repository/FestivalRepository.kt
@@ -0,0 +1,21 @@
+package com.festago.festago.domain.repository
+
+import com.festago.festago.domain.model.festival.FestivalDetail
+import com.festago.festago.domain.model.festival.FestivalFilter
+import com.festago.festago.domain.model.festival.FestivalsPage
+import com.festago.festago.domain.model.festival.PopularFestivals
+import com.festago.festago.domain.model.festival.SchoolRegion
+import java.time.LocalDate
+
+interface FestivalRepository {
+ suspend fun loadPopularFestivals(): Result
+ suspend fun loadFestivals(
+ schoolRegion: SchoolRegion? = null,
+ festivalFilter: FestivalFilter? = null,
+ lastFestivalId: Long? = null,
+ lastStartDate: LocalDate? = null,
+ size: Int? = null,
+ ): Result
+
+ suspend fun loadFestivalDetail(id: Long, delayTimeMillis: Long = 0L): Result
+}
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/repository/RecentSearchRepository.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/repository/RecentSearchRepository.kt
new file mode 100644
index 000000000..b24c8c6b8
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/repository/RecentSearchRepository.kt
@@ -0,0 +1,10 @@
+package com.festago.festago.domain.repository
+
+import com.festago.festago.domain.model.recentsearch.RecentSearchQuery
+
+interface RecentSearchRepository {
+ suspend fun insertOrReplaceRecentSearch(searchQuery: String)
+ suspend fun deleteRecentSearch(searchQuery: String)
+ suspend fun getRecentSearchQueries(limit: Int): List
+ suspend fun clearRecentSearches()
+}
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/repository/SchoolRepository.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/repository/SchoolRepository.kt
new file mode 100644
index 000000000..5133ea60e
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/repository/SchoolRepository.kt
@@ -0,0 +1,16 @@
+package com.festago.festago.domain.repository
+
+import com.festago.festago.domain.model.festival.FestivalsPage
+import com.festago.festago.domain.model.school.SchoolInfo
+import java.time.LocalDate
+
+interface SchoolRepository {
+ suspend fun loadSchoolInfo(schoolId: Long, delayTimeMillis: Long = 0L): Result
+ suspend fun loadSchoolFestivals(
+ schoolId: Long,
+ size: Int? = null,
+ isPast: Boolean? = null,
+ lastFestivalId: Int? = null,
+ lastStartDate: LocalDate? = null,
+ ): Result
+}
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/repository/SearchRepository.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/repository/SearchRepository.kt
new file mode 100644
index 000000000..276707831
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/repository/SearchRepository.kt
@@ -0,0 +1,11 @@
+package com.festago.festago.domain.repository
+
+import com.festago.festago.domain.model.search.ArtistSearch
+import com.festago.festago.domain.model.search.FestivalSearch
+import com.festago.festago.domain.model.search.SchoolSearch
+
+interface SearchRepository {
+ suspend fun searchFestivals(searchQuery: String): Result>
+ suspend fun searchArtists(searchQuery: String): Result>
+ suspend fun searchSchools(searchQuery: String): Result>
+}
diff --git a/android/festago/domain/src/main/java/com/festago/festago/domain/repository/UserRepository.kt b/android/festago/domain/src/main/java/com/festago/festago/domain/repository/UserRepository.kt
new file mode 100644
index 000000000..d80e0c516
--- /dev/null
+++ b/android/festago/domain/src/main/java/com/festago/festago/domain/repository/UserRepository.kt
@@ -0,0 +1,17 @@
+package com.festago.festago.domain.repository
+
+import com.festago.festago.domain.model.user.Token
+import com.festago.festago.domain.model.user.UserInfo
+
+interface UserRepository {
+ suspend fun isSignRejected(): Boolean
+ suspend fun isSigned(): Boolean
+ suspend fun getAccessToken(): Result
+ suspend fun getRefreshToken(): Result
+ suspend fun signIn(idToken: String): Result
+ suspend fun signOut(): Result
+ suspend fun deleteAccount(): Result
+ suspend fun rejectSignIn()
+ suspend fun getUserInfo(): Result
+ suspend fun clearToken()
+}
diff --git a/android/festago/domain/src/main/java/com/festago/festago/repository/FestivalRepository.kt b/android/festago/domain/src/main/java/com/festago/festago/repository/FestivalRepository.kt
deleted file mode 100644
index db08d52dd..000000000
--- a/android/festago/domain/src/main/java/com/festago/festago/repository/FestivalRepository.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package com.festago.festago.repository
-
-import com.festago.festago.model.Festival
-import com.festago.festago.model.Reservation
-
-interface FestivalRepository {
- suspend fun loadFestivals(): Result>
- suspend fun loadFestivalDetail(festivalId: Long): Result
-}
diff --git a/android/festago/presentation-legacy/.gitignore b/android/festago/presentation-legacy/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/android/festago/presentation-legacy/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/android/festago/presentation-legacy/build.gradle.kts b/android/festago/presentation-legacy/build.gradle.kts
new file mode 100644
index 000000000..6706372db
--- /dev/null
+++ b/android/festago/presentation-legacy/build.gradle.kts
@@ -0,0 +1,138 @@
+import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties
+
+plugins {
+ id("com.android.library")
+ id("org.jetbrains.kotlin.android")
+ id("kotlin-parcelize")
+ id("kotlin-kapt")
+ id("org.jlleitschuh.gradle.ktlint")
+ id("com.google.dagger.hilt.android")
+}
+
+android {
+ namespace = "com.festago.festago.presentation"
+ compileSdk = 34
+
+ defaultConfig {
+ minSdk = 28
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+
+ buildConfigField("String", "KAKAO_NATIVE_APP_KEY", getSecretKey("kakao_native_app_key"))
+ resValue("string", "kakao_redirection_scheme", getSecretKey("kakao_redirection_scheme"))
+ }
+
+ buildTypes {
+ debug {
+ buildConfigField("Boolean", "DEBUG_MODE", "true")
+ }
+ release {
+ isMinifyEnabled = false
+ buildConfigField("Boolean", "DEBUG_MODE", "false")
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro",
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_17.toString()
+ }
+
+ dataBinding {
+ enable = true
+ }
+}
+
+tasks.withType().all {
+ kotlinOptions {
+ jvmTarget = "17"
+ }
+}
+
+dependencies {
+ implementation(project(":common"))
+ implementation(project(":domain-legacy"))
+
+ // android
+ implementation("androidx.core:core-ktx:1.10.1")
+ implementation("androidx.appcompat:appcompat:1.6.1")
+ implementation("com.google.android.material:material:1.9.0")
+ implementation("androidx.constraintlayout:constraintlayout:2.1.4")
+
+ // hilt
+ implementation("com.google.dagger:hilt-android:2.44")
+ kapt("com.google.dagger:hilt-android-compiler:2.44")
+ // hilt-android-testing = { group = "com.google.dagger", name = "hilt-android-testing", version.ref = "hilt" }
+ implementation("com.google.dagger:hilt-android-testing:2.44")
+
+ // recyclerview
+ implementation("androidx.recyclerview:recyclerview:1.3.1-rc01")
+
+ // lifecycle
+ implementation("androidx.lifecycle:lifecycle-extensions:2.2.0")
+
+ // glide
+ implementation("com.github.bumptech.glide:glide:4.15.1")
+
+ // retrofit
+ implementation("com.squareup.retrofit2:retrofit:2.9.0")
+
+ // junit4
+ testImplementation("junit:junit:4.13.2")
+ testImplementation("androidx.test.ext:junit:1.1.5")
+ testImplementation("androidx.test:runner:1.5.2")
+
+ // assertJ
+ testImplementation("org.assertj:assertj-core:3.22.0")
+
+ // android-test
+ testImplementation("androidx.arch.core:core-testing:2.2.0")
+
+ // mock
+ testImplementation("io.mockk:mockk-android:1.13.5")
+
+ // espresso
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
+ androidTestImplementation("androidx.test.ext:junit:1.1.5")
+
+ // coroutine
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.6.4")
+ testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4")
+
+ // viewModel
+ implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
+ implementation("androidx.activity:activity-ktx:1.7.2")
+ implementation("androidx.fragment:fragment-ktx:1.6.0")
+
+ // zxing
+ implementation("com.journeyapps:zxing-android-embedded:4.3.0")
+
+ // firebase
+ implementation("com.google.firebase:firebase-messaging-ktx:23.4.0")
+
+ // swiperefreshlayout
+ implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
+
+ // kakao login
+ implementation("com.kakao.sdk:v2-user:2.12.0")
+
+ // turbine
+ testImplementation("app.cash.turbine:turbine:1.0.0")
+
+ // inApp Update
+ implementation("com.google.android.play:app-update-ktx:2.1.0")
+
+ // splash
+ implementation("androidx.core:core-splashscreen:1.1.0-alpha02")
+}
+
+fun getSecretKey(propertyKey: String): String {
+ return gradleLocalProperties(rootDir).getProperty(propertyKey)
+}
diff --git a/android/festago/presentation-legacy/proguard-rules.pro b/android/festago/presentation-legacy/proguard-rules.pro
new file mode 100644
index 000000000..481bb4348
--- /dev/null
+++ b/android/festago/presentation-legacy/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/android/festago/presentation-legacy/src/main/AndroidManifest.xml b/android/festago/presentation-legacy/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..2f1063f45
--- /dev/null
+++ b/android/festago/presentation-legacy/src/main/AndroidManifest.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/fcm/FcmMessageType.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/fcm/FcmMessageType.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/fcm/FcmMessageType.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/fcm/FcmMessageType.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/fcm/NotificationManager.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/fcm/NotificationManager.kt
similarity index 97%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/fcm/NotificationManager.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/fcm/NotificationManager.kt
index 6abea300b..15a12572f 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/fcm/NotificationManager.kt
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/fcm/NotificationManager.kt
@@ -5,7 +5,7 @@ import android.content.Context
import android.content.Intent
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
-import com.festago.festago.R
+import com.festago.festago.presentation.R
import com.festago.festago.presentation.fcm.FcmMessageType.ENTRY_ALERT
import com.festago.festago.presentation.ui.home.HomeActivity
import com.festago.festago.presentation.util.checkNotificationPermission
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/fcm/TicketEntryService.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/fcm/TicketEntryService.kt
similarity index 90%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/fcm/TicketEntryService.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/fcm/TicketEntryService.kt
index b2061db31..891d75ad9 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/fcm/TicketEntryService.kt
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/fcm/TicketEntryService.kt
@@ -9,7 +9,9 @@ import kotlinx.coroutines.runBlocking
class TicketEntryService : FirebaseMessagingService() {
- private val notificationManager by lazy { NotificationManager(this) }
+ private val notificationManager by lazy {
+ NotificationManager(this)
+ }
override fun onMessageReceived(remoteMessage: RemoteMessage) {
when (remoteMessage.notification?.channelId) {
@@ -28,7 +30,7 @@ class TicketEntryService : FirebaseMessagingService() {
private fun handleEntryAlert(remoteMessage: RemoteMessage) {
notificationManager.sendEntryAlertNotification(
remoteMessage.notification?.title ?: "",
- remoteMessage.notification?.body ?: ""
+ remoteMessage.notification?.body ?: "",
)
}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/BindingAdapter.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/BindingAdapter.kt
similarity index 96%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/BindingAdapter.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/BindingAdapter.kt
index 320a72cdf..5c1966fa8 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/BindingAdapter.kt
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/BindingAdapter.kt
@@ -6,7 +6,7 @@ import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.core.view.doOnLayout
import androidx.databinding.BindingAdapter
import com.bumptech.glide.Glide
-import com.festago.festago.R
+import com.festago.festago.presentation.R
@BindingAdapter("visibility")
fun View.setVisibility(isVisible: Boolean) {
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/customview/OkDialogFragment.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/customview/OkDialogFragment.kt
similarity index 97%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/customview/OkDialogFragment.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/customview/OkDialogFragment.kt
index 1b3500a8a..c5ffd4c8c 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/customview/OkDialogFragment.kt
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/customview/OkDialogFragment.kt
@@ -9,7 +9,7 @@ import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import androidx.fragment.app.DialogFragment
-import com.festago.festago.databinding.FragmentOkDialogBinding
+import com.festago.festago.presentation.databinding.FragmentOkDialogBinding
import com.festago.festago.presentation.ui.customview.OkDialogFragment.OnClickListener
import dagger.hilt.android.AndroidEntryPoint
diff --git a/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/HomeActivity.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/HomeActivity.kt
new file mode 100644
index 000000000..7b6020e51
--- /dev/null
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/HomeActivity.kt
@@ -0,0 +1,177 @@
+package com.festago.festago.presentation.ui.home
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.widget.Toast
+import androidx.activity.addCallback
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import androidx.fragment.app.Fragment
+import com.festago.festago.presentation.R
+import com.festago.festago.presentation.databinding.ActivityHomeBinding
+import com.festago.festago.presentation.ui.home.festivallist.FestivalListFragment
+import com.festago.festago.presentation.ui.home.mypage.MyPageFragment
+import com.festago.festago.presentation.ui.home.ticketlist.TicketListFragment
+import com.festago.festago.presentation.ui.signin.SignInActivity
+import com.festago.festago.presentation.util.repeatOnStarted
+import com.festago.festago.presentation.util.requestNotificationPermission
+import com.google.android.material.navigation.NavigationBarView
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class HomeActivity : AppCompatActivity() {
+
+ private val binding by lazy { ActivityHomeBinding.inflate(layoutInflater) }
+
+ private val vm: HomeViewModel by viewModels()
+
+ private lateinit var resultLauncher: ActivityResultLauncher
+
+ private val navigationBarView by lazy { binding.nvHome as NavigationBarView }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ initBinding()
+ initView()
+ initObserve()
+ initResultLauncher()
+ initBackPressedDispatcher()
+ }
+
+ private fun initResultLauncher() {
+ resultLauncher =
+ registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+ if (result.resultCode == SignInActivity.RESULT_NOT_SIGN_IN) {
+ navigationBarView.selectedItemId = R.id.item_festival
+ }
+ }
+ initNotificationPermission()
+ }
+
+ private fun initBinding() {
+ setContentView(binding.root)
+ }
+
+ private fun initView() {
+ navigationBarView.setOnItemSelectedListener {
+ vm.selectItem(getItemType(it.itemId))
+ true
+ }
+
+ binding.fabTicket.setOnClickListener {
+ navigationBarView.selectedItemId = R.id.item_ticket
+ }
+
+ changeFragment()
+ }
+
+ private fun initObserve() {
+ repeatOnStarted(this) {
+ vm.event.collect { event ->
+ when (event) {
+ is HomeEvent.ShowSignIn -> showSignIn()
+ }
+ }
+ }
+
+ repeatOnStarted(this) {
+ vm.selectedItem.collect { homeItemType ->
+ when (homeItemType) {
+ HomeItemType.FESTIVAL_LIST -> showFestivalList()
+ HomeItemType.TICKET_LIST -> showTicketList()
+ HomeItemType.MY_PAGE -> showMyPage()
+ }
+ }
+ }
+ }
+
+ private fun initNotificationPermission() {
+ val requestPermissionLauncher = registerForActivityResult(
+ ActivityResultContracts.RequestPermission(),
+ ) { isGranted: Boolean ->
+ if (!isGranted) {
+ Toast.makeText(
+ this,
+ getString(R.string.home_notification_permission_denied),
+ Toast.LENGTH_SHORT,
+ ).show()
+ }
+ }
+ requestNotificationPermission(requestPermissionLauncher)
+ }
+
+ private fun getItemType(menuItemId: Int): HomeItemType {
+ return when (menuItemId) {
+ R.id.item_festival -> HomeItemType.FESTIVAL_LIST
+ R.id.item_mypage -> HomeItemType.MY_PAGE
+ R.id.item_ticket -> HomeItemType.TICKET_LIST
+ else -> throw IllegalArgumentException("menu item id not found")
+ }
+ }
+
+ private fun showFestivalList() {
+ changeFragment()
+ binding.fabTicket.isSelected = false
+ }
+
+ private fun showTicketList() {
+ changeFragment()
+ binding.fabTicket.isSelected = true
+ }
+
+ private fun showMyPage() {
+ changeFragment()
+ binding.fabTicket.isSelected = false
+ }
+
+ private fun showSignIn() {
+ resultLauncher.launch(SignInActivity.getIntent(this))
+ }
+
+ private fun initBackPressedDispatcher() {
+ var backPressedTime = START_BACK_PRESSED_TIME
+ onBackPressedDispatcher.addCallback {
+ if ((System.currentTimeMillis() - backPressedTime) > FINISH_BACK_PRESSED_TIME) {
+ backPressedTime = System.currentTimeMillis()
+ Toast.makeText(
+ this@HomeActivity,
+ getString(R.string.home_back_pressed),
+ Toast.LENGTH_SHORT,
+ ).show()
+ } else {
+ finish()
+ }
+ }
+ }
+
+ private inline fun changeFragment() {
+ val tag = T::class.java.name
+ val fragmentTransaction = supportFragmentManager.beginTransaction()
+
+ supportFragmentManager.fragments.forEach { fragment ->
+ fragmentTransaction.hide(fragment)
+ }
+
+ var targetFragment = supportFragmentManager.findFragmentByTag(tag)
+
+ if (targetFragment == null) {
+ targetFragment = T::class.java.newInstance()
+ fragmentTransaction.add(R.id.fcv_home_container, targetFragment, tag)
+ } else {
+ fragmentTransaction.show(targetFragment)
+ }
+
+ fragmentTransaction.commit()
+ }
+
+ companion object {
+ private const val START_BACK_PRESSED_TIME = 0L
+ private const val FINISH_BACK_PRESSED_TIME = 3000L
+ fun getIntent(context: Context): Intent {
+ return Intent(context, HomeActivity::class.java)
+ }
+ }
+}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/HomeEvent.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/HomeEvent.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/HomeEvent.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/HomeEvent.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/HomeItemType.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/HomeItemType.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/HomeItemType.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/HomeItemType.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/HomeViewModel.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/HomeViewModel.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/HomeViewModel.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/HomeViewModel.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalItemUiState.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalItemUiState.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalItemUiState.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalItemUiState.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalItemViewHolder.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalItemViewHolder.kt
similarity index 90%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalItemViewHolder.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalItemViewHolder.kt
index b07700b58..a303399c7 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalItemViewHolder.kt
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalItemViewHolder.kt
@@ -3,7 +3,7 @@ package com.festago.festago.presentation.ui.home.festivallist
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
-import com.festago.festago.databinding.ItemFestivalListBinding
+import com.festago.festago.presentation.databinding.ItemFestivalListBinding
class FestivalItemViewHolder(
private val binding: ItemFestivalListBinding,
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListAdapter.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListAdapter.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListAdapter.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListAdapter.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListEvent.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListEvent.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListEvent.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListEvent.kt
diff --git a/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListFragment.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListFragment.kt
new file mode 100644
index 000000000..ba09c5237
--- /dev/null
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListFragment.kt
@@ -0,0 +1,138 @@
+package com.festago.festago.presentation.ui.home.festivallist
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.recyclerview.widget.GridLayoutManager
+import com.festago.festago.model.FestivalFilter
+import com.festago.festago.presentation.R
+import com.festago.festago.presentation.databinding.FragmentFestivalListBinding
+import com.festago.festago.presentation.ui.home.ticketlist.TicketListFragment
+import com.festago.festago.presentation.ui.ticketreserve.TicketReserveActivity
+import com.festago.festago.presentation.util.repeatOnStarted
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class FestivalListFragment : Fragment(R.layout.fragment_festival_list) {
+
+ private var _binding: FragmentFestivalListBinding? = null
+ private val binding get() = _binding!!
+
+ private val vm: FestivalListViewModel by viewModels()
+
+ private lateinit var adapter: FestivalListAdapter
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ _binding = FragmentFestivalListBinding.inflate(inflater)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ initObserve()
+ initView()
+ }
+
+ private fun initObserve() {
+ repeatOnStarted(viewLifecycleOwner) {
+ vm.uiState.collect {
+ binding.uiState = it
+ updateUi(it)
+ }
+ }
+ repeatOnStarted(viewLifecycleOwner) {
+ vm.event.collect {
+ handleEvent(it)
+ }
+ }
+ }
+
+ private val Int.dp: Int get() = (this / resources.displayMetrics.density).toInt()
+
+ private fun initView() {
+ adapter = FestivalListAdapter()
+ binding.rvFestivalList.adapter = adapter
+
+ initFestivalListSpanSize()
+ initRefresh()
+ initFestivalFilters()
+ if (vm.uiState.value is FestivalListUiState.Loading) {
+ loadFestivalsBy(binding.cgFilterOption.checkedChipId)
+ }
+ }
+
+ private fun initFestivalListSpanSize() {
+ binding.rvFestivalList.layoutManager.apply {
+ if (this is GridLayoutManager) {
+ val spanSize = (resources.displayMetrics.widthPixels.dp / 160)
+ spanCount = when {
+ spanSize < 2 -> 2
+ spanSize > 4 -> 4
+ else -> spanSize
+ }
+ }
+ }
+ }
+
+ private fun initRefresh() {
+ binding.srlFestivalList.setOnRefreshListener {
+ loadFestivalsBy(binding.cgFilterOption.checkedChipId)
+ binding.srlFestivalList.isRefreshing = false
+ }
+ }
+
+ private fun initFestivalFilters() {
+ binding.cgFilterOption.setOnCheckedStateChangeListener { group, _ ->
+ loadFestivalsBy(checkedChipId = group.checkedChipId)
+ }
+ }
+
+ private fun loadFestivalsBy(checkedChipId: Int) {
+ when (checkedChipId) {
+ R.id.chipProgress -> vm.loadFestivals(FestivalFilter.PROGRESS)
+ R.id.chipPlanned -> vm.loadFestivals(FestivalFilter.PLANNED)
+ R.id.chipEnd -> vm.loadFestivals(FestivalFilter.END)
+ }
+ }
+
+ private fun updateUi(uiState: FestivalListUiState) {
+ when (uiState) {
+ is FestivalListUiState.Loading,
+ is FestivalListUiState.Error,
+ -> Unit
+
+ is FestivalListUiState.Success -> handleSuccess(uiState)
+ }
+ }
+
+ private fun handleSuccess(uiState: FestivalListUiState.Success) {
+ adapter.submitList(uiState.festivals)
+ }
+
+ private fun handleEvent(event: FestivalListEvent) {
+ when (event) {
+ is FestivalListEvent.ShowTicketReserve -> {
+ removeTicketListFragment()
+ startActivity(TicketReserveActivity.getIntent(requireContext(), event.festivalId))
+ }
+ }
+ }
+
+ private fun removeTicketListFragment() {
+ parentFragmentManager.findFragmentByTag(TicketListFragment::class.java.name)?.let {
+ parentFragmentManager.beginTransaction().remove(it).commit()
+ }
+ }
+
+ override fun onDestroyView() {
+ _binding = null
+ super.onDestroyView()
+ }
+}
diff --git a/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListUiState.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListUiState.kt
new file mode 100644
index 000000000..8ba2e7774
--- /dev/null
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListUiState.kt
@@ -0,0 +1,18 @@
+package com.festago.festago.presentation.ui.home.festivallist
+
+sealed interface FestivalListUiState {
+ object Loading : FestivalListUiState
+
+ data class Success(
+ val festivals: List,
+ ) : FestivalListUiState {
+ val hasFestival get() = festivals.isNotEmpty()
+ }
+
+ object Error : FestivalListUiState
+
+ val shouldShowSuccess get() = this is Success && hasFestival
+ val shouldShowSuccessAndEmpty get() = this is Success && !hasFestival
+ val shouldShowLoading get() = this is Loading
+ val shouldShowError get() = this is Error
+}
diff --git a/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListViewModel.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListViewModel.kt
new file mode 100644
index 000000000..66808564b
--- /dev/null
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListViewModel.kt
@@ -0,0 +1,64 @@
+package com.festago.festago.presentation.ui.home.festivallist
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.festago.festago.common.analytics.AnalyticsHelper
+import com.festago.festago.common.analytics.logNetworkFailure
+import com.festago.festago.model.FestivalFilter
+import com.festago.festago.presentation.ui.home.festivallist.FestivalListEvent.ShowTicketReserve
+import com.festago.festago.repository.FestivalRepository
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class FestivalListViewModel @Inject constructor(
+ private val festivalRepository: FestivalRepository,
+ private val analyticsHelper: AnalyticsHelper,
+) : ViewModel() {
+
+ private val _uiState = MutableStateFlow(FestivalListUiState.Loading)
+ val uiState: StateFlow = _uiState.asStateFlow()
+
+ private val _event = MutableSharedFlow()
+ val event: SharedFlow = _event.asSharedFlow()
+
+ fun loadFestivals(festivalFilter: FestivalFilter) {
+ viewModelScope.launch {
+ festivalRepository.loadFestivals(festivalFilter)
+ .onSuccess {
+ _uiState.value = FestivalListUiState.Success(
+ festivals = it.map { festival ->
+ FestivalItemUiState(
+ id = festival.id,
+ name = festival.name,
+ startDate = festival.startDate,
+ endDate = festival.endDate,
+ thumbnail = festival.thumbnail,
+ onFestivalDetail = ::showTicketReserve,
+ )
+ },
+ )
+ }.onFailure {
+ _uiState.value = FestivalListUiState.Error
+ analyticsHelper.logNetworkFailure(KEY_LOAD_FESTIVALS_LOG, it.message.toString())
+ }
+ }
+ }
+
+ fun showTicketReserve(festivalId: Long) {
+ viewModelScope.launch {
+ _event.emit(ShowTicketReserve(festivalId))
+ }
+ }
+
+ companion object {
+ private const val KEY_LOAD_FESTIVALS_LOG = "load_festivals"
+ }
+}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/mypage/MyPageEvent.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/mypage/MyPageEvent.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/mypage/MyPageEvent.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/mypage/MyPageEvent.kt
diff --git a/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/mypage/MyPageFragment.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/mypage/MyPageFragment.kt
new file mode 100644
index 000000000..9089b0d39
--- /dev/null
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/mypage/MyPageFragment.kt
@@ -0,0 +1,134 @@
+package com.festago.festago.presentation.ui.home.mypage
+
+import android.app.AlertDialog
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import com.festago.festago.presentation.R
+import com.festago.festago.presentation.databinding.FragmentMyPageBinding
+import com.festago.festago.presentation.ui.home.HomeActivity
+import com.festago.festago.presentation.ui.selectschool.SelectSchoolActivity
+import com.festago.festago.presentation.ui.signin.SignInActivity
+import com.festago.festago.presentation.ui.tickethistory.TicketHistoryActivity
+import com.festago.festago.presentation.util.repeatOnStarted
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class MyPageFragment : Fragment(R.layout.fragment_my_page) {
+
+ private var _binding: FragmentMyPageBinding? = null
+ private val binding get() = _binding!!
+
+ private val vm: MyPageViewModel by viewModels()
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ _binding = FragmentMyPageBinding.inflate(inflater)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ initObserve()
+ initView()
+ }
+
+ private fun initObserve() {
+ repeatOnStarted(viewLifecycleOwner) {
+ vm.uiState.collect { uiState ->
+ handleUiState(uiState)
+ }
+ }
+ repeatOnStarted(viewLifecycleOwner) {
+ vm.event.collect { event ->
+ handleEvent(event)
+ }
+ }
+ }
+
+ private fun handleUiState(uiState: MyPageUiState) {
+ binding.uiState = uiState
+ when (uiState) {
+ is MyPageUiState.Loading, is MyPageUiState.Error -> Unit
+
+ is MyPageUiState.Success -> handleSuccess(uiState)
+ }
+ }
+
+ private fun handleEvent(event: MyPageEvent) {
+ when (event) {
+ is MyPageEvent.ShowSignIn -> handleShowSignInEvent()
+ is MyPageEvent.SignOutSuccess -> handleSignOutSuccessEvent()
+ is MyPageEvent.DeleteAccountSuccess -> handleDeleteAccountSuccess()
+ is MyPageEvent.ShowTicketHistory -> handleShowTicketHistory()
+ is MyPageEvent.ShowConfirmDelete -> handleShowConfirmDelete()
+ }
+ }
+
+ private fun handleShowSignInEvent() {
+ startActivity(SignInActivity.getIntent(requireContext()))
+ }
+
+ private fun handleSignOutSuccessEvent() {
+ restartHome()
+ }
+
+ private fun handleDeleteAccountSuccess() {
+ restartHome()
+ }
+
+ private fun restartHome() {
+ requireActivity().finishAffinity()
+ startActivity(HomeActivity.getIntent(requireContext()))
+ }
+
+ private fun handleShowTicketHistory() {
+ startActivity(TicketHistoryActivity.getIntent(requireContext()))
+ }
+
+ private fun handleShowConfirmDelete() {
+ val dialog = AlertDialog.Builder(requireContext()).apply {
+ setTitle(getString(R.string.confirm_delete_dialog_title))
+ setMessage(getString(R.string.confirm_delete_dialog_message))
+ setPositiveButton(getString(R.string.confirm_delete_dialog_yes)) { dialog, _ ->
+ vm.deleteAccount()
+ dialog.dismiss()
+ }
+ setNegativeButton(getString(R.string.confirm_delete_dialog_no)) { dialog, _ ->
+ dialog.dismiss()
+ }
+ }
+ dialog.show()
+ }
+
+ private fun initView() {
+ binding.vm = vm
+
+ vm.loadUserInfo()
+
+ binding.srlMyPage.setOnRefreshListener {
+ vm.loadUserInfo()
+ binding.srlMyPage.isRefreshing = false
+ }
+
+ binding.tvSchoolAuthorization.setOnClickListener {
+ startActivity(SelectSchoolActivity.getIntent(requireContext()))
+ }
+ }
+
+ private fun handleSuccess(uiState: MyPageUiState.Success) {
+ binding.successState = uiState
+ }
+
+ override fun onDestroyView() {
+ _binding = null
+ super.onDestroyView()
+ }
+}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/mypage/MyPageUiState.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/mypage/MyPageUiState.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/mypage/MyPageUiState.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/mypage/MyPageUiState.kt
diff --git a/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/mypage/MyPageViewModel.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/mypage/MyPageViewModel.kt
new file mode 100644
index 000000000..77cf032bf
--- /dev/null
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/mypage/MyPageViewModel.kt
@@ -0,0 +1,109 @@
+package com.festago.festago.presentation.ui.home.mypage
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.festago.festago.common.analytics.AnalyticsHelper
+import com.festago.festago.common.analytics.logNetworkFailure
+import com.festago.festago.repository.AuthRepository
+import com.festago.festago.repository.TicketRepository
+import com.festago.festago.repository.UserRepository
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.async
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class MyPageViewModel @Inject constructor(
+ private val userRepository: UserRepository,
+ private val ticketRepository: TicketRepository,
+ private val authRepository: AuthRepository,
+ private val analyticsHelper: AnalyticsHelper,
+) : ViewModel() {
+
+ private val _uiState = MutableStateFlow(MyPageUiState.Loading)
+ val uiState: StateFlow = _uiState.asStateFlow()
+
+ private val _event = MutableSharedFlow()
+ val event: SharedFlow = _event.asSharedFlow()
+
+ fun loadUserInfo() {
+ if (!authRepository.isSigned) {
+ viewModelScope.launch {
+ _event.emit(MyPageEvent.ShowSignIn)
+ _uiState.value = MyPageUiState.Error
+ }
+ return
+ }
+ viewModelScope.launch {
+ val deferredUserProfile = async { userRepository.loadUserProfile() }
+ val deferredHistoryTicket = async { ticketRepository.loadHistoryTickets(size = 1) }
+
+ runCatching {
+ _uiState.value = MyPageUiState.Success(
+ userProfile = deferredUserProfile.await().getOrThrow(),
+ ticket = deferredHistoryTicket.await().getOrThrow().firstOrNull(),
+ )
+ }.onFailure {
+ _uiState.value = MyPageUiState.Error
+ analyticsHelper.logNetworkFailure(
+ key = KEY_LOAD_USER_INFO,
+ value = it.message.toString(),
+ )
+ }
+ }
+ }
+
+ fun signOut() {
+ viewModelScope.launch {
+ authRepository.signOut()
+ .onSuccess {
+ _event.emit(MyPageEvent.SignOutSuccess)
+ _uiState.value = MyPageUiState.Error
+ }.onFailure {
+ analyticsHelper.logNetworkFailure(
+ key = KEY_SIGN_OUT,
+ value = it.message.toString(),
+ )
+ }
+ }
+ }
+
+ fun showConfirmDelete() {
+ viewModelScope.launch {
+ _event.emit(MyPageEvent.ShowConfirmDelete)
+ }
+ }
+
+ fun deleteAccount() {
+ viewModelScope.launch {
+ authRepository.deleteAccount()
+ .onSuccess {
+ _event.emit(MyPageEvent.DeleteAccountSuccess)
+ _uiState.value = MyPageUiState.Error
+ }.onFailure {
+ analyticsHelper.logNetworkFailure(
+ key = KEY_DELETE_ACCOUNT,
+ value = it.message.toString(),
+ )
+ }
+ }
+ }
+
+ fun showTicketHistory() {
+ viewModelScope.launch {
+ _event.emit(MyPageEvent.ShowTicketHistory)
+ }
+ }
+
+ companion object {
+ private const val KEY_LOAD_USER_INFO = "loadUserInfo"
+ private const val KEY_SIGN_OUT = "KEY_SIGN_OUT"
+ private const val KEY_DELETE_ACCOUNT = "KEY_DELETE_ACCOUNT"
+ }
+}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListAdapter.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListAdapter.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListAdapter.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListAdapter.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListEvent.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListEvent.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListEvent.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListEvent.kt
diff --git a/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListFragment.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListFragment.kt
new file mode 100644
index 000000000..b52258f88
--- /dev/null
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListFragment.kt
@@ -0,0 +1,116 @@
+package com.festago.festago.presentation.ui.home.ticketlist
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import com.festago.festago.presentation.R
+import com.festago.festago.presentation.databinding.FragmentTicketListBinding
+import com.festago.festago.presentation.ui.ticketentry.TicketEntryActivity
+import com.festago.festago.presentation.util.repeatOnStarted
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class TicketListFragment : Fragment(R.layout.fragment_ticket_list) {
+
+ private var _binding: FragmentTicketListBinding? = null
+ private val binding get() = _binding!!
+
+ private lateinit var adapter: TicketListAdapter
+
+ private lateinit var resultLauncher: ActivityResultLauncher
+
+ private val vm: TicketListViewModel by viewModels()
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ _binding = FragmentTicketListBinding.inflate(inflater)
+ binding.lifecycleOwner = viewLifecycleOwner
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ initObserve()
+ initView()
+ initActivityResult()
+ }
+
+ private fun initObserve() {
+ repeatOnStarted(viewLifecycleOwner) {
+ vm.uiState.collect {
+ binding.uiState = it
+ updateUi(it)
+ }
+ }
+ repeatOnStarted(viewLifecycleOwner) {
+ vm.event.collect { event ->
+ handleEvent(event)
+ }
+ }
+ }
+
+ private fun updateUi(uiState: TicketListUiState) {
+ when (uiState) {
+ is TicketListUiState.Loading,
+ is TicketListUiState.Error,
+ -> Unit
+
+ is TicketListUiState.Success -> {
+ adapter.submitList(uiState.tickets)
+ }
+ }
+ }
+
+ private fun handleEvent(event: TicketListEvent) {
+ when (event) {
+ is TicketListEvent.ShowTicketEntry -> showTicketEntry(event)
+ }
+ }
+
+ private fun showTicketEntry(event: TicketListEvent.ShowTicketEntry) {
+ resultLauncher.launch(
+ TicketEntryActivity.getIntent(
+ context = requireContext(),
+ ticketId = event.ticketId,
+ ),
+ )
+ }
+
+ private fun initActivityResult() {
+ resultLauncher =
+ registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+ if (result.resultCode == TicketEntryActivity.RESULT_OK) {
+ requireActivity().supportFragmentManager.beginTransaction()
+ .replace(R.id.fcv_home_container, TicketListFragment()).commit()
+ }
+ }
+ }
+
+ private fun initView() {
+ adapter = TicketListAdapter()
+ binding.rvTicketList.adapter = adapter
+ vm.loadCurrentTickets()
+ initRefresh()
+ }
+
+ private fun initRefresh() {
+ binding.srlTicketList.setOnRefreshListener {
+ vm.loadCurrentTickets()
+ binding.srlTicketList.isRefreshing = false
+ }
+ }
+
+ override fun onDestroyView() {
+ _binding = null
+ super.onDestroyView()
+ }
+}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListItemUiState.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListItemUiState.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListItemUiState.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListItemUiState.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListItemViewHolder.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListItemViewHolder.kt
similarity index 94%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListItemViewHolder.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListItemViewHolder.kt
index ebd8045c4..3165e3b24 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListItemViewHolder.kt
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListItemViewHolder.kt
@@ -4,9 +4,9 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.Button
import androidx.recyclerview.widget.RecyclerView
-import com.festago.festago.R
-import com.festago.festago.databinding.ItemTicketListBinding
import com.festago.festago.model.TicketCondition
+import com.festago.festago.presentation.R
+import com.festago.festago.presentation.databinding.ItemTicketListBinding
import java.time.format.DateTimeFormatter
class TicketListItemViewHolder(
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListUiState.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListUiState.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListUiState.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListUiState.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListViewModel.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListViewModel.kt
similarity index 93%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListViewModel.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListViewModel.kt
index ce15bfd2b..379be7b25 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListViewModel.kt
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListViewModel.kt
@@ -2,8 +2,8 @@ package com.festago.festago.presentation.ui.home.ticketlist
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
-import com.festago.festago.analytics.AnalyticsHelper
-import com.festago.festago.analytics.logNetworkFailure
+import com.festago.festago.common.analytics.AnalyticsHelper
+import com.festago.festago.common.analytics.logNetworkFailure
import com.festago.festago.repository.TicketRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/reservationcomplete/ReservationCompleteActivity.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/reservationcomplete/ReservationCompleteActivity.kt
similarity index 94%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/reservationcomplete/ReservationCompleteActivity.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/reservationcomplete/ReservationCompleteActivity.kt
index 145bbb44d..1b45eb5e7 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/reservationcomplete/ReservationCompleteActivity.kt
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/reservationcomplete/ReservationCompleteActivity.kt
@@ -4,7 +4,7 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
-import com.festago.festago.databinding.ActivityReservationCompleteBinding
+import com.festago.festago.presentation.databinding.ActivityReservationCompleteBinding
import com.festago.festago.presentation.util.getParcelableExtraCompat
import dagger.hilt.android.AndroidEntryPoint
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/reservationcomplete/ReservedTicketArg.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/reservationcomplete/ReservedTicketArg.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/reservationcomplete/ReservedTicketArg.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/reservationcomplete/ReservedTicketArg.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/selectschool/SelectSchoolActivity.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/selectschool/SelectSchoolActivity.kt
similarity index 95%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/selectschool/SelectSchoolActivity.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/selectschool/SelectSchoolActivity.kt
index 595016076..97457a100 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/selectschool/SelectSchoolActivity.kt
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/selectschool/SelectSchoolActivity.kt
@@ -6,8 +6,8 @@ import android.os.Bundle
import android.widget.ArrayAdapter
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
-import com.festago.festago.R
-import com.festago.festago.databinding.ActivitySelectSchoolBinding
+import com.festago.festago.presentation.R
+import com.festago.festago.presentation.databinding.ActivitySelectSchoolBinding
import com.festago.festago.presentation.ui.studentverification.StudentVerificationActivity
import com.festago.festago.presentation.util.repeatOnStarted
import dagger.hilt.android.AndroidEntryPoint
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/selectschool/SelectSchoolEvent.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/selectschool/SelectSchoolEvent.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/selectschool/SelectSchoolEvent.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/selectschool/SelectSchoolEvent.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/selectschool/SelectSchoolUiState.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/selectschool/SelectSchoolUiState.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/selectschool/SelectSchoolUiState.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/selectschool/SelectSchoolUiState.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/selectschool/SelectSchoolViewModel.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/selectschool/SelectSchoolViewModel.kt
similarity index 95%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/selectschool/SelectSchoolViewModel.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/selectschool/SelectSchoolViewModel.kt
index 5a7bd892e..e1c7881c1 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/selectschool/SelectSchoolViewModel.kt
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/selectschool/SelectSchoolViewModel.kt
@@ -2,8 +2,8 @@ package com.festago.festago.presentation.ui.selectschool
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
-import com.festago.festago.analytics.AnalyticsHelper
-import com.festago.festago.analytics.logNetworkFailure
+import com.festago.festago.common.analytics.AnalyticsHelper
+import com.festago.festago.common.analytics.logNetworkFailure
import com.festago.festago.repository.SchoolRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
diff --git a/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/signin/SignInActivity.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/signin/SignInActivity.kt
new file mode 100644
index 000000000..17b3ecc01
--- /dev/null
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/signin/SignInActivity.kt
@@ -0,0 +1,111 @@
+package com.festago.festago.presentation.ui.signin
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.text.Spannable
+import android.text.SpannableStringBuilder
+import android.text.style.ForegroundColorSpan
+import androidx.activity.OnBackPressedCallback
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import com.festago.festago.presentation.R
+import com.festago.festago.presentation.databinding.ActivitySignInBinding
+import com.festago.festago.presentation.ui.customview.OkDialogFragment
+import com.festago.festago.presentation.ui.home.HomeActivity
+import com.festago.festago.presentation.util.repeatOnStarted
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class SignInActivity : AppCompatActivity() {
+
+ private lateinit var binding: ActivitySignInBinding
+
+ private val vm: SignInViewModel by viewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ initBinding()
+ initView()
+ initObserve()
+ }
+
+ private fun initBinding() {
+ binding = ActivitySignInBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+ }
+
+ private fun initView() {
+ binding.lifecycleOwner = this
+ binding.vm = vm
+ initComment()
+ initBackPressed()
+ }
+
+ private fun initBackPressed() {
+ val callback = object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ setResult(RESULT_NOT_SIGN_IN, intent)
+ finish()
+ }
+ }
+ this.onBackPressedDispatcher.addCallback(this, callback)
+ }
+
+ private fun initObserve() {
+ repeatOnStarted(this) {
+ vm.event.collect { event ->
+ when (event) {
+ is SignInEvent.SignInSuccess -> handleSuccessEvent()
+ is SignInEvent.SignInFailure -> handleFailureEvent()
+ }
+ }
+ }
+ }
+
+ private fun initComment() {
+ val spannableStringBuilder = SpannableStringBuilder(
+ getString(R.string.mypage_tv_signin_description),
+ ).apply {
+ setSpan(
+ ForegroundColorSpan(getColor(R.color.seed)),
+ COLOR_SPAN_START_INDEX,
+ COLOR_SPAN_END_INDEX,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
+ )
+ }
+ binding.tvLoginDescription.text = spannableStringBuilder
+ }
+
+ private fun handleSuccessEvent() {
+ showHomeWithFinish()
+ }
+
+ private fun handleFailureEvent() {
+ val dialog = OkDialogFragment.newInstance(FAILURE_SIGN_IN).apply {
+ listener = OkDialogFragment.OnClickListener {
+ showHomeWithFinish()
+ }
+ }
+ dialog.show(supportFragmentManager, OkDialogFragment::class.java.name)
+ }
+
+ private fun showHomeWithFinish() {
+ val intent = HomeActivity.getIntent(this).apply {
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
+ }
+ finishAffinity()
+ startActivity(intent)
+ }
+
+ companion object {
+ private const val COLOR_SPAN_START_INDEX = 0
+ private const val COLOR_SPAN_END_INDEX = 4
+
+ private const val FAILURE_SIGN_IN = "로그인에 실패했습니다."
+ const val RESULT_NOT_SIGN_IN = 1
+ fun getIntent(context: Context): Intent {
+ return Intent(context, SignInActivity::class.java)
+ }
+ }
+}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/signin/SignInEvent.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/signin/SignInEvent.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/signin/SignInEvent.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/signin/SignInEvent.kt
diff --git a/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/signin/SignInViewModel.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/signin/SignInViewModel.kt
new file mode 100644
index 000000000..a3be0826d
--- /dev/null
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/signin/SignInViewModel.kt
@@ -0,0 +1,37 @@
+package com.festago.festago.presentation.ui.signin
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.festago.festago.common.analytics.AnalyticsHelper
+import com.festago.festago.common.analytics.logNetworkFailure
+import com.festago.festago.repository.AuthRepository
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class SignInViewModel @Inject constructor(
+ private val authRepository: AuthRepository,
+ private val analyticsHelper: AnalyticsHelper,
+) : ViewModel() {
+
+ private val _event = MutableSharedFlow()
+ val event: SharedFlow = _event
+
+ fun signIn() {
+ viewModelScope.launch {
+ authRepository.signIn().onSuccess {
+ _event.emit(SignInEvent.SignInSuccess)
+ }.onFailure {
+ _event.emit(SignInEvent.SignInFailure)
+ analyticsHelper.logNetworkFailure(KEY_SIGN_IN_LOG, it.message.toString())
+ }
+ }
+ }
+
+ companion object {
+ private const val KEY_SIGN_IN_LOG = "KEY_SIGN_IN_LOG"
+ }
+}
diff --git a/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/splash/SplashActivity.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/splash/SplashActivity.kt
new file mode 100644
index 000000000..541007137
--- /dev/null
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/splash/SplashActivity.kt
@@ -0,0 +1,101 @@
+package com.festago.festago.presentation.ui.splash
+
+import android.annotation.SuppressLint
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.util.Log
+import android.widget.Toast
+import androidx.activity.ComponentActivity
+import androidx.appcompat.app.AlertDialog
+import androidx.core.splashscreen.SplashScreen
+import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
+import com.festago.festago.presentation.BuildConfig
+import com.festago.festago.presentation.R
+import com.festago.festago.presentation.databinding.ActivitySplashBinding
+import com.festago.festago.presentation.ui.home.HomeActivity
+import com.google.android.play.core.appupdate.AppUpdateInfo
+import com.google.android.play.core.appupdate.AppUpdateManagerFactory
+import com.google.android.play.core.install.model.UpdateAvailability
+import com.kakao.sdk.common.KakaoSdk
+import dagger.hilt.android.AndroidEntryPoint
+
+@SuppressLint("CustomSplashScreen")
+@AndroidEntryPoint
+class SplashActivity : ComponentActivity() {
+
+ val binding by lazy {
+ ActivitySplashBinding.inflate(layoutInflater)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val splashScreen = installSplashScreen()
+ splashScreen.setKeepOnScreenCondition { true }
+ initKakaoSdk()
+ checkAppUpdate(splashScreen)
+ setContentView(binding.root)
+ }
+
+ private fun initKakaoSdk() {
+ Log.d("SplashActivity", "initKakaoSdk: ${BuildConfig.KAKAO_NATIVE_APP_KEY}")
+ KakaoSdk.init(this.applicationContext, BuildConfig.KAKAO_NATIVE_APP_KEY)
+ }
+
+ private fun checkAppUpdate(splashScreen: SplashScreen) {
+ val appUpdateManager = AppUpdateManagerFactory.create(this)
+ appUpdateManager.appUpdateInfo
+ .addOnSuccessListener { appUpdateInfo ->
+ handleOnSuccess(appUpdateInfo, splashScreen)
+ }.addOnFailureListener {
+ showHome()
+ }
+ }
+
+ private fun handleOnSuccess(appUpdateInfo: AppUpdateInfo, splashScreen: SplashScreen) {
+ if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) {
+ splashScreen.setKeepOnScreenCondition { false }
+ requestUpdate()
+ } else {
+ showHome()
+ }
+ }
+
+ private fun showHome() {
+ startActivity(HomeActivity.getIntent(this))
+ finish()
+ }
+
+ private fun requestUpdate() {
+ AlertDialog.Builder(this).apply {
+ setTitle(getString(R.string.splash_app_update_request_dialog_title))
+ setMessage(getString(R.string.splash_app_update_request_dialog_message))
+ setNegativeButton(R.string.ok_dialog_btn_cancel) { _, _ ->
+ handleCancelUpdate()
+ }
+ setPositiveButton(R.string.ok_dialog_btn_ok) { _, _ ->
+ handleOkUpdate()
+ }
+ setCancelable(false)
+ }.show()
+ }
+
+ private fun handleCancelUpdate() {
+ Toast.makeText(
+ this@SplashActivity,
+ getString(R.string.splash_app_update_denied),
+ Toast.LENGTH_SHORT,
+ ).show()
+ finish()
+ }
+
+ private fun handleOkUpdate() {
+ navigateToAppStore()
+ finish()
+ }
+
+ private fun navigateToAppStore() {
+ startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=$packageName")))
+ finish()
+ }
+}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationActivity.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationActivity.kt
similarity index 93%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationActivity.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationActivity.kt
index 349ec05d4..2f9b619ab 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationActivity.kt
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationActivity.kt
@@ -5,11 +5,12 @@ import android.content.Intent
import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
-import com.festago.festago.R
-import com.festago.festago.databinding.ActivityStudentVerificationBinding
+import com.festago.festago.presentation.R
+import com.festago.festago.presentation.databinding.ActivityStudentVerificationBinding
import com.festago.festago.presentation.ui.customview.OkDialogFragment
import com.festago.festago.presentation.ui.home.HomeActivity
import com.festago.festago.presentation.util.repeatOnStarted
+import com.festago.festago.presentation.util.setOnSingleClickListener
import dagger.hilt.android.AndroidEntryPoint
import java.time.LocalTime
import java.time.format.DateTimeFormatter
@@ -43,7 +44,7 @@ class StudentVerificationActivity : AppCompatActivity() {
}
private fun initRequestVerificationCodeBtn(schoolId: Long) {
- binding.btnRequestVerificationCode.setOnClickListener {
+ binding.btnRequestVerificationCode.setOnSingleClickListener {
vm.sendVerificationCode(binding.tieUserName.text.toString(), schoolId)
}
}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationEvent.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationEvent.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationEvent.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationEvent.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationUiState.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationUiState.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationUiState.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationUiState.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationViewModel.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationViewModel.kt
similarity index 97%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationViewModel.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationViewModel.kt
index 39f0ca38b..102005fc5 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationViewModel.kt
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationViewModel.kt
@@ -2,8 +2,8 @@ package com.festago.festago.presentation.ui.studentverification
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
-import com.festago.festago.analytics.AnalyticsHelper
-import com.festago.festago.analytics.logNetworkFailure
+import com.festago.festago.common.analytics.AnalyticsHelper
+import com.festago.festago.common.analytics.logNetworkFailure
import com.festago.festago.model.StudentVerificationCode
import com.festago.festago.model.timer.Timer
import com.festago.festago.model.timer.TimerListener
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryActivity.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryActivity.kt
similarity index 97%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryActivity.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryActivity.kt
index 74094c06b..085172bfc 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryActivity.kt
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryActivity.kt
@@ -8,7 +8,7 @@ import androidx.activity.OnBackPressedCallback
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.res.ResourcesCompat
-import com.festago.festago.databinding.ActivityTicketEntryBinding
+import com.festago.festago.presentation.databinding.ActivityTicketEntryBinding
import com.festago.festago.presentation.fcm.TicketEntryService
import com.festago.festago.presentation.util.repeatOnStarted
import com.google.zxing.BarcodeFormat
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryUiState.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryUiState.kt
similarity index 98%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryUiState.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryUiState.kt
index 1ab505157..d45407b2b 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryUiState.kt
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryUiState.kt
@@ -1,11 +1,11 @@
package com.festago.festago.presentation.ui.ticketentry
-import com.festago.festago.R
import com.festago.festago.model.Ticket
import com.festago.festago.model.TicketCode
import com.festago.festago.model.TicketCondition.AFTER_ENTRY
import com.festago.festago.model.TicketCondition.AWAY
import com.festago.festago.model.TicketCondition.BEFORE_ENTRY
+import com.festago.festago.presentation.R
sealed interface TicketEntryUiState {
object Loading : TicketEntryUiState
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryViewModel.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryViewModel.kt
similarity index 96%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryViewModel.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryViewModel.kt
index ae14081a2..0e60abe49 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryViewModel.kt
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryViewModel.kt
@@ -2,8 +2,8 @@ package com.festago.festago.presentation.ui.ticketentry
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
-import com.festago.festago.analytics.AnalyticsHelper
-import com.festago.festago.analytics.logNetworkFailure
+import com.festago.festago.common.analytics.AnalyticsHelper
+import com.festago.festago.common.analytics.logNetworkFailure
import com.festago.festago.model.Ticket
import com.festago.festago.model.TicketCode
import com.festago.festago.model.timer.Timer
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryActivity.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryActivity.kt
similarity index 95%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryActivity.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryActivity.kt
index 46019fce1..0292967b1 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryActivity.kt
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryActivity.kt
@@ -5,7 +5,7 @@ import android.content.Intent
import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
-import com.festago.festago.databinding.ActivityTicketHistoryBinding
+import com.festago.festago.presentation.databinding.ActivityTicketHistoryBinding
import com.festago.festago.presentation.util.repeatOnStarted
import dagger.hilt.android.AndroidEntryPoint
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryAdapter.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryAdapter.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryAdapter.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryAdapter.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryItemUiState.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryItemUiState.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryItemUiState.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryItemUiState.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryUiState.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryUiState.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryUiState.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryUiState.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryViewHolder.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryViewHolder.kt
similarity index 90%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryViewHolder.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryViewHolder.kt
index 0e4c66658..fd192731e 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryViewHolder.kt
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryViewHolder.kt
@@ -3,7 +3,7 @@ package com.festago.festago.presentation.ui.tickethistory
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
-import com.festago.festago.databinding.ItemTicketHistoryBinding
+import com.festago.festago.presentation.databinding.ItemTicketHistoryBinding
class TicketHistoryViewHolder(
val binding: ItemTicketHistoryBinding,
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryViewModel.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryViewModel.kt
similarity index 93%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryViewModel.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryViewModel.kt
index f1cf12e61..99f46f002 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryViewModel.kt
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryViewModel.kt
@@ -2,8 +2,8 @@ package com.festago.festago.presentation.ui.tickethistory
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
-import com.festago.festago.analytics.AnalyticsHelper
-import com.festago.festago.analytics.logNetworkFailure
+import com.festago.festago.common.analytics.AnalyticsHelper
+import com.festago.festago.common.analytics.logNetworkFailure
import com.festago.festago.repository.TicketRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/ReservationFestivalUiState.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/ReservationFestivalUiState.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/ReservationFestivalUiState.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/ReservationFestivalUiState.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveActivity.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveActivity.kt
similarity index 87%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveActivity.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveActivity.kt
index 529e364dd..fc6fa2dbc 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveActivity.kt
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveActivity.kt
@@ -6,10 +6,11 @@ import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.ConcatAdapter
-import com.festago.festago.R
-import com.festago.festago.databinding.ActivityTicketReserveBinding
+import com.festago.festago.model.ErrorCode
import com.festago.festago.model.ReservationTicket
import com.festago.festago.model.ReservedTicket
+import com.festago.festago.presentation.R
+import com.festago.festago.presentation.databinding.ActivityTicketReserveBinding
import com.festago.festago.presentation.ui.customview.OkDialogFragment
import com.festago.festago.presentation.ui.reservationcomplete.ReservationCompleteActivity
import com.festago.festago.presentation.ui.reservationcomplete.ReservedTicketArg
@@ -75,7 +76,7 @@ class TicketReserveActivity : AppCompatActivity() {
)
is ReserveTicketSuccess -> handleReserveTicketSuccess(event.reservedTicket)
- is ReserveTicketFailed -> handleReserveTicketFailed()
+ is ReserveTicketFailed -> handleReserveTicketFailed(event.errorCode)
is ShowSignIn -> handleShowSignIn()
}
@@ -113,8 +114,14 @@ class TicketReserveActivity : AppCompatActivity() {
finish()
}
- private fun handleReserveTicketFailed() {
- OkDialogFragment.newInstance("예약에 실패하였습니다.")
+ private fun handleReserveTicketFailed(errorCode: ErrorCode) {
+ val message: String = when (errorCode) {
+ is ErrorCode.TICKET_SOLD_OUT -> getString(R.string.ticket_reserve_dialog_sold_out)
+ is ErrorCode.RESERVE_TICKET_OVER_AMOUNT -> getString(R.string.ticket_reserve_dialog_over_amount)
+ is ErrorCode.NEED_STUDENT_VERIFICATION -> getString(R.string.ticket_reserve_dialog_need_student_verification)
+ is ErrorCode.UNKNOWN -> getString(R.string.ticket_reserve_dialog_unknown)
+ }
+ OkDialogFragment.newInstance(message)
.show(supportFragmentManager, OkDialogFragment::class.java.name)
}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveEvent.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveEvent.kt
similarity index 80%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveEvent.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveEvent.kt
index 5cc0f3cc7..3ed05c64f 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveEvent.kt
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveEvent.kt
@@ -1,5 +1,6 @@
package com.festago.festago.presentation.ui.ticketreserve
+import com.festago.festago.model.ErrorCode
import com.festago.festago.model.ReservationTicket
import com.festago.festago.model.ReservedTicket
import java.time.LocalDateTime
@@ -11,6 +12,6 @@ sealed interface TicketReserveEvent {
) : TicketReserveEvent
class ReserveTicketSuccess(val reservedTicket: ReservedTicket) : TicketReserveEvent
- object ReserveTicketFailed : TicketReserveEvent
+ class ReserveTicketFailed(val errorCode: ErrorCode) : TicketReserveEvent
object ShowSignIn : TicketReserveEvent
}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveItemUiState.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveItemUiState.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveItemUiState.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveItemUiState.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveUiState.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveUiState.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveUiState.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveUiState.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveViewModel.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveViewModel.kt
similarity index 78%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveViewModel.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveViewModel.kt
index 05c056faf..a3819ad31 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveViewModel.kt
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveViewModel.kt
@@ -2,8 +2,9 @@ package com.festago.festago.presentation.ui.ticketreserve
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
-import com.festago.festago.analytics.AnalyticsHelper
-import com.festago.festago.analytics.logNetworkFailure
+import com.festago.festago.common.analytics.AnalyticsHelper
+import com.festago.festago.common.analytics.logNetworkFailure
+import com.festago.festago.model.ErrorCode
import com.festago.festago.model.ReservationStage
import com.festago.festago.repository.AuthRepository
import com.festago.festago.repository.FestivalRepository
@@ -49,12 +50,9 @@ class TicketReserveViewModel @Inject constructor(
),
stages = it.reservationStages.toTicketReserveItems(),
)
- }.onFailure {
+ }.onFailure { error ->
_uiState.value = TicketReserveUiState.Error
- analyticsHelper.logNetworkFailure(
- KEY_LOAD_RESERVATION_LOG,
- it.message.toString(),
- )
+ analyticsHelper.logNetworkFailure(KEY_LOAD_RESERVATION_LOG, error.message ?: "")
}
}
}
@@ -70,8 +68,12 @@ class TicketReserveViewModel @Inject constructor(
reservationTickets.sortedByTicketTypes(),
),
)
- }.onFailure {
+ }.onFailure { error ->
_uiState.value = TicketReserveUiState.Error
+ analyticsHelper.logNetworkFailure(
+ KEY_SHOW_TICKET_TYPES_LOG,
+ error.message ?: "",
+ )
}
} else {
_event.emit(TicketReserveEvent.ShowSignIn)
@@ -84,8 +86,13 @@ class TicketReserveViewModel @Inject constructor(
ticketRepository.reserveTicket(ticketId)
.onSuccess {
_event.emit(TicketReserveEvent.ReserveTicketSuccess(it))
- }.onFailure {
- _event.emit(TicketReserveEvent.ReserveTicketFailed)
+ }.onFailure { error ->
+ if (error is ErrorCode) {
+ _event.emit(TicketReserveEvent.ReserveTicketFailed(error))
+ } else {
+ _event.emit(TicketReserveEvent.ReserveTicketFailed(ErrorCode.UNKNOWN()))
+ analyticsHelper.logNetworkFailure(KEY_RESERVE_TICKET, error.message ?: "")
+ }
}
}
}
@@ -108,5 +115,7 @@ class TicketReserveViewModel @Inject constructor(
companion object {
private const val KEY_LOAD_RESERVATION_LOG = "load_reservation"
+ private const val KEY_SHOW_TICKET_TYPES_LOG = "show_ticket_types"
+ private const val KEY_RESERVE_TICKET = "reserve_ticket"
}
}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/adapter/TicketReserveAdapter.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/adapter/TicketReserveAdapter.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/adapter/TicketReserveAdapter.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/adapter/TicketReserveAdapter.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/adapter/TicketReserveHeaderAdapter.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/adapter/TicketReserveHeaderAdapter.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/adapter/TicketReserveHeaderAdapter.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/adapter/TicketReserveHeaderAdapter.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/BottomSheetReservationTicketArg.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/BottomSheetReservationTicketArg.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/BottomSheetReservationTicketArg.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/BottomSheetReservationTicketArg.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/BottomSheetTicketTypeArg.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/BottomSheetTicketTypeArg.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/BottomSheetTicketTypeArg.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/BottomSheetTicketTypeArg.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/TicketReserveBottomItem.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/TicketReserveBottomItem.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/TicketReserveBottomItem.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/TicketReserveBottomItem.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/TicketReserveBottomSheetAdapter.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/TicketReserveBottomSheetAdapter.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/TicketReserveBottomSheetAdapter.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/TicketReserveBottomSheetAdapter.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/TicketReserveBottomSheetCallback.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/TicketReserveBottomSheetCallback.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/TicketReserveBottomSheetCallback.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/TicketReserveBottomSheetCallback.kt
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/TicketReserveBottomSheetFragment.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/TicketReserveBottomSheetFragment.kt
similarity index 89%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/TicketReserveBottomSheetFragment.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/TicketReserveBottomSheetFragment.kt
index d665e2b94..a31f66ddc 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/TicketReserveBottomSheetFragment.kt
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/TicketReserveBottomSheetFragment.kt
@@ -5,7 +5,8 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.ViewModelProvider
-import com.festago.festago.databinding.FragmentTicketReserveBottomSheetBinding
+import androidx.recyclerview.widget.DividerItemDecoration
+import com.festago.festago.presentation.databinding.FragmentTicketReserveBottomSheetBinding
import com.festago.festago.presentation.ui.ticketreserve.TicketReserveViewModel
import com.festago.festago.presentation.util.getParcelableArrayListCompat
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
@@ -22,11 +23,11 @@ class TicketReserveBottomSheetFragment : BottomSheetDialogFragment() {
private val ticketTypeAdapter = TicketReserveBottomSheetAdapter { ticketId ->
binding.selectedTicketTypeId = ticketId
binding.btnReserveTicket.isEnabled = true
+ binding.tvTicketTypePrompt.visibility = View.INVISIBLE
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
-
val viewModelProvider = ViewModelProvider(requireActivity())
vm = viewModelProvider[TicketReserveViewModel::class.java]
}
@@ -37,6 +38,7 @@ class TicketReserveBottomSheetFragment : BottomSheetDialogFragment() {
savedInstanceState: Bundle?,
): View {
_binding = FragmentTicketReserveBottomSheetBinding.inflate(inflater)
+ dialog?.setCanceledOnTouchOutside(false)
binding.lifecycleOwner = viewLifecycleOwner
return binding.root
}
@@ -56,6 +58,7 @@ class TicketReserveBottomSheetFragment : BottomSheetDialogFragment() {
private fun initView() {
binding.rvTicketTypes.adapter = ticketTypeAdapter
+ binding.rvTicketTypes.addItemDecoration(DividerItemDecoration(requireContext(), 1))
val onReserve: (Int) -> Unit = { id -> vm.reserveTicket(id) }
binding.onReserve = onReserve
binding.btnReserveTicket.isEnabled = false
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/TicketReserveBottomViewHolder.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/TicketReserveBottomViewHolder.kt
similarity index 92%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/TicketReserveBottomViewHolder.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/TicketReserveBottomViewHolder.kt
index a4995ec59..3b2168253 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/TicketReserveBottomViewHolder.kt
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/bottomsheet/TicketReserveBottomViewHolder.kt
@@ -3,8 +3,8 @@ package com.festago.festago.presentation.ui.ticketreserve.bottomsheet
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
-import com.festago.festago.R
-import com.festago.festago.databinding.ItemTicketReserveBottomSheetBinding
+import com.festago.festago.presentation.R
+import com.festago.festago.presentation.databinding.ItemTicketReserveBottomSheetBinding
class TicketReserveBottomViewHolder(
private val binding: ItemTicketReserveBottomSheetBinding,
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/viewHolder/TicketReserveHeaderViewHolder.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/viewHolder/TicketReserveHeaderViewHolder.kt
similarity index 92%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/viewHolder/TicketReserveHeaderViewHolder.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/viewHolder/TicketReserveHeaderViewHolder.kt
index 6548ed102..1c45c70cd 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/viewHolder/TicketReserveHeaderViewHolder.kt
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/viewHolder/TicketReserveHeaderViewHolder.kt
@@ -5,8 +5,8 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
-import com.festago.festago.R
-import com.festago.festago.databinding.ItemTicketReserveHeaderBinding
+import com.festago.festago.presentation.R
+import com.festago.festago.presentation.databinding.ItemTicketReserveHeaderBinding
import com.festago.festago.presentation.ui.ticketreserve.ReservationFestivalUiState
import java.time.format.DateTimeFormatter
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/viewHolder/TicketReserveViewHolder.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/viewHolder/TicketReserveViewHolder.kt
similarity index 95%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/viewHolder/TicketReserveViewHolder.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/viewHolder/TicketReserveViewHolder.kt
index 09fe4df95..be38e83f9 100644
--- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketreserve/viewHolder/TicketReserveViewHolder.kt
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/ui/ticketreserve/viewHolder/TicketReserveViewHolder.kt
@@ -3,9 +3,9 @@ package com.festago.festago.presentation.ui.ticketreserve.viewHolder
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
-import com.festago.festago.R
-import com.festago.festago.databinding.ItemTicketReserveBinding
import com.festago.festago.model.TicketType
+import com.festago.festago.presentation.R
+import com.festago.festago.presentation.databinding.ItemTicketReserveBinding
import com.festago.festago.presentation.ui.ticketreserve.TicketReserveItemUiState
import java.time.format.DateTimeFormatter
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/util/LifecycleOwnerUtil.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/util/LifecycleOwnerUtil.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/util/LifecycleOwnerUtil.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/util/LifecycleOwnerUtil.kt
diff --git a/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/util/ParcelizeUtil.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/util/ParcelizeUtil.kt
new file mode 100644
index 000000000..abdef748d
--- /dev/null
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/util/ParcelizeUtil.kt
@@ -0,0 +1,34 @@
+package com.festago.festago.presentation.util
+
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import android.os.Parcelable
+import androidx.core.os.BundleCompat
+
+@Suppress("DEPRECATION")
+inline fun Bundle.getParcelableCompat(key: String): T? {
+ return if (Build.VERSION.SDK_INT >= 33) {
+ getParcelable(key, T::class.java)
+ } else {
+ getParcelable(key)
+ }
+}
+
+@Suppress("DEPRECATION")
+inline fun Bundle.getParcelableArrayListCompat(key: String): ArrayList? {
+ return if (Build.VERSION.SDK_INT >= 33) {
+ getParcelableArrayList(key, T::class.java)
+ } else {
+ getParcelableArrayList(key)
+ }
+}
+
+inline fun Intent.getParcelableExtraCompat(key: String): T? {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ val bundle = extras ?: return null
+ return BundleCompat.getParcelable(bundle, key, T::class.java)
+ }
+ @Suppress("DEPRECATION")
+ return getParcelableExtra(key) as? T
+}
diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/util/PermissionUtil.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/util/PermissionUtil.kt
similarity index 100%
rename from android/festago/app/src/main/java/com/festago/festago/presentation/util/PermissionUtil.kt
rename to android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/util/PermissionUtil.kt
diff --git a/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/util/SingleClickUtil.kt b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/util/SingleClickUtil.kt
new file mode 100644
index 000000000..ae4b96588
--- /dev/null
+++ b/android/festago/presentation-legacy/src/main/java/com/festago/festago/presentation/util/SingleClickUtil.kt
@@ -0,0 +1,30 @@
+package com.festago.festago.presentation.util
+
+import android.os.SystemClock
+import android.view.View
+import androidx.databinding.BindingAdapter
+
+class OnSingleClickListener(
+ private var interval: Int = 600,
+ private var onSingleClick: (View) -> Unit,
+) : View.OnClickListener {
+
+ private var lastClickTime: Long = 0
+
+ override fun onClick(v: View) {
+ val elapsedRealtime = SystemClock.elapsedRealtime()
+ if ((elapsedRealtime - lastClickTime) < interval) {
+ return
+ }
+ lastClickTime = elapsedRealtime
+ onSingleClick(v)
+ }
+}
+
+@BindingAdapter("onSingleClick")
+fun View.setOnSingleClickListener(onSingleClick: (View) -> Unit) {
+ val oneClick = OnSingleClickListener {
+ onSingleClick(it)
+ }
+ setOnClickListener(oneClick)
+}
diff --git a/android/festago/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/android/festago/presentation-legacy/src/main/res/drawable-v24/ic_launcher_foreground.xml
similarity index 100%
rename from android/festago/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
rename to android/festago/presentation-legacy/src/main/res/drawable-v24/ic_launcher_foreground.xml
diff --git a/android/festago/presentation-legacy/src/main/res/drawable/bg_chip_festival_list_selected.xml b/android/festago/presentation-legacy/src/main/res/drawable/bg_chip_festival_list_selected.xml
new file mode 100644
index 000000000..b92ade845
--- /dev/null
+++ b/android/festago/presentation-legacy/src/main/res/drawable/bg_chip_festival_list_selected.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/android/festago/app/src/main/res/drawable/bg_custom_dialog.xml b/android/festago/presentation-legacy/src/main/res/drawable/bg_custom_dialog.xml
similarity index 100%
rename from android/festago/app/src/main/res/drawable/bg_custom_dialog.xml
rename to android/festago/presentation-legacy/src/main/res/drawable/bg_custom_dialog.xml
diff --git a/android/festago/app/src/main/res/drawable/bg_mypage_kakao_login.xml b/android/festago/presentation-legacy/src/main/res/drawable/bg_mypage_kakao_login.xml
similarity index 100%
rename from android/festago/app/src/main/res/drawable/bg_mypage_kakao_login.xml
rename to android/festago/presentation-legacy/src/main/res/drawable/bg_mypage_kakao_login.xml
diff --git a/android/festago/app/src/main/res/drawable/bg_ticket_gradient_primary.xml b/android/festago/presentation-legacy/src/main/res/drawable/bg_ticket_gradient_primary.xml
similarity index 100%
rename from android/festago/app/src/main/res/drawable/bg_ticket_gradient_primary.xml
rename to android/festago/presentation-legacy/src/main/res/drawable/bg_ticket_gradient_primary.xml
diff --git a/android/festago/app/src/main/res/drawable/bg_ticket_gradient_secondary.xml b/android/festago/presentation-legacy/src/main/res/drawable/bg_ticket_gradient_secondary.xml
similarity index 100%
rename from android/festago/app/src/main/res/drawable/bg_ticket_gradient_secondary.xml
rename to android/festago/presentation-legacy/src/main/res/drawable/bg_ticket_gradient_secondary.xml
diff --git a/android/festago/app/src/main/res/drawable/bg_ticket_gradient_tertiary.xml b/android/festago/presentation-legacy/src/main/res/drawable/bg_ticket_gradient_tertiary.xml
similarity index 100%
rename from android/festago/app/src/main/res/drawable/bg_ticket_gradient_tertiary.xml
rename to android/festago/presentation-legacy/src/main/res/drawable/bg_ticket_gradient_tertiary.xml
diff --git a/android/festago/app/src/main/res/drawable/bg_ticket_reserve_bottom_sheet.xml b/android/festago/presentation-legacy/src/main/res/drawable/bg_ticket_reserve_bottom_sheet.xml
similarity index 100%
rename from android/festago/app/src/main/res/drawable/bg_ticket_reserve_bottom_sheet.xml
rename to android/festago/presentation-legacy/src/main/res/drawable/bg_ticket_reserve_bottom_sheet.xml
diff --git a/android/festago/app/src/main/res/drawable/bg_ticket_reserve_bottom_sheet_item.xml b/android/festago/presentation-legacy/src/main/res/drawable/bg_ticket_reserve_bottom_sheet_item.xml
similarity index 100%
rename from android/festago/app/src/main/res/drawable/bg_ticket_reserve_bottom_sheet_item.xml
rename to android/festago/presentation-legacy/src/main/res/drawable/bg_ticket_reserve_bottom_sheet_item.xml
diff --git a/android/festago/app/src/main/res/drawable/btn_circle_primary.xml b/android/festago/presentation-legacy/src/main/res/drawable/btn_circle_primary.xml
similarity index 100%
rename from android/festago/app/src/main/res/drawable/btn_circle_primary.xml
rename to android/festago/presentation-legacy/src/main/res/drawable/btn_circle_primary.xml
diff --git a/android/festago/app/src/main/res/drawable/btn_circle_secondary.xml b/android/festago/presentation-legacy/src/main/res/drawable/btn_circle_secondary.xml
similarity index 100%
rename from android/festago/app/src/main/res/drawable/btn_circle_secondary.xml
rename to android/festago/presentation-legacy/src/main/res/drawable/btn_circle_secondary.xml
diff --git a/android/festago/app/src/main/res/drawable/btn_circle_tertiary.xml b/android/festago/presentation-legacy/src/main/res/drawable/btn_circle_tertiary.xml
similarity index 100%
rename from android/festago/app/src/main/res/drawable/btn_circle_tertiary.xml
rename to android/festago/presentation-legacy/src/main/res/drawable/btn_circle_tertiary.xml
diff --git a/android/festago/app/src/main/res/drawable/btn_festa.xml b/android/festago/presentation-legacy/src/main/res/drawable/btn_festa.xml
similarity index 100%
rename from android/festago/app/src/main/res/drawable/btn_festa.xml
rename to android/festago/presentation-legacy/src/main/res/drawable/btn_festa.xml
diff --git a/android/festago/app/src/main/res/drawable/btn_festa_disabled.xml b/android/festago/presentation-legacy/src/main/res/drawable/btn_festa_disabled.xml
similarity index 100%
rename from android/festago/app/src/main/res/drawable/btn_festa_disabled.xml
rename to android/festago/presentation-legacy/src/main/res/drawable/btn_festa_disabled.xml
diff --git a/android/festago/app/src/main/res/drawable/btn_festa_normal.xml b/android/festago/presentation-legacy/src/main/res/drawable/btn_festa_normal.xml
similarity index 100%
rename from android/festago/app/src/main/res/drawable/btn_festa_normal.xml
rename to android/festago/presentation-legacy/src/main/res/drawable/btn_festa_normal.xml
diff --git a/android/festago/app/src/main/res/drawable/ic_baseline_error.xml b/android/festago/presentation-legacy/src/main/res/drawable/ic_baseline_error.xml
similarity index 100%
rename from android/festago/app/src/main/res/drawable/ic_baseline_error.xml
rename to android/festago/presentation-legacy/src/main/res/drawable/ic_baseline_error.xml
diff --git a/android/festago/app/src/main/res/drawable/ic_bottom_navigation_festival.xml b/android/festago/presentation-legacy/src/main/res/drawable/ic_bottom_navigation_festival.xml
similarity index 100%
rename from android/festago/app/src/main/res/drawable/ic_bottom_navigation_festival.xml
rename to android/festago/presentation-legacy/src/main/res/drawable/ic_bottom_navigation_festival.xml
diff --git a/android/festago/app/src/main/res/drawable/ic_bottom_navigation_user.xml b/android/festago/presentation-legacy/src/main/res/drawable/ic_bottom_navigation_user.xml
similarity index 100%
rename from android/festago/app/src/main/res/drawable/ic_bottom_navigation_user.xml
rename to android/festago/presentation-legacy/src/main/res/drawable/ic_bottom_navigation_user.xml
diff --git a/android/festago/app/src/main/res/drawable/ic_festago_coupon.xml b/android/festago/presentation-legacy/src/main/res/drawable/ic_festago_coupon.xml
similarity index 100%
rename from android/festago/app/src/main/res/drawable/ic_festago_coupon.xml
rename to android/festago/presentation-legacy/src/main/res/drawable/ic_festago_coupon.xml
diff --git a/android/festago/app/src/main/res/drawable/ic_festago_logo_background.xml b/android/festago/presentation-legacy/src/main/res/drawable/ic_festago_logo_background.xml
similarity index 100%
rename from android/festago/app/src/main/res/drawable/ic_festago_logo_background.xml
rename to android/festago/presentation-legacy/src/main/res/drawable/ic_festago_logo_background.xml
diff --git a/android/festago/app/src/main/res/drawable/ic_festago_logo_foreground.xml b/android/festago/presentation-legacy/src/main/res/drawable/ic_festago_logo_foreground.xml
similarity index 100%
rename from android/festago/app/src/main/res/drawable/ic_festago_logo_foreground.xml
rename to android/festago/presentation-legacy/src/main/res/drawable/ic_festago_logo_foreground.xml
diff --git a/android/festago/app/src/main/res/drawable/ic_image_placeholder.xml b/android/festago/presentation-legacy/src/main/res/drawable/ic_image_placeholder.xml
similarity index 100%
rename from android/festago/app/src/main/res/drawable/ic_image_placeholder.xml
rename to android/festago/presentation-legacy/src/main/res/drawable/ic_image_placeholder.xml
diff --git a/android/festago/app/src/main/res/drawable/ic_launcher_background.xml b/android/festago/presentation-legacy/src/main/res/drawable/ic_launcher_background.xml
similarity index 100%
rename from android/festago/app/src/main/res/drawable/ic_launcher_background.xml
rename to android/festago/presentation-legacy/src/main/res/drawable/ic_launcher_background.xml
diff --git a/android/festago/app/src/main/res/drawable/ic_mypage_kakao_logo.xml b/android/festago/presentation-legacy/src/main/res/drawable/ic_mypage_kakao_logo.xml
similarity index 100%
rename from android/festago/app/src/main/res/drawable/ic_mypage_kakao_logo.xml
rename to android/festago/presentation-legacy/src/main/res/drawable/ic_mypage_kakao_logo.xml
diff --git a/android/festago/app/src/main/res/drawable/ic_renew.xml b/android/festago/presentation-legacy/src/main/res/drawable/ic_renew.xml
similarity index 100%
rename from android/festago/app/src/main/res/drawable/ic_renew.xml
rename to android/festago/presentation-legacy/src/main/res/drawable/ic_renew.xml
diff --git a/android/festago/presentation-legacy/src/main/res/drawable/img_festago_home_logo.xml b/android/festago/presentation-legacy/src/main/res/drawable/img_festago_home_logo.xml
new file mode 100644
index 000000000..1694c060c
--- /dev/null
+++ b/android/festago/presentation-legacy/src/main/res/drawable/img_festago_home_logo.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/festago/app/src/main/res/drawable/img_festago_logo.xml b/android/festago/presentation-legacy/src/main/res/drawable/img_festago_logo.xml
similarity index 100%
rename from android/festago/app/src/main/res/drawable/img_festago_logo.xml
rename to android/festago/presentation-legacy/src/main/res/drawable/img_festago_logo.xml
diff --git a/android/festago/app/src/main/res/drawable/img_festago_qr.png b/android/festago/presentation-legacy/src/main/res/drawable/img_festago_qr.png
similarity index 100%
rename from android/festago/app/src/main/res/drawable/img_festago_qr.png
rename to android/festago/presentation-legacy/src/main/res/drawable/img_festago_qr.png
diff --git a/android/festago/app/src/main/res/drawable/img_mypage_logo.xml b/android/festago/presentation-legacy/src/main/res/drawable/img_mypage_logo.xml
similarity index 100%
rename from android/festago/app/src/main/res/drawable/img_mypage_logo.xml
rename to android/festago/presentation-legacy/src/main/res/drawable/img_mypage_logo.xml
diff --git a/android/festago/app/src/main/res/drawable/img_ticket.xml b/android/festago/presentation-legacy/src/main/res/drawable/img_ticket.xml
similarity index 100%
rename from android/festago/app/src/main/res/drawable/img_ticket.xml
rename to android/festago/presentation-legacy/src/main/res/drawable/img_ticket.xml
diff --git a/android/festago/app/src/main/res/drawable/menu_selector_color.xml b/android/festago/presentation-legacy/src/main/res/drawable/menu_selector_color.xml
similarity index 100%
rename from android/festago/app/src/main/res/drawable/menu_selector_color.xml
rename to android/festago/presentation-legacy/src/main/res/drawable/menu_selector_color.xml
diff --git a/android/festago/app/src/main/res/drawable/menu_selector_color_inverse.xml b/android/festago/presentation-legacy/src/main/res/drawable/menu_selector_color_inverse.xml
similarity index 100%
rename from android/festago/app/src/main/res/drawable/menu_selector_color_inverse.xml
rename to android/festago/presentation-legacy/src/main/res/drawable/menu_selector_color_inverse.xml
diff --git a/android/festago/app/src/main/res/drawable/pb_ticket_remain_time_primary.xml b/android/festago/presentation-legacy/src/main/res/drawable/pb_ticket_remain_time_primary.xml
similarity index 100%
rename from android/festago/app/src/main/res/drawable/pb_ticket_remain_time_primary.xml
rename to android/festago/presentation-legacy/src/main/res/drawable/pb_ticket_remain_time_primary.xml
diff --git a/android/festago/app/src/main/res/drawable/pb_ticket_remain_time_secondary.xml b/android/festago/presentation-legacy/src/main/res/drawable/pb_ticket_remain_time_secondary.xml
similarity index 100%
rename from android/festago/app/src/main/res/drawable/pb_ticket_remain_time_secondary.xml
rename to android/festago/presentation-legacy/src/main/res/drawable/pb_ticket_remain_time_secondary.xml
diff --git a/android/festago/app/src/main/res/drawable/pb_ticket_remain_time_tertiary.xml b/android/festago/presentation-legacy/src/main/res/drawable/pb_ticket_remain_time_tertiary.xml
similarity index 100%
rename from android/festago/app/src/main/res/drawable/pb_ticket_remain_time_tertiary.xml
rename to android/festago/presentation-legacy/src/main/res/drawable/pb_ticket_remain_time_tertiary.xml
diff --git a/android/festago/presentation-legacy/src/main/res/drawable/text_chip_festival_list_selected.xml b/android/festago/presentation-legacy/src/main/res/drawable/text_chip_festival_list_selected.xml
new file mode 100644
index 000000000..f7ec5e8b0
--- /dev/null
+++ b/android/festago/presentation-legacy/src/main/res/drawable/text_chip_festival_list_selected.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/android/festago/app/src/main/res/layout-land/activity_home.xml b/android/festago/presentation-legacy/src/main/res/layout-land/activity_home.xml
similarity index 100%
rename from android/festago/app/src/main/res/layout-land/activity_home.xml
rename to android/festago/presentation-legacy/src/main/res/layout-land/activity_home.xml
diff --git a/android/festago/app/src/main/res/layout/activity_home.xml b/android/festago/presentation-legacy/src/main/res/layout/activity_home.xml
similarity index 100%
rename from android/festago/app/src/main/res/layout/activity_home.xml
rename to android/festago/presentation-legacy/src/main/res/layout/activity_home.xml
diff --git a/android/festago/app/src/main/res/layout/activity_reservation_complete.xml b/android/festago/presentation-legacy/src/main/res/layout/activity_reservation_complete.xml
similarity index 100%
rename from android/festago/app/src/main/res/layout/activity_reservation_complete.xml
rename to android/festago/presentation-legacy/src/main/res/layout/activity_reservation_complete.xml
diff --git a/android/festago/app/src/main/res/layout/activity_select_school.xml b/android/festago/presentation-legacy/src/main/res/layout/activity_select_school.xml
similarity index 100%
rename from android/festago/app/src/main/res/layout/activity_select_school.xml
rename to android/festago/presentation-legacy/src/main/res/layout/activity_select_school.xml
diff --git a/android/festago/app/src/main/res/layout/activity_sign_in.xml b/android/festago/presentation-legacy/src/main/res/layout/activity_sign_in.xml
similarity index 100%
rename from android/festago/app/src/main/res/layout/activity_sign_in.xml
rename to android/festago/presentation-legacy/src/main/res/layout/activity_sign_in.xml
diff --git a/android/festago/app/src/main/res/layout/activity_splash.xml b/android/festago/presentation-legacy/src/main/res/layout/activity_splash.xml
similarity index 100%
rename from android/festago/app/src/main/res/layout/activity_splash.xml
rename to android/festago/presentation-legacy/src/main/res/layout/activity_splash.xml
diff --git a/android/festago/app/src/main/res/layout/activity_student_verification.xml b/android/festago/presentation-legacy/src/main/res/layout/activity_student_verification.xml
similarity index 100%
rename from android/festago/app/src/main/res/layout/activity_student_verification.xml
rename to android/festago/presentation-legacy/src/main/res/layout/activity_student_verification.xml
diff --git a/android/festago/app/src/main/res/layout/activity_ticket_entry.xml b/android/festago/presentation-legacy/src/main/res/layout/activity_ticket_entry.xml
similarity index 100%
rename from android/festago/app/src/main/res/layout/activity_ticket_entry.xml
rename to android/festago/presentation-legacy/src/main/res/layout/activity_ticket_entry.xml
diff --git a/android/festago/app/src/main/res/layout/activity_ticket_history.xml b/android/festago/presentation-legacy/src/main/res/layout/activity_ticket_history.xml
similarity index 100%
rename from android/festago/app/src/main/res/layout/activity_ticket_history.xml
rename to android/festago/presentation-legacy/src/main/res/layout/activity_ticket_history.xml
diff --git a/android/festago/app/src/main/res/layout/activity_ticket_reserve.xml b/android/festago/presentation-legacy/src/main/res/layout/activity_ticket_reserve.xml
similarity index 100%
rename from android/festago/app/src/main/res/layout/activity_ticket_reserve.xml
rename to android/festago/presentation-legacy/src/main/res/layout/activity_ticket_reserve.xml
diff --git a/android/festago/presentation-legacy/src/main/res/layout/fragment_festival_list.xml b/android/festago/presentation-legacy/src/main/res/layout/fragment_festival_list.xml
new file mode 100644
index 000000000..14da2ce1b
--- /dev/null
+++ b/android/festago/presentation-legacy/src/main/res/layout/fragment_festival_list.xml
@@ -0,0 +1,144 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/festago/app/src/main/res/layout/fragment_my_page.xml b/android/festago/presentation-legacy/src/main/res/layout/fragment_my_page.xml
similarity index 100%
rename from android/festago/app/src/main/res/layout/fragment_my_page.xml
rename to android/festago/presentation-legacy/src/main/res/layout/fragment_my_page.xml
diff --git a/android/festago/app/src/main/res/layout/fragment_ok_dialog.xml b/android/festago/presentation-legacy/src/main/res/layout/fragment_ok_dialog.xml
similarity index 100%
rename from android/festago/app/src/main/res/layout/fragment_ok_dialog.xml
rename to android/festago/presentation-legacy/src/main/res/layout/fragment_ok_dialog.xml
diff --git a/android/festago/app/src/main/res/layout/fragment_ticket_list.xml b/android/festago/presentation-legacy/src/main/res/layout/fragment_ticket_list.xml
similarity index 100%
rename from android/festago/app/src/main/res/layout/fragment_ticket_list.xml
rename to android/festago/presentation-legacy/src/main/res/layout/fragment_ticket_list.xml
diff --git a/android/festago/presentation-legacy/src/main/res/layout/fragment_ticket_reserve_bottom_sheet.xml b/android/festago/presentation-legacy/src/main/res/layout/fragment_ticket_reserve_bottom_sheet.xml
new file mode 100644
index 000000000..384feb9f2
--- /dev/null
+++ b/android/festago/presentation-legacy/src/main/res/layout/fragment_ticket_reserve_bottom_sheet.xml
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/festago/app/src/main/res/layout/item_festival_list.xml b/android/festago/presentation-legacy/src/main/res/layout/item_festival_list.xml
similarity index 100%
rename from android/festago/app/src/main/res/layout/item_festival_list.xml
rename to android/festago/presentation-legacy/src/main/res/layout/item_festival_list.xml
diff --git a/android/festago/app/src/main/res/layout/item_select_school.xml b/android/festago/presentation-legacy/src/main/res/layout/item_select_school.xml
similarity index 100%
rename from android/festago/app/src/main/res/layout/item_select_school.xml
rename to android/festago/presentation-legacy/src/main/res/layout/item_select_school.xml
diff --git a/android/festago/app/src/main/res/layout/item_ticket_history.xml b/android/festago/presentation-legacy/src/main/res/layout/item_ticket_history.xml
similarity index 100%
rename from android/festago/app/src/main/res/layout/item_ticket_history.xml
rename to android/festago/presentation-legacy/src/main/res/layout/item_ticket_history.xml
diff --git a/android/festago/app/src/main/res/layout/item_ticket_list.xml b/android/festago/presentation-legacy/src/main/res/layout/item_ticket_list.xml
similarity index 100%
rename from android/festago/app/src/main/res/layout/item_ticket_list.xml
rename to android/festago/presentation-legacy/src/main/res/layout/item_ticket_list.xml
diff --git a/android/festago/app/src/main/res/layout/item_ticket_reserve.xml b/android/festago/presentation-legacy/src/main/res/layout/item_ticket_reserve.xml
similarity index 97%
rename from android/festago/app/src/main/res/layout/item_ticket_reserve.xml
rename to android/festago/presentation-legacy/src/main/res/layout/item_ticket_reserve.xml
index c1df86cff..f1835d3db 100644
--- a/android/festago/app/src/main/res/layout/item_ticket_reserve.xml
+++ b/android/festago/presentation-legacy/src/main/res/layout/item_ticket_reserve.xml
@@ -111,11 +111,11 @@
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="28dp"
- android:onClick="@{() -> stage.onShowStageTickets.invoke(stage.id, stage.startTime)}"
android:text="@string/ticket_reserve_btn_reserve_ticket"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/tvAuthGuideStudent" />
+ app:layout_constraintTop_toBottomOf="@id/tvAuthGuideStudent"
+ app:onSingleClick="@{() -> stage.onShowStageTickets.invoke(stage.id, stage.startTime)}" />
diff --git a/android/festago/app/src/main/res/layout/item_ticket_reserve_bottom_sheet.xml b/android/festago/presentation-legacy/src/main/res/layout/item_ticket_reserve_bottom_sheet.xml
similarity index 100%
rename from android/festago/app/src/main/res/layout/item_ticket_reserve_bottom_sheet.xml
rename to android/festago/presentation-legacy/src/main/res/layout/item_ticket_reserve_bottom_sheet.xml
diff --git a/android/festago/app/src/main/res/layout/item_ticket_reserve_header.xml b/android/festago/presentation-legacy/src/main/res/layout/item_ticket_reserve_header.xml
similarity index 94%
rename from android/festago/app/src/main/res/layout/item_ticket_reserve_header.xml
rename to android/festago/presentation-legacy/src/main/res/layout/item_ticket_reserve_header.xml
index 221d4abaa..eb30c1b19 100644
--- a/android/festago/app/src/main/res/layout/item_ticket_reserve_header.xml
+++ b/android/festago/presentation-legacy/src/main/res/layout/item_ticket_reserve_header.xml
@@ -33,12 +33,13 @@
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvUniversityName"
+ android:layout_marginBottom="20dp"
tools:text="2023.07.03 - 2023.07.09" />
diff --git a/android/festago/app/src/main/res/menu/menu_bottom_navigation.xml b/android/festago/presentation-legacy/src/main/res/menu/menu_bottom_navigation.xml
similarity index 100%
rename from android/festago/app/src/main/res/menu/menu_bottom_navigation.xml
rename to android/festago/presentation-legacy/src/main/res/menu/menu_bottom_navigation.xml
diff --git a/android/festago/app/src/main/res/mipmap-anydpi-v26/ic_festago_logo.xml b/android/festago/presentation-legacy/src/main/res/mipmap-anydpi-v26/ic_festago_logo.xml
similarity index 100%
rename from android/festago/app/src/main/res/mipmap-anydpi-v26/ic_festago_logo.xml
rename to android/festago/presentation-legacy/src/main/res/mipmap-anydpi-v26/ic_festago_logo.xml
diff --git a/android/festago/app/src/main/res/mipmap-anydpi-v26/ic_festago_logo_round.xml b/android/festago/presentation-legacy/src/main/res/mipmap-anydpi-v26/ic_festago_logo_round.xml
similarity index 100%
rename from android/festago/app/src/main/res/mipmap-anydpi-v26/ic_festago_logo_round.xml
rename to android/festago/presentation-legacy/src/main/res/mipmap-anydpi-v26/ic_festago_logo_round.xml
diff --git a/android/festago/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/festago/presentation-legacy/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
similarity index 100%
rename from android/festago/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
rename to android/festago/presentation-legacy/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
diff --git a/android/festago/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/festago/presentation-legacy/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
similarity index 100%
rename from android/festago/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
rename to android/festago/presentation-legacy/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
diff --git a/android/festago/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/android/festago/presentation-legacy/src/main/res/mipmap-hdpi/ic_launcher.webp
similarity index 100%
rename from android/festago/app/src/main/res/mipmap-hdpi/ic_launcher.webp
rename to android/festago/presentation-legacy/src/main/res/mipmap-hdpi/ic_launcher.webp
diff --git a/android/festago/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/android/festago/presentation-legacy/src/main/res/mipmap-hdpi/ic_launcher_round.webp
similarity index 100%
rename from android/festago/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
rename to android/festago/presentation-legacy/src/main/res/mipmap-hdpi/ic_launcher_round.webp
diff --git a/android/festago/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/android/festago/presentation-legacy/src/main/res/mipmap-mdpi/ic_launcher.webp
similarity index 100%
rename from android/festago/app/src/main/res/mipmap-mdpi/ic_launcher.webp
rename to android/festago/presentation-legacy/src/main/res/mipmap-mdpi/ic_launcher.webp
diff --git a/android/festago/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/android/festago/presentation-legacy/src/main/res/mipmap-mdpi/ic_launcher_round.webp
similarity index 100%
rename from android/festago/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
rename to android/festago/presentation-legacy/src/main/res/mipmap-mdpi/ic_launcher_round.webp
diff --git a/android/festago/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/android/festago/presentation-legacy/src/main/res/mipmap-xhdpi/ic_launcher.webp
similarity index 100%
rename from android/festago/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
rename to android/festago/presentation-legacy/src/main/res/mipmap-xhdpi/ic_launcher.webp
diff --git a/android/festago/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/android/festago/presentation-legacy/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
similarity index 100%
rename from android/festago/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
rename to android/festago/presentation-legacy/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
diff --git a/android/festago/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/android/festago/presentation-legacy/src/main/res/mipmap-xxhdpi/ic_launcher.webp
similarity index 100%
rename from android/festago/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
rename to android/festago/presentation-legacy/src/main/res/mipmap-xxhdpi/ic_launcher.webp
diff --git a/android/festago/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/android/festago/presentation-legacy/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
similarity index 100%
rename from android/festago/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
rename to android/festago/presentation-legacy/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
diff --git a/android/festago/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/android/festago/presentation-legacy/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
similarity index 100%
rename from android/festago/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
rename to android/festago/presentation-legacy/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
diff --git a/android/festago/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/android/festago/presentation-legacy/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
similarity index 100%
rename from android/festago/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
rename to android/festago/presentation-legacy/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
diff --git a/android/festago/app/src/main/res/values-night/colors.xml b/android/festago/presentation-legacy/src/main/res/values-night/colors.xml
similarity index 100%
rename from android/festago/app/src/main/res/values-night/colors.xml
rename to android/festago/presentation-legacy/src/main/res/values-night/colors.xml
diff --git a/android/festago/app/src/main/res/values-night/themes.xml b/android/festago/presentation-legacy/src/main/res/values-night/themes.xml
similarity index 100%
rename from android/festago/app/src/main/res/values-night/themes.xml
rename to android/festago/presentation-legacy/src/main/res/values-night/themes.xml
diff --git a/android/festago/app/src/main/res/values/colors.xml b/android/festago/presentation-legacy/src/main/res/values/colors.xml
similarity index 100%
rename from android/festago/app/src/main/res/values/colors.xml
rename to android/festago/presentation-legacy/src/main/res/values/colors.xml
diff --git a/android/festago/app/src/main/res/values/dimens.xml b/android/festago/presentation-legacy/src/main/res/values/dimens.xml
similarity index 100%
rename from android/festago/app/src/main/res/values/dimens.xml
rename to android/festago/presentation-legacy/src/main/res/values/dimens.xml
diff --git a/android/festago/presentation-legacy/src/main/res/values/strings.xml b/android/festago/presentation-legacy/src/main/res/values/strings.xml
new file mode 100644
index 000000000..8cf6589d1
--- /dev/null
+++ b/android/festago/presentation-legacy/src/main/res/values/strings.xml
@@ -0,0 +1,150 @@
+
+
+ 페스타고
+
+
+ yyyy.MM.dd
+
+
+ 입장전
+ 입장완료
+ 외출중
+
+
+ 재학생용
+ 방문객용
+ 기타
+
+
+ 업데이트 알림
+ 새로운 페스타고를 사용하기 위해 업데이트 해주세요.
+ 업데이트 후 정상 사용가능합니다.
+ 페스타고 실행 중 문제가 발생했습니다. 페스타고로 문의해주세요.
+
+
+ HH:mm 티켓 활성화
+ 티켓 제시
+ 입장 전
+ 입장 완료
+ 외출중
+
+
+ 입장 전
+ 입장 완료
+ 외출중
+ 티켓 조회 과정에 문제가 발생했습니다.
+
+
+ 축제 목록
+ 티켓 목록
+ 마이페이지
+ 알림 권한을 거부했습니다 :(
+ 한 번 더 누르면 앱이 종료됩니다
+
+
+ %1s ~ %1s
+ yyyy.MM.dd
+ MM.dd (E) HH:mm
+ [라인업]
+ [예매 가능 티켓]
+ %1$s(%2$s/%3$s)
+ ", "
+ ⚠️ 재학생용 티켓예매를 위해 사전에 학교 인증이 필요합니다.
+ 축제 조회에 실패했습니다.
+ 티켓 예매
+ (%1$s/%2$s)
+ 예매 하기
+ MM월 dd일 HH:mm 오픈예정
+ 로그인 후 예매
+ 예매 유형을 선택해주세요
+ 공연이 매진되었습니다
+ 이미 예매한 공연입니다
+ 학생 인증이 필요합니다
+ 알 수 없는 오류가 발생했습니다
+
+
+ yyyy.MM.dd
+ 진행중
+ 진행 예정
+ 지난 축제
+ 축제 조회에 실패했습니다.
+ 조회된 축제가 없습니다.
+ %s - %s
+
+
+ 티켓 목록
+ yyyy.MM.dd HH:mm
+ HH:mm 티켓 활성화
+ 입장하기
+ %d번
+ 무대 시작 시간 %s
+ 입장 시작 시간 %s
+ 사용할 수 있는 티켓이 없습니다
+ 티켓 목록 조회에 실패했습니다.
+
+
+ 확인
+ 취소
+
+
+ 카카오로 로그인
+ 페스타고로\n대학교 축제를 즐겨보세요.
+
+
+ 예매에 성공했습니다!
+ 나의 티켓 번호
+ HH:mm
+ yyyy.MM.dd
+ [입장 가능 시간] %s
+
+
+ 계정
+ 학교 인증
+ 로그아웃
+ 탈퇴하기
+ 예매 목록
+ [무대 시작 시간]
+ [입장 시작 시간]
+ [입장 번호]
+ [예매 일자]
+ 더보기 >
+ 예매 내역이 존재하지 않습니다.
+ yyyy.MM.dd. HH:mm
+ 마이페이지 정보 받아오기에 실패했습니다.
+
+
+ 예매 목록
+ [무대 시작 시간]
+ [입장 시작 시간]
+ [입장 번호]
+ %d번
+ [예매 일자]
+ 티켓 조회에 실패했습니다.
+ 조회된 티켓이 없습니다.
+
+
+ 정말 탈퇴하시겠어요?
+ 탈퇴 버튼 선택 시, 계정은 삭제되며 복구되지 않습니다.
+ 탈퇴
+ 취소
+
+
+
+ 학교 이메일
+ 인증 코드
+ 인증 번호 받기
+ 인증 번호 확인
+ \@%s
+ mm:ss
+ 학교 정보 받아오기에 실패했습니다.
+
+
+ 다음
+ 학교 선택
+ 학교 목록 불러오기에 실패했습니다.
+ 학교 선택
+
+
+ 공연 입장 알림
+
+
diff --git a/android/festago/app/src/main/res/values/style.xml b/android/festago/presentation-legacy/src/main/res/values/style.xml
similarity index 76%
rename from android/festago/app/src/main/res/values/style.xml
rename to android/festago/presentation-legacy/src/main/res/values/style.xml
index 004ea7295..d071718c5 100644
--- a/android/festago/app/src/main/res/values/style.xml
+++ b/android/festago/presentation-legacy/src/main/res/values/style.xml
@@ -22,4 +22,10 @@
- 50%
+
+
diff --git a/android/festago/app/src/main/res/values/themes.xml b/android/festago/presentation-legacy/src/main/res/values/themes.xml
similarity index 100%
rename from android/festago/app/src/main/res/values/themes.xml
rename to android/festago/presentation-legacy/src/main/res/values/themes.xml
diff --git a/android/festago/app/src/main/res/xml/activity_ticket_history_scene.xml b/android/festago/presentation-legacy/src/main/res/xml/activity_ticket_history_scene.xml
similarity index 100%
rename from android/festago/app/src/main/res/xml/activity_ticket_history_scene.xml
rename to android/festago/presentation-legacy/src/main/res/xml/activity_ticket_history_scene.xml
diff --git a/android/festago/app/src/main/res/xml/backup_rules.xml b/android/festago/presentation-legacy/src/main/res/xml/backup_rules.xml
similarity index 100%
rename from android/festago/app/src/main/res/xml/backup_rules.xml
rename to android/festago/presentation-legacy/src/main/res/xml/backup_rules.xml
diff --git a/android/festago/app/src/main/res/xml/data_extraction_rules.xml b/android/festago/presentation-legacy/src/main/res/xml/data_extraction_rules.xml
similarity index 100%
rename from android/festago/app/src/main/res/xml/data_extraction_rules.xml
rename to android/festago/presentation-legacy/src/main/res/xml/data_extraction_rules.xml
diff --git a/android/festago/app/src/test/java/com/festago/festago/presentation/fixture/TicketFixture.kt b/android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/fixture/TicketFixture.kt
similarity index 100%
rename from android/festago/app/src/test/java/com/festago/festago/presentation/fixture/TicketFixture.kt
rename to android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/fixture/TicketFixture.kt
diff --git a/android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/rule/MainDispatcherRule.kt b/android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/rule/MainDispatcherRule.kt
new file mode 100644
index 000000000..3059da632
--- /dev/null
+++ b/android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/rule/MainDispatcherRule.kt
@@ -0,0 +1,28 @@
+package com.festago.festago.presentation.rule
+
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.setMain
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+
+// Reusable JUnit4 TestRule to override the Main dispatcher
+class MainDispatcherRule
+@OptIn(ExperimentalCoroutinesApi::class)
+constructor(
+ private val testDispatcher: TestDispatcher = UnconfinedTestDispatcher(),
+) : TestWatcher() {
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ override fun starting(description: Description) {
+ Dispatchers.setMain(testDispatcher)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ override fun finished(description: Description) {
+ Dispatchers.resetMain()
+ }
+}
diff --git a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/home/HomeViewModelTest.kt b/android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/ui/home/HomeViewModelTest.kt
similarity index 89%
rename from android/festago/app/src/test/java/com/festago/festago/presentation/ui/home/HomeViewModelTest.kt
rename to android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/ui/home/HomeViewModelTest.kt
index 6a7c5616c..b867fe986 100644
--- a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/home/HomeViewModelTest.kt
+++ b/android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/ui/home/HomeViewModelTest.kt
@@ -1,38 +1,30 @@
package com.festago.festago.presentation.ui.home
import app.cash.turbine.test
+import com.festago.festago.presentation.rule.MainDispatcherRule
import com.festago.festago.repository.AuthRepository
import io.mockk.every
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.runTest
-import kotlinx.coroutines.test.setMain
import org.assertj.core.api.Assertions.assertThat
-import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
class HomeViewModelTest {
+
private lateinit var vm: HomeViewModel
private lateinit var authRepository: AuthRepository
- @OptIn(ExperimentalCoroutinesApi::class)
+ @get:Rule
+ val mainDispatcherRule = MainDispatcherRule()
+
@Before
fun setUp() {
- Dispatchers.setMain(UnconfinedTestDispatcher())
authRepository = mockk()
vm = HomeViewModel(authRepository)
}
- @OptIn(ExperimentalCoroutinesApi::class)
- @After
- fun finish() {
- Dispatchers.resetMain()
- }
-
private fun `사용자 인증 유무가 다음과 같을 때`(isSigned: Boolean) {
every { authRepository.isSigned } returns isSigned
}
diff --git a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListViewModelTest.kt b/android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListViewModelTest.kt
similarity index 81%
rename from android/festago/app/src/test/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListViewModelTest.kt
rename to android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListViewModelTest.kt
index 56b5b060b..83681a632 100644
--- a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListViewModelTest.kt
+++ b/android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/ui/home/festivallist/FestivalListViewModelTest.kt
@@ -1,22 +1,19 @@
package com.festago.festago.presentation.ui.home.festivallist
import app.cash.turbine.test
-import com.festago.festago.analytics.AnalyticsHelper
+import com.festago.festago.common.analytics.AnalyticsHelper
import com.festago.festago.model.Festival
+import com.festago.festago.model.FestivalFilter
+import com.festago.festago.presentation.rule.MainDispatcherRule
import com.festago.festago.repository.FestivalRepository
import io.mockk.coEvery
import io.mockk.mockk
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
-import kotlinx.coroutines.test.setMain
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.SoftAssertions
-import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import java.time.LocalDate
@@ -36,36 +33,31 @@ class FestivalListViewModelTest {
)
}
- @OptIn(ExperimentalCoroutinesApi::class)
+ @get:Rule
+ val mainDispatcherRule = MainDispatcherRule()
+
@Before
fun setUp() {
- Dispatchers.setMain(UnconfinedTestDispatcher())
festivalRepository = mockk()
analyticsHelper = mockk(relaxed = true)
vm = FestivalListViewModel(festivalRepository, analyticsHelper)
}
- @OptIn(ExperimentalCoroutinesApi::class)
- @After
- fun finish() {
- Dispatchers.resetMain()
- }
-
private fun `축제 목록 요청 결과가 다음과 같을 때`(result: Result>) {
coEvery {
- festivalRepository.loadFestivals()
+ festivalRepository.loadFestivals(any())
} answers {
result
}
}
@Test
- fun `축제 목록 받아오기에 성공하면 성공 상태이고 축제 목록을 반환한다`() {
+ fun `진행 예정인 축제 목록 받아오기에 성공하면 성공 상태이고 축제 목록을 반환한다`() {
// given
`축제 목록 요청 결과가 다음과 같을 때`(Result.success(fakeFestivals))
// when
- vm.loadFestivals()
+ vm.loadFestivals(FestivalFilter.PLANNED)
// then
val softly = SoftAssertions().apply {
@@ -85,12 +77,12 @@ class FestivalListViewModelTest {
}
@Test
- fun `축제 목록 받아오기에 실패하면 에러 상태다`() {
+ fun `진행 예정 축제 목록 받아오기에 실패하면 에러 상태이다`() {
// given
`축제 목록 요청 결과가 다음과 같을 때`(Result.failure(Exception()))
// when
- vm.loadFestivals()
+ vm.loadFestivals(FestivalFilter.PLANNED)
// then
val softly = SoftAssertions().apply {
@@ -108,14 +100,14 @@ class FestivalListViewModelTest {
fun `축제 목록을 받아오는 중이면 로딩 상태다`() {
// given
coEvery {
- festivalRepository.loadFestivals()
+ festivalRepository.loadFestivals(any())
} coAnswers {
delay(1000)
Result.success(emptyList())
}
// when
- vm.loadFestivals()
+ vm.loadFestivals(FestivalFilter.PLANNED)
// then
val softly = SoftAssertions().apply {
@@ -131,7 +123,6 @@ class FestivalListViewModelTest {
@Test
fun `티켓 예매를 열면 티켓 예매 열기 이벤트가 발생한다`() = runTest {
-
vm.event.test {
// when
val fakeFestivalId = 1L
diff --git a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/home/mypage/MyPageViewModelTest.kt b/android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/ui/home/mypage/MyPageViewModelTest.kt
similarity index 94%
rename from android/festago/app/src/test/java/com/festago/festago/presentation/ui/home/mypage/MyPageViewModelTest.kt
rename to android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/ui/home/mypage/MyPageViewModelTest.kt
index 2eb87bdae..4cad701b8 100644
--- a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/home/mypage/MyPageViewModelTest.kt
+++ b/android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/ui/home/mypage/MyPageViewModelTest.kt
@@ -1,32 +1,29 @@
package com.festago.festago.presentation.ui.home.mypage
import app.cash.turbine.test
-import com.festago.festago.analytics.AnalyticsHelper
+import com.festago.festago.common.analytics.AnalyticsHelper
import com.festago.festago.model.MemberTicketFestival
import com.festago.festago.model.Stage
import com.festago.festago.model.Ticket
import com.festago.festago.model.TicketCondition
import com.festago.festago.model.UserProfile
+import com.festago.festago.presentation.rule.MainDispatcherRule
import com.festago.festago.repository.AuthRepository
import com.festago.festago.repository.TicketRepository
import com.festago.festago.repository.UserRepository
import io.mockk.coEvery
import io.mockk.mockk
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
-import kotlinx.coroutines.test.setMain
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.SoftAssertions
-import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import java.time.LocalDateTime
class MyPageViewModelTest {
+
private lateinit var vm: MyPageViewModel
private lateinit var userRepository: UserRepository
private lateinit var ticketRepository: TicketRepository
@@ -55,10 +52,11 @@ class MyPageViewModelTest {
),
)
- @OptIn(ExperimentalCoroutinesApi::class)
+ @get:Rule
+ val mainDispatcherRule = MainDispatcherRule()
+
@Before
fun setUp() {
- Dispatchers.setMain(UnconfinedTestDispatcher())
userRepository = mockk(relaxed = true)
ticketRepository = mockk()
authRepository = mockk()
@@ -66,12 +64,6 @@ class MyPageViewModelTest {
vm = MyPageViewModel(userRepository, ticketRepository, authRepository, analyticsHelper)
}
- @OptIn(ExperimentalCoroutinesApi::class)
- @After
- fun finish() {
- Dispatchers.resetMain()
- }
-
private fun `로그인 상태가 다음과 같다`(result: Boolean) {
coEvery {
authRepository.isSigned
diff --git a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListViewModelTest.kt b/android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListViewModelTest.kt
similarity index 91%
rename from android/festago/app/src/test/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListViewModelTest.kt
rename to android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListViewModelTest.kt
index 050fe50fd..2b309ba8e 100644
--- a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListViewModelTest.kt
+++ b/android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/ui/home/ticketlist/TicketListViewModelTest.kt
@@ -1,44 +1,36 @@
package com.festago.festago.presentation.ui.home.ticketlist
import app.cash.turbine.test
-import com.festago.festago.analytics.AnalyticsHelper
+import com.festago.festago.common.analytics.AnalyticsHelper
import com.festago.festago.model.Ticket
import com.festago.festago.presentation.fixture.TicketFixture
+import com.festago.festago.presentation.rule.MainDispatcherRule
import com.festago.festago.repository.TicketRepository
import io.mockk.coEvery
import io.mockk.mockk
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
-import kotlinx.coroutines.test.setMain
import org.assertj.core.api.SoftAssertions
-import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
class TicketListViewModelTest {
+
private lateinit var vm: TicketListViewModel
private lateinit var ticketRepository: TicketRepository
private lateinit var analyticsHelper: AnalyticsHelper
- @OptIn(ExperimentalCoroutinesApi::class)
+ @get:Rule
+ val mainDispatcherRule = MainDispatcherRule()
+
@Before
fun setUp() {
- Dispatchers.setMain(UnconfinedTestDispatcher())
ticketRepository = mockk()
analyticsHelper = mockk(relaxed = true)
vm = TicketListViewModel(ticketRepository, analyticsHelper)
}
- @OptIn(ExperimentalCoroutinesApi::class)
- @After
- fun finish() {
- Dispatchers.resetMain()
- }
-
private fun `현재 티켓 요청 결과가 다음과 같을 때`(result: Result>) {
coEvery { ticketRepository.loadCurrentTickets() } returns result
}
diff --git a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/selectschool/SelectSchoolViewModelTest.kt b/android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/ui/selectschool/SelectSchoolViewModelTest.kt
similarity index 88%
rename from android/festago/app/src/test/java/com/festago/festago/presentation/ui/selectschool/SelectSchoolViewModelTest.kt
rename to android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/ui/selectschool/SelectSchoolViewModelTest.kt
index d6708dc56..86ecc2796 100644
--- a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/selectschool/SelectSchoolViewModelTest.kt
+++ b/android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/ui/selectschool/SelectSchoolViewModelTest.kt
@@ -1,34 +1,30 @@
package com.festago.festago.presentation.ui.selectschool
import app.cash.turbine.test
-import com.festago.festago.analytics.AnalyticsHelper
+import com.festago.festago.common.analytics.AnalyticsHelper
import com.festago.festago.model.School
+import com.festago.festago.presentation.rule.MainDispatcherRule
import com.festago.festago.repository.SchoolRepository
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.runTest
-import kotlinx.coroutines.test.setMain
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.SoftAssertions.assertSoftly
-import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
-@OptIn(ExperimentalCoroutinesApi::class)
class SelectSchoolViewModelTest {
- private val testDispatcher = UnconfinedTestDispatcher()
private lateinit var vm: SelectSchoolViewModel
private lateinit var schoolRepository: SchoolRepository
private lateinit var analyticsHelper: AnalyticsHelper
+ @get:Rule
+ val mainDispatcherRule = MainDispatcherRule()
+
@Before
fun setup() {
- Dispatchers.setMain(testDispatcher)
schoolRepository = mockk()
analyticsHelper = mockk(relaxed = true)
vm = SelectSchoolViewModel(
@@ -37,11 +33,6 @@ class SelectSchoolViewModelTest {
)
}
- @After
- fun finish() {
- Dispatchers.resetMain()
- }
-
private fun `학교 목록 불러오기 요청 결과가 다음과 같을 때 `(result: Result>) {
coEvery {
schoolRepository.loadSchools()
diff --git a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/signin/SignInViewModelTest.kt b/android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/ui/signin/SignInViewModelTest.kt
similarity index 75%
rename from android/festago/app/src/test/java/com/festago/festago/presentation/ui/signin/SignInViewModelTest.kt
rename to android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/ui/signin/SignInViewModelTest.kt
index c8f209389..aab821c90 100644
--- a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/signin/SignInViewModelTest.kt
+++ b/android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/ui/signin/SignInViewModelTest.kt
@@ -1,42 +1,33 @@
package com.festago.festago.presentation.ui.signin
import app.cash.turbine.test
-import com.festago.festago.analytics.AnalyticsHelper
+import com.festago.festago.common.analytics.AnalyticsHelper
+import com.festago.festago.presentation.rule.MainDispatcherRule
import com.festago.festago.repository.AuthRepository
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.runTest
-import kotlinx.coroutines.test.setMain
import org.assertj.core.api.Assertions.assertThat
-import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
class SignInViewModelTest {
+
private lateinit var vm: SignInViewModel
private lateinit var authRepository: AuthRepository
private lateinit var analyticsHelper: AnalyticsHelper
- @OptIn(ExperimentalCoroutinesApi::class)
+ @get:Rule
+ val mainDispatcherRule = MainDispatcherRule()
+
@Before
fun setUp() {
- Dispatchers.setMain(UnconfinedTestDispatcher())
-
authRepository = mockk(relaxed = true)
analyticsHelper = mockk(relaxed = true)
vm = SignInViewModel(authRepository, analyticsHelper)
}
- @OptIn(ExperimentalCoroutinesApi::class)
- @After
- fun finish() {
- Dispatchers.resetMain()
- }
-
private fun `로그인 결과가 다음과 같을 때`(result: Result) {
coEvery { authRepository.signIn() } returns result
}
diff --git a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationViewModelTest.kt b/android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationViewModelTest.kt
similarity index 94%
rename from android/festago/app/src/test/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationViewModelTest.kt
rename to android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationViewModelTest.kt
index 672066619..9fd56ab87 100644
--- a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationViewModelTest.kt
+++ b/android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/ui/studentverification/StudentVerificationViewModelTest.kt
@@ -1,36 +1,32 @@
package com.festago.festago.presentation.ui.studentverification
import app.cash.turbine.test
-import com.festago.festago.analytics.AnalyticsHelper
+import com.festago.festago.common.analytics.AnalyticsHelper
import com.festago.festago.model.StudentVerificationCode
+import com.festago.festago.presentation.rule.MainDispatcherRule
import com.festago.festago.repository.SchoolRepository
import com.festago.festago.repository.StudentVerificationRepository
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.runTest
-import kotlinx.coroutines.test.setMain
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.SoftAssertions.assertSoftly
-import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
-@OptIn(ExperimentalCoroutinesApi::class)
class StudentVerificationViewModelTest {
- private val testDispatcher = UnconfinedTestDispatcher()
private lateinit var vm: StudentVerificationViewModel
private lateinit var studentVerificationRepository: StudentVerificationRepository
private lateinit var schoolRepository: SchoolRepository
private lateinit var analyticsHelper: AnalyticsHelper
+ @get:Rule
+ val mainDispatcherRule = MainDispatcherRule()
+
@Before
fun setUp() {
- Dispatchers.setMain(testDispatcher)
studentVerificationRepository = mockk()
schoolRepository = mockk()
analyticsHelper = mockk(relaxed = true)
@@ -41,12 +37,6 @@ class StudentVerificationViewModelTest {
)
}
- @OptIn(ExperimentalCoroutinesApi::class)
- @After
- fun finish() {
- Dispatchers.resetMain()
- }
-
private fun `이메일 요청 결과가 다음과 같을 때`(result: Result) {
coEvery {
schoolRepository.loadSchoolEmail(any())
diff --git a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryViewModelTest.kt b/android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryViewModelTest.kt
similarity index 92%
rename from android/festago/app/src/test/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryViewModelTest.kt
rename to android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryViewModelTest.kt
index 9369a2a87..8398da7ed 100644
--- a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryViewModelTest.kt
+++ b/android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryViewModelTest.kt
@@ -1,43 +1,35 @@
package com.festago.festago.presentation.ui.ticketentry
-import com.festago.festago.analytics.AnalyticsHelper
+import com.festago.festago.common.analytics.AnalyticsHelper
import com.festago.festago.model.Ticket
import com.festago.festago.model.TicketCode
import com.festago.festago.presentation.fixture.TicketFixture
+import com.festago.festago.presentation.rule.MainDispatcherRule
import com.festago.festago.repository.TicketRepository
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.runTest
-import kotlinx.coroutines.test.setMain
import org.assertj.core.api.SoftAssertions
-import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
class TicketEntryViewModelTest {
+
private lateinit var vm: TicketEntryViewModel
private lateinit var ticketRepository: TicketRepository
private lateinit var analyticsHelper: AnalyticsHelper
- @OptIn(ExperimentalCoroutinesApi::class)
+ @get:Rule
+ val mainDispatcherRule = MainDispatcherRule()
+
@Before
fun setUp() {
- Dispatchers.setMain(UnconfinedTestDispatcher())
ticketRepository = mockk()
analyticsHelper = mockk(relaxed = true)
vm = TicketEntryViewModel(ticketRepository, analyticsHelper)
}
- @OptIn(ExperimentalCoroutinesApi::class)
- @After
- fun finish() {
- Dispatchers.resetMain()
- }
-
private fun `티켓 요쳥 결과는 다음과 같을 때`(result: Result) {
coEvery { ticketRepository.loadTicket(any()) } returns result
}
diff --git a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryViewModelTest.kt b/android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryViewModelTest.kt
similarity index 90%
rename from android/festago/app/src/test/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryViewModelTest.kt
rename to android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryViewModelTest.kt
index b9c8f8fec..7cdf3f2f5 100644
--- a/android/festago/app/src/test/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryViewModelTest.kt
+++ b/android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/ui/tickethistory/TicketHistoryViewModelTest.kt
@@ -1,32 +1,29 @@
package com.festago.festago.presentation.ui.tickethistory
-import com.festago.festago.analytics.AnalyticsHelper
+import com.festago.festago.common.analytics.AnalyticsHelper
import com.festago.festago.model.Ticket
import com.festago.festago.presentation.fixture.TicketFixture
+import com.festago.festago.presentation.rule.MainDispatcherRule
import com.festago.festago.repository.TicketRepository
import io.mockk.coEvery
import io.mockk.mockk
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.resetMain
-import kotlinx.coroutines.test.setMain
import org.assertj.core.api.SoftAssertions
-import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
class TicketHistoryViewModelTest {
- private lateinit var vm: TicketHistoryViewModel
+ private lateinit var vm: TicketHistoryViewModel
private lateinit var ticketRepository: TicketRepository
private lateinit var analyticsHelper: AnalyticsHelper
- @OptIn(ExperimentalCoroutinesApi::class)
+ @get:Rule
+ val mainDispatcherRule = MainDispatcherRule()
+
@Before
fun setUp() {
- Dispatchers.setMain(UnconfinedTestDispatcher())
ticketRepository = mockk()
analyticsHelper = mockk(relaxed = true)
@@ -36,12 +33,6 @@ class TicketHistoryViewModelTest {
)
}
- @OptIn(ExperimentalCoroutinesApi::class)
- @After
- fun finish() {
- Dispatchers.resetMain()
- }
-
private fun `티켓 기록 요청 결과가 다음과 같을 때`(result: Result>) {
coEvery {
ticketRepository.loadHistoryTickets(any())
diff --git a/android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveViewModelTest.kt b/android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveViewModelTest.kt
new file mode 100644
index 000000000..852e3f9fb
--- /dev/null
+++ b/android/festago/presentation-legacy/src/test/java/com/festago/festago/presentation/ui/ticketreserve/TicketReserveViewModelTest.kt
@@ -0,0 +1,280 @@
+package com.festago.festago.presentation.ui.ticketreserve
+
+import app.cash.turbine.test
+import com.festago.festago.common.analytics.AnalyticsHelper
+import com.festago.festago.model.ErrorCode
+import com.festago.festago.model.Reservation
+import com.festago.festago.model.ReservationStage
+import com.festago.festago.model.ReservationTicket
+import com.festago.festago.model.ReservationTickets
+import com.festago.festago.model.ReservedTicket
+import com.festago.festago.model.TicketType
+import com.festago.festago.presentation.rule.MainDispatcherRule
+import com.festago.festago.repository.AuthRepository
+import com.festago.festago.repository.FestivalRepository
+import com.festago.festago.repository.ReservationTicketRepository
+import com.festago.festago.repository.TicketRepository
+import io.mockk.coEvery
+import io.mockk.mockk
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.test.runTest
+import org.assertj.core.api.AssertionsForClassTypes.assertThat
+import org.assertj.core.api.SoftAssertions
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import java.time.LocalDate
+import java.time.LocalDateTime
+
+class TicketReserveViewModelTest {
+
+ private lateinit var vm: TicketReserveViewModel
+ private lateinit var reservationTicketRepository: ReservationTicketRepository
+ private lateinit var festivalRepository: FestivalRepository
+ private lateinit var ticketRepository: TicketRepository
+ private lateinit var authRepository: AuthRepository
+ private lateinit var analyticsHelper: AnalyticsHelper
+
+ private val fakeReservationTickets = ReservationTickets(
+ listOf(
+ ReservationTicket(1, TicketType.STUDENT, 219, 500),
+ ReservationTicket(1, TicketType.VISITOR, 212, 300),
+ ),
+ )
+ private val fakeReservationStage = ReservationStage(
+ id = 1,
+ lineUp = "르세라핌, 아이브, 뉴진스",
+ reservationTickets = fakeReservationTickets,
+ startTime = LocalDateTime.now(),
+ ticketOpenTime = LocalDateTime.now(),
+ )
+ private val fakeReservationStages = List(5) { fakeReservationStage }
+ private val fakeReservation = Reservation(
+ id = 1,
+ name = "테코대학교",
+ reservationStages = fakeReservationStages,
+ startDate = LocalDate.now(),
+ endDate = LocalDate.now(),
+ thumbnail = "https://search2.kakaocdn.net/argon/656x0_80_wr/8vLywd3V06c",
+ )
+
+ private val fakeReservedTicket = ReservedTicket(
+ id = 1,
+ entryTime = LocalDateTime.now(),
+ number = 1,
+ )
+
+ @get:Rule
+ val mainDispatcherRule = MainDispatcherRule()
+
+ @Before
+ fun setUp() {
+ reservationTicketRepository = mockk()
+ festivalRepository = mockk()
+ ticketRepository = mockk()
+ authRepository = mockk()
+ analyticsHelper = mockk(relaxed = true)
+ vm = TicketReserveViewModel(
+ reservationTicketRepository,
+ festivalRepository,
+ ticketRepository,
+ authRepository,
+ analyticsHelper,
+ )
+ }
+
+ private fun `예약 정보 요청 결과가 다음과 같을 때`(result: Result) {
+ coEvery { festivalRepository.loadFestivalDetail(any()) } returns result
+ }
+
+ private fun `인증 여부가 다음과 같을 때`(isSigned: Boolean) {
+ coEvery { authRepository.isSigned } answers { isSigned }
+ }
+
+ private fun `특정 공연의 티켓 타입 요청 결과가 다음과 같을 때`(result: Result) {
+ coEvery { reservationTicketRepository.loadTicketTypes(any()) } returns result
+ }
+
+ private fun `티켓 예약 요청 결과가 다음과 같을 때`(result: Result) {
+ coEvery { ticketRepository.reserveTicket(any()) } returns result
+ }
+
+ @Test
+ fun `예약 정보를 불러오면 성공 이벤트가 발생하고 리스트를 반환한다`() {
+ // given
+ `예약 정보 요청 결과가 다음과 같을 때`(Result.success(fakeReservation))
+ `인증 여부가 다음과 같을 때`(true)
+
+ // when
+ vm.loadReservation()
+
+ // then
+ assertThat(vm.uiState.value).isInstanceOf(TicketReserveUiState.Success::class.java)
+
+ // and
+ val festival = (vm.uiState.value as TicketReserveUiState.Success).festival
+ val expected = ReservationFestivalUiState(
+ id = festival.id,
+ name = festival.name,
+ thumbnail = festival.thumbnail,
+ endDate = festival.endDate,
+ startDate = festival.startDate,
+ )
+ assertThat(festival).isEqualTo(expected)
+ }
+
+ @Test
+ fun `예약 정보를 불러오는 것을 실패하면 에러 이벤트가 발생한다`() {
+ // given
+ `예약 정보 요청 결과가 다음과 같을 때`(Result.failure(Exception()))
+
+ // when
+ vm.loadReservation(0)
+
+ // then
+ assertThat(vm.uiState.value).isEqualTo(TicketReserveUiState.Error)
+ }
+
+ @Test
+ fun `예약 정보를 불러오는 중이면 로딩 이벤트가 발생한다`() {
+ // given
+ coEvery {
+ festivalRepository.loadFestivalDetail(0)
+ } coAnswers {
+ delay(1000)
+ Result.success(fakeReservation)
+ }
+
+ // when
+ vm.loadReservation()
+
+ // then
+ assertThat(vm.uiState.value).isEqualTo(TicketReserveUiState.Loading)
+ }
+
+ @Test
+ fun `특정 공연의 티켓 타입을 보여주는 이벤트가 발생하면 해당 공연의 티켓 타입을 보여준다`() = runTest {
+ // given
+ `특정 공연의 티켓 타입 요청 결과가 다음과 같을 때`(Result.success(fakeReservationTickets))
+ `인증 여부가 다음과 같을 때`(true)
+
+ vm.event.test {
+ // when
+ vm.showTicketTypes(1, LocalDateTime.MIN)
+
+ // then
+ val softly = SoftAssertions().apply {
+ val event = awaitItem()
+ assertThat(event).isExactlyInstanceOf(TicketReserveEvent.ShowTicketTypes::class.java)
+
+ // and
+ val actual = (event as? TicketReserveEvent.ShowTicketTypes)?.tickets
+ assertThat(actual).isEqualTo(fakeReservationTickets.sortedByTicketTypes())
+ }
+ softly.assertAll()
+ }
+ }
+
+ @Test
+ fun `특정 공연의 티켓 타입을 보여주는 것을 실패하면 에러 이벤트가 발생한다`() {
+ // given
+ `특정 공연의 티켓 타입 요청 결과가 다음과 같을 때`(Result.failure(Exception()))
+ `인증 여부가 다음과 같을 때`(true)
+
+ // when
+ vm.showTicketTypes(1, LocalDateTime.MIN)
+
+ // then
+ assertThat(vm.uiState.value).isEqualTo(TicketReserveUiState.Error)
+ }
+
+ @Test
+ fun `티켓 유형을 선택하고 예약하면 예매 성공 이벤트가 발생한다`() = runTest {
+ // given
+ coEvery {
+ ticketRepository.reserveTicket(any())
+ } answers {
+ Result.success(fakeReservedTicket)
+ }
+
+ vm.event.test {
+ // when
+ vm.reserveTicket(0)
+
+ // then
+ assertThat(awaitItem()).isExactlyInstanceOf(TicketReserveEvent.ReserveTicketSuccess::class.java)
+ }
+ }
+
+ @Test
+ fun `학생 인증하지 않아 티켓 예매에 실패하면 예매 실패 이벤트가 발생한다`() = runTest {
+ // given
+ `티켓 예약 요청 결과가 다음과 같을 때`(Result.failure(ErrorCode.NEED_STUDENT_VERIFICATION()))
+
+ vm.event.test {
+ // when
+ vm.reserveTicket(0)
+
+ // then
+ val actual = awaitItem() as? TicketReserveEvent.ReserveTicketFailed
+ assertThat(actual).isNotNull
+
+ // and: 학생 인증 필요 예매 실패 코드를 가진다
+ assertThat(actual?.errorCode).isExactlyInstanceOf(ErrorCode.NEED_STUDENT_VERIFICATION::class.java)
+ }
+ }
+
+ @Test
+ fun `이미 예매한 티켓이라서 티켓 예매에 실패하면 예매 실패 이벤트가 발생한다`() = runTest {
+ // given
+ `티켓 예약 요청 결과가 다음과 같을 때`(Result.failure(ErrorCode.RESERVE_TICKET_OVER_AMOUNT()))
+
+ vm.event.test {
+ // when
+ vm.reserveTicket(0)
+
+ // then
+ val actual = awaitItem() as? TicketReserveEvent.ReserveTicketFailed
+ assertThat(actual).isNotNull
+
+ // and: 보유 가능한 수량 초과 예매 실패 코드를 가진다
+ assertThat(actual?.errorCode).isExactlyInstanceOf(ErrorCode.RESERVE_TICKET_OVER_AMOUNT::class.java)
+ }
+ }
+
+ @Test
+ fun `티켓이 매진되어 티켓 예매에 실패하면 예매 실패 이벤트가 발생한다`() = runTest {
+ // given
+ `티켓 예약 요청 결과가 다음과 같을 때`(Result.failure(ErrorCode.TICKET_SOLD_OUT()))
+
+ vm.event.test {
+ // when
+ vm.reserveTicket(0)
+
+ // then
+ val actual = awaitItem() as? TicketReserveEvent.ReserveTicketFailed
+ assertThat(actual).isNotNull
+
+ // and: 티켓 매진 예매 실패 코드를 가진다
+ assertThat(actual?.errorCode).isExactlyInstanceOf(ErrorCode.TICKET_SOLD_OUT::class.java)
+ }
+ }
+
+ @Test
+ fun `알 수 없는 오류로 티켓 예매에 실패하면 예매 실패 이벤트가 발생한다`() = runTest {
+ // given
+ `티켓 예약 요청 결과가 다음과 같을 때`(Result.failure(Exception()))
+
+ vm.event.test {
+ // when
+ vm.reserveTicket(0)
+
+ // then
+ val actual = awaitItem() as? TicketReserveEvent.ReserveTicketFailed
+ assertThat(actual).isNotNull
+
+ // and: 알 수 없는 예매 실패 코드를 가진다
+ assertThat(actual?.errorCode).isExactlyInstanceOf(ErrorCode.UNKNOWN::class.java)
+ }
+ }
+}
diff --git a/android/festago/presentation/.gitignore b/android/festago/presentation/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/android/festago/presentation/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/android/festago/presentation/build.gradle.kts b/android/festago/presentation/build.gradle.kts
new file mode 100644
index 000000000..05e1d77f8
--- /dev/null
+++ b/android/festago/presentation/build.gradle.kts
@@ -0,0 +1,149 @@
+import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties
+
+plugins {
+ id("com.android.library")
+ id("org.jetbrains.kotlin.android")
+ id("kotlin-parcelize")
+ id("kotlin-kapt")
+ id("org.jlleitschuh.gradle.ktlint")
+ id("com.google.dagger.hilt.android")
+ id("androidx.navigation.safeargs")
+}
+
+android {
+ namespace = "com.festago.festago.presentation"
+ compileSdk = 34
+
+ defaultConfig {
+ minSdk = 28
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+
+ buildConfigField("String", "KAKAO_NATIVE_APP_KEY", getSecretKey("kakao_native_app_key"))
+ resValue("string", "kakao_redirection_scheme", getSecretKey("kakao_redirection_scheme"))
+ }
+
+ buildTypes {
+ debug {
+ buildConfigField("Boolean", "DEBUG_MODE", "true")
+ }
+
+ release {
+ isMinifyEnabled = false
+ buildConfigField("Boolean", "DEBUG_MODE", "false")
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro",
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_17.toString()
+ }
+
+ dataBinding {
+ enable = true
+ }
+}
+
+tasks.withType().all {
+ kotlinOptions {
+ jvmTarget = "17"
+ }
+}
+
+dependencies {
+ implementation(project(":common"))
+ implementation(project(":domain"))
+
+ // Feature module Support
+ implementation("androidx.navigation:navigation-dynamic-features-fragment:2.7.7")
+ implementation("androidx.navigation:navigation-ui-ktx:2.7.7")
+ implementation("com.google.firebase:firebase-config-ktx:21.6.3")
+
+ // Testing Navigation
+ androidTestImplementation("androidx.navigation:navigation-testing:2.7.7")
+ // android
+ implementation("androidx.core:core-ktx:1.10.1")
+ implementation("androidx.appcompat:appcompat:1.6.1")
+ implementation("com.google.android.material:material:1.9.0")
+ implementation("androidx.constraintlayout:constraintlayout:2.1.4")
+
+ // hilt
+ implementation("com.google.dagger:hilt-android:2.44")
+ kapt("com.google.dagger:hilt-android-compiler:2.44")
+ // hilt-android-testing = { group = "com.google.dagger", name = "hilt-android-testing", version.ref = "hilt" }
+ implementation("com.google.dagger:hilt-android-testing:2.44")
+
+ // recyclerview
+ implementation("androidx.recyclerview:recyclerview:1.3.1-rc01")
+
+ // lifecycle
+ implementation("androidx.lifecycle:lifecycle-extensions:2.2.0")
+
+ // glide
+ implementation("com.github.bumptech.glide:glide:4.15.1")
+
+ // glide blur
+ implementation("jp.wasabeef:glide-transformations:4.3.0")
+
+ // retrofit
+ implementation("com.squareup.retrofit2:retrofit:2.9.0")
+
+ // junit4
+ testImplementation("junit:junit:4.13.2")
+ testImplementation("androidx.test.ext:junit:1.1.5")
+ testImplementation("androidx.test:runner:1.5.2")
+
+ // assertJ
+ testImplementation("org.assertj:assertj-core:3.22.0")
+
+ // android-test
+ testImplementation("androidx.arch.core:core-testing:2.2.0")
+
+ // mock
+ testImplementation("io.mockk:mockk-android:1.13.5")
+
+ // espresso
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
+ androidTestImplementation("androidx.test.ext:junit:1.1.5")
+
+ // coroutine
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.6.4")
+ testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4")
+
+ // viewModel
+ implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
+ implementation("androidx.activity:activity-ktx:1.7.2")
+ implementation("androidx.fragment:fragment-ktx:1.6.0")
+
+ // zxing
+ implementation("com.journeyapps:zxing-android-embedded:4.3.0")
+
+ // firebase
+ implementation("com.google.firebase:firebase-messaging-ktx:23.4.0")
+
+ // swiperefreshlayout
+ implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
+
+ // kakao login
+ implementation("com.kakao.sdk:v2-user:2.12.0")
+
+ // turbine
+ testImplementation("app.cash.turbine:turbine:1.0.0")
+
+ // inApp Update
+ implementation("com.google.android.play:app-update-ktx:2.1.0")
+
+ // splash
+ implementation("androidx.core:core-splashscreen:1.1.0-alpha02")
+}
+
+fun getSecretKey(propertyKey: String): String {
+ return gradleLocalProperties(rootDir).getProperty(propertyKey)
+}
diff --git a/android/festago/presentation/consumer-rules.pro b/android/festago/presentation/consumer-rules.pro
new file mode 100644
index 000000000..e69de29bb
diff --git a/android/festago/presentation/proguard-rules.pro b/android/festago/presentation/proguard-rules.pro
new file mode 100644
index 000000000..481bb4348
--- /dev/null
+++ b/android/festago/presentation/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/android/festago/presentation/src/androidTest/java/com/festago/festago/.gitkeep b/android/festago/presentation/src/androidTest/java/com/festago/festago/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/android/festago/presentation/src/main/AndroidManifest.xml b/android/festago/presentation/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..5ab54f712
--- /dev/null
+++ b/android/festago/presentation/src/main/AndroidManifest.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+