diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 000000000..7717b74a7
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,13 @@
+root = true
+
+[*]
+charset = utf-8
+indent_size = 4
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.{kt,kts}]
+ij_kotlin_allow_trailing_comma=true
+ij_kotlin_allow_trailing_comma_on_call_site=true
+ktlint_disabled_rules = import-ordering
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 000000000..74519ded4
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -0,0 +1,32 @@
+name: Bug report
+description: File a bug report
+title: '[QA]: '
+labels: ['๐QA๐']
+
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for taking the time to fill out this bug report! ๐
+ - type: textarea
+ id: what-happened
+ attributes:
+ label: ์ด๋ค ์ผ์ด ๋ฐ์ํ๋์? ๐ค
+ description: ๋ํ, ์ด๋ค ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ํ์๋์ง ์๋ ค์ฃผ์ธ์.
+ placeholder: ์์์น ๋ชปํ ๋ฒ๊ทธ๊ฐ ๋ฐ์ํ์ต๋๋ค...
+ validations:
+ required: true
+ - type: textarea
+ id: screenshots
+ attributes:
+ label: ๊ด๋ จ๋ ์คํฌ๋ฆฐ ์ท์ด๋, ๋ฒ๊ทธ ๋ฐ์ ์กฐ๊ฑด์ ์ค๋ช
ํด์ฃผ์ธ์
+ description: ๋น ๋ฅด๊ฒ ์ดํดํ ์๋ก ๋น ๋ฅธ ๋์์ด ๊ฐ๋ฅํด์ !
+ placeholder: ๋ค๋ก๊ฐ๊ธฐ ๋ฒํผ 10๋ฒ ํด๋ฆญ์ ํฌ๋์ฌ๊ฐ ๋ฐ์
+ - type: checkboxes
+ id: terms
+ attributes:
+ label: ๋์ ๐
+ description:
+ options:
+ - label: ๋ค๋ฅธ ์ด์๊ฐ ์๋์ง ํ์ธํ์ต๋๋ค. โ
+ required: true
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 3d508255d..b6431641f 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -1,7 +1,9 @@
## 1. ๐ ๊ด๋ จ๋ ์ด์ ๋ฐ ์๊ฐ
-## 2. ๐ฅ๋ณ๊ฒฝ๋ ์
+## 2. ๐ฅ ๋ณ๊ฒฝ๋ ์
-## 3. ๐ธ ์คํฌ๋ฆฐ์ท(์ ํ)
+## 3. โ
๊ผญ ํ์ธํด์คฌ์ผ๋ฉด ํ๋ ๋ถ๋ถ
-## 4. ๐ก์๊ฒ๋ ํน์ ๊ถ๊ธํ ์ฌํญ๋ค
+## 4. ๐ธ ์คํฌ๋ฆฐ์ท(์ ํ)
+
+## 5. ๐ก์๊ฒ๋ ํน์ ๊ถ๊ธํ ์ฌํญ๋ค
diff --git a/.github/workflows/opened-pr-notification.yml b/.github/workflows/opened-pr-notification.yml
index 3addfa014..1cded89dd 100644
--- a/.github/workflows/opened-pr-notification.yml
+++ b/.github/workflows/opened-pr-notification.yml
@@ -21,6 +21,9 @@ jobs:
distribution: zulu
cache: gradle
+ - name: add google-services.json
+ run: echo '${{ secrets.GOOGLE_SERVICES_JSON }}' > ./app/google-services.json
+
- name: add local.properties
run: |
echo api_key=\"${{ secrets.API_KEY }}\" >> ./local.properties
diff --git a/.github/workflows/released-app-distribution.yml b/.github/workflows/released-app-distribution.yml
new file mode 100644
index 000000000..65b485b48
--- /dev/null
+++ b/.github/workflows/released-app-distribution.yml
@@ -0,0 +1,111 @@
+name: Released-App-Distribution
+on:
+ push:
+ branches:
+ - 'release'
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: set up JDK 17
+ uses: actions/setup-java@v3
+ with:
+ java-version: 17
+ distribution: zulu
+ cache: gradle
+
+ - name: add google-services.json
+ run: echo '${{ secrets.GOOGLE_SERVICES_JSON }}' > ./app/google-services.json
+
+ - name: add local.properties
+ run: |
+ echo api_key=\"${{ secrets.API_KEY }}\" >> ./local.properties
+
+ - name: Grant execute permission for gradlew
+ run: chmod +x gradlew
+
+ - name: Build with Gradle
+ run: ./gradlew build
+
+ - name: Build release APK
+ run: ./gradlew assembleRelease
+
+ - name: Setup build tool version variable
+ shell: bash
+ run: |
+ BUILD_TOOL_VERSION=$(ls /usr/local/lib/android/sdk/build-tools/ | tail -n 1)
+ echo "BUILD_TOOL_VERSION=$BUILD_TOOL_VERSION" >> $GITHUB_ENV
+ echo Last build tool version is: $BUILD_TOOL_VERSION
+
+ - name: Sign APK
+ id: sign_app
+ uses: r0adkll/sign-android-release@v1
+ with:
+ releaseDirectory: app/build/outputs/apk/release
+ signingKeyBase64: ${{ secrets.KEY_BASE_64_RELEASE }}
+ alias: ${{ secrets.KEY_ALIAS }}
+ keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}
+ keyPassword: ${{ secrets.KEY_PASSWORD }}
+ env:
+ BUILD_TOOLS_VERSION: ${{ env.BUILD_TOOL_VERSION }}
+
+ - name: Authenticate to Firebase
+ uses: google-github-actions/auth@v1
+ with:
+ credentials_json: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
+
+ - name: Setup Firebase CLI
+ run: curl -sL https://firebase.tools | bash
+
+ - name: upload artifact to Firebase App Distribution
+ uses: wzieba/Firebase-Distribution-Github-Action@v1.7.0
+ with:
+ appId: ${{secrets.FIREBASE_APP_ID}}
+ serviceCredentialsFileContent: ${{ secrets.CREDENTIAL_FILE_CONTENT }}
+ groups: WAPP_QA
+ file: ${{steps.sign_app.outputs.signedReleaseFile}}
+
+ - name: Send Success Message
+ if: ${{ success() }}
+ uses: Ilshidur/action-discord@0.3.2
+ env:
+ DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
+ DISCORD_USERNAME: WAPP_BOT
+ DISCORD_AVATAR: https://github.com/pknu-wap/WAPP/blob/main/image/icon.png?raw=true
+ DISCORD_EMBEDS: |
+ [
+ {
+ "author": {
+ "name": "WAPP Release",
+ "url": "https://github.com/pknu-wap/WAPP/blob/main/image/icon.png?raw=true",
+ "icon_url": "https://github.com/pknu-wap/WAPP/blob/main/image/icon.png?raw=true"
+ },
+ "title": "๋ฆด๋ฆฌ์ฆ ์ฑ๊ณต, ์ ๋ ๊ณผ ์ ~ ๐ฅ๐ฅ",
+ "color": 10478271,
+ "description": "๋ฉ์ผ์ ์๋ก์ด ๋ฆด๋ฆฌ์ฆ ์ฑ ๋ฐฐ์ก์๋ฃํ์ด์!"
+ }
+ ]
+
+ - name: Send Failure Message
+ if: ${{ failure() }}
+ uses: Ilshidur/action-discord@0.3.2
+ env:
+ DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
+ DISCORD_USERNAME: WAPP_BOT
+ DISCORD_AVATAR: https://github.com/pknu-wap/WAPP/blob/main/image/icon.png?raw=true
+ DISCORD_EMBEDS: |
+ [
+ {
+ "author": {
+ "name": "WAPP Release",
+ "url": "https://github.com/pknu-wap/WAPP/blob/main/image/icon.png?raw=true",
+ "icon_url": "https://github.com/pknu-wap/WAPP/blob/main/image/icon.png?raw=true"
+ },
+ "title": "๋ฆด๋ฆฌ์ฆ ์คํจ, ๋๊ฐ ์ด๋ ๊ฒ ํ๋. ๐ญ๐ญ",
+ "color": 13458524,
+ "description": "๋ค์ ๋ฆด๋ฆฌ์ฆ ํด์ค์ธ์. ์๋น
"
+ }
+ ]
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..70efd0777
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,16 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
+google-services.json
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 000000000..b589d56e9
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml
new file mode 100644
index 000000000..2f0367c47
--- /dev/null
+++ b/.idea/deploymentTargetDropDown.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 000000000..fc333ade3
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 000000000..103e00cbe
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
new file mode 100644
index 000000000..f8467b458
--- /dev/null
+++ b/.idea/kotlinc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/migrations.xml b/.idea/migrations.xml
new file mode 100644
index 000000000..f8051a6f9
--- /dev/null
+++ b/.idea/migrations.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 000000000..8978d23db
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 000000000..35eb1ddfb
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index db8f360bd..436908272 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# ์ํผ - WAP Official App
+# ์ํผ - WAP ์ผ์ ํ์ธ ๋ฐ ์ค๋ฌธ์กฐ์ฌ ํ๋ซํผ
```
WAP ํ์ฌ์ผ์ ์ ์ฝ๊ฒ ์๋ ค๋๋ฆด๊ฒ์! ํจ๊ปํด์ ์ํผ
@@ -8,36 +8,148 @@ WAP ํ์ฌ์ผ์ ์ ์ฝ๊ฒ ์๋ ค๋๋ฆด๊ฒ์! ํจ๊ปํด์ ์ํผ
-### ๊ธฐ๋ฅ ์๊ฐ
+## ๐ฑ Feature Introduce
+
+#### ์ธํธ๋ก ํ๋ฉด
+
+
+
+
#### ๊ณต์ง์ฌํญ
- WAP ์ ๊ท ํ๋ ๋ฐ ํ์ฌ๋ฅผ ๋ฌ๋ ฅ๊ณผ ๋ชฉ๋ก์ ํตํด ํ์ธํ ์ ์์ด์.
-- ์ง์ ๋ ํ์ฌ ๋ ์ง์ ํธ์ ์๋ฆผ์ ํตํด ๋ฆฌ๋ง์ธ๋ ํด๋๋ ค์.
+
+
+
+
+
+
#### ์ถ์
- ํ์ฌ๋ง๋ค ์ถ์์ ์ฒดํฌํ ์ ์์ด์.
- ํ์คํ ๋ฆฌ๋ฅผ ํตํด ์ถ๊ฒฐ์ํฉ์ ์ฒดํฌํ ์ ์์ด์.
+
+
+
+
+
+
#### ์ค๋ฌธ
- ํ์ฌ๋ง๋ค ์ค๋ฌธ ๋ฐ ํผ๋๋ฐฑ์ ์์ฑํ ์ ์์ด์
- ์ค๋ฌธ๊ณผ ํผ๋๋ฐฑ์ ํตํด, ๋ ์ข์ ํ์ฌ๋ก ๋ฐ์ ํ ์ ์์ด์.
-## ๐ Contributors ๐
+
+
+
+
+
+
+#### ์ด์์ง
+
+- ํ์๋ค์ ์ค๋ฌธ์ ํ์ธํ ์ ์์ด์.
+- ๊ณต์ง์ฌํญ ๋ฐ ์ค๋ฌธ์ ๋ฑ๋กํ ์ ์์ด์.
+- ์ถ์์ ์์ํ ์ ์์ด์
+
+
+
+
+
+
+
+#### ๊ทธ ์ธ
+
+- ํ๋กํ
+- ํ๋กํ ๋ ๋ณด๊ธฐ
+
+
+
+
+
+
+
+## ๐๏ธ Module Dependency Graph
+
+
+
+
+
+
+## ๐ฉ Android Tech Stack
+
+
+
+
+
+ Version Catalog
+
+
+
+
+## ๐ Contributors ๐
- JinHo Jeong ๐ป |
- Tgyuu An ๐ป |
+ JinHo Jeong ๐ป |
+ Tgyuu An ๐ป |
์๋๋ก์ด๋ |
์๋๋ก์ด๋ |
- ๋ก๊ทธ์ธ , ์ถ์์ฒดํฌ , ๋ง์ดํ์ด์ง |
- ๊ณต์ง์ฌํญ , ๋ฌ๋ ฅ , ์ค๋ฌธ์กฐ์ฌ |
+ ๋ก๊ทธ์ธ , ์ด์์งํ์ด์ง , ์ค๋ฌธ , ์ผ์ |
+ ๊ณต์ง์ฌํญ , ๋ฌ๋ ฅ , ํ๋กํ , ์ถ์ , ์ ๋๋ฉ์ด์
|
+
+
+
+## ๐ Trouble Shooting
+```
+ํ๋ก์ ํธ ์ค ๋ฐ์ํ ์ด์์ ๋ํด ํธ๋ฌ๋ธ ์ํ
์ ๊ธฐ๋กํ๋ ๊ณต๊ฐ์
๋๋ค.
+```
+[WAPP ํธ๋ฌ๋ธ ์ํ
](https://discovered-trust-803.notion.site/WAPP-238f82deeac44721a3321665573c9f76?pvs=4)
+
+
+
+## ๐โโ๏ธ Sprint
+```
+๋งค์ฃผ ์์์ผ, ์คํ๋ฆฐํธ์ ํ ๋นํ ์ด์๋ฅผ ์ง๋ผ์ ๊นํ์ ๋ฑ๋กํ๋ค. ์คํ๋ฆฐํธ ๋จ์๋ ์ผ์ฃผ์ผ์ด๋ฉฐ, ๊ฐ๋ฐ ์ผ์ ์ ๋ฐ๋ฅธ๋ค.
+๋ชฉ์์ผ ์คํ 11์ 30๋ถ๊น์ง ๋ชป ๋๋ธ ์ด์ ํ๋๋น ์คํ ํ๋๋ก ๊ฐ์ฃผํ๋ฉฐ, ์คํ ์ธ ๊ฐ๊ฐ ๋ชจ์์ ๋ ๋ฐฅ ํ ๋ผ๋ฅผ ์ฌ์ผ ํ๋ค.
+```
+[์คํ๋ฆฐํธ ๊ธฐ๋ก](https://www.notion.so/79134caa75394435a221a15c53226726?v=c3640a0dae5f4bac8ecf51d61aae4acf)
+
+
+
+## ๐จ UI/UX
+
+![image](https://github.com/pknu-wap/WAPP/assets/116813010/ccb60d3b-ec63-41dc-8eca-d0a8b735c1fb)
+
+[ํผ๊ทธ๋ง ๋ณด๋ฌ๊ฐ๊ธฐ](https://www.figma.com/file/ldfJcNruLXpb7e41P7LK3O/WAPP?type=design&node-id=0%3A1&mode=design&t=Q2vI9pGnu1OcsFWP-1)
+
+
+
+## ๐ป Code Convention
+
+[WAPP ์๋๋ก์ด๋ ์ฝ๋ ์ปจ๋ฒค์
](https://github.com/pknu-wap/WAPP/wiki/%F0%9F%A6%92%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%BB%A8%EB%B2%A4%EC%85%98%F0%9F%A6%92)
+
+
+
+## โ๏ธ Git Convention & Git Flow ์ ๋ต
+[WAPP ๊น ์ปจ๋ฒค์
](https://github.com/pknu-wap/WAPP/wiki/%F0%9F%90%B1%EA%B9%83-%EC%BB%A8%EB%B2%A4%EC%85%98%F0%9F%90%B1)
+``` kotlin
+1. Issue๋ฅผ ์์ฑํ๋ค.
+2. feature Branch๋ฅผ ์์ฑํ๋ค.
+3. Add - Commit - Push - Pull Request ์ ๊ณผ์ ์ ๊ฑฐ์น๋ค.
+4. Pull Request๊ฐ ์์ฑ๋๋ฉด ์์ฑ์ ์ด์ธ์ ๋ค๋ฅธ ํ์์ด Code Review๋ฅผ ํ๋ค.
+5. Code Review๊ฐ ์๋ฃ๋๋ฉด Pull Request ์์ฑ์๊ฐ develop Branch๋ก merge ํ๋ค.
+6. merge๋ ์์
์ด ์์ ๊ฒฝ์ฐ, ๋ค๋ฅธ ๋ธ๋์น์์ ์์
์ ์งํ ์ค์ด๋ ๊ฐ๋ฐ์๋ ๋ณธ์ธ์ ๋ธ๋์น๋ก merge๋ ์์
์ Pull ๋ฐ์์จ๋ค.
+7. ์ข
๋ฃ๋ Issue์ Pull Request์ Label๊ณผ Project๋ฅผ ๊ด๋ฆฌํ๋ค.
+```
+
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 000000000..b46299f7e
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1,2 @@
+/build
+/google-services.json
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
new file mode 100644
index 000000000..7488122a0
--- /dev/null
+++ b/app/build.gradle.kts
@@ -0,0 +1,68 @@
+plugins {
+ id("com.wap.wapp.application")
+ id("com.wap.wapp.firebase")
+ id("com.wap.wapp.compose")
+ id("com.wap.wapp.hilt")
+ id("com.wap.wapp.navigation")
+}
+
+android {
+ namespace = "com.wap.wapp"
+
+ defaultConfig {
+ applicationId = "com.wap.wapp"
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro",
+ )
+ signingConfig = signingConfigs.getByName("debug")
+ }
+ }
+}
+
+dependencies {
+ implementation(project(":feature:auth"))
+ implementation(project(":feature:notice"))
+ implementation(project(":feature:survey"))
+ implementation(project(":feature:survey-check"))
+ implementation(project(":feature:profile"))
+ implementation(project(":feature:attendance"))
+ implementation(project(":feature:management"))
+ implementation(project(":feature:management-survey"))
+ implementation(project(":feature:management-event"))
+ implementation(project(":feature:splash"))
+ implementation(project(":core:designresource"))
+ implementation(project(":core:designsystem"))
+ implementation(project(":core:domain"))
+
+ implementation(libs.bundles.androidx)
+ implementation(libs.material)
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.test.junit)
+ androidTestImplementation(libs.androidx.test.espresso)
+}
+
+// tasks.getByPath(":app:preBuild").dependsOn("installGitHook")
+//
+// tasks.register("installGitHook") {
+// dependsOn("deletePreviousGitHook")
+// from("${rootProject.rootDir}/script/pre-commit")
+// into("${rootProject.rootDir}/.git/hooks")
+// eachFile {
+// fileMode = 777
+// }
+// }
+//
+// tasks.register("deletePreviousGitHook") {
+//
+// val prePush = "${rootProject.rootDir}/.git/hooks/pre-commit"
+// if (file(prePush).exists()) {
+// delete(prePush)
+// }
+// }
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 000000000..481bb4348
--- /dev/null
+++ b/app/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/app/src/androidTest/java/com/wap/wapp/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/wap/wapp/ExampleInstrumentedTest.kt
new file mode 100644
index 000000000..2845a4bc7
--- /dev/null
+++ b/app/src/androidTest/java/com/wap/wapp/ExampleInstrumentedTest.kt
@@ -0,0 +1,22 @@
+package com.wap.wapp
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import junit.framework.TestCase.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.wap.wapp", appContext.packageName)
+ }
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..4ded0df9c
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png
new file mode 100644
index 000000000..b6a52692f
Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ
diff --git a/app/src/main/java/com/wap/wapp/MainActivity.kt b/app/src/main/java/com/wap/wapp/MainActivity.kt
new file mode 100644
index 000000000..04fcb5230
--- /dev/null
+++ b/app/src/main/java/com/wap/wapp/MainActivity.kt
@@ -0,0 +1,136 @@
+package com.wap.wapp
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.SystemBarStyle
+import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.navigationBars
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.windowInsetsPadding
+import androidx.compose.material3.Scaffold
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.navigation.NavController
+import androidx.navigation.NavGraph.Companion.findStartDestination
+import androidx.navigation.compose.currentBackStackEntryAsState
+import androidx.navigation.compose.rememberNavController
+import com.wap.designsystem.WappTheme
+import com.wap.wapp.component.WappBottomBar
+import com.wap.wapp.core.domain.usecase.auth.SignInUseCase
+import com.wap.wapp.feature.attendance.management.navigation.attendanceManagementNavigationRoute
+import com.wap.wapp.feature.auth.signin.navigation.signInNavigationRoute
+import com.wap.wapp.feature.auth.signup.navigation.signUpNavigationRoute
+import com.wap.wapp.feature.management.event.navigation.eventEditNavigationRoute
+import com.wap.wapp.feature.management.event.navigation.eventRegistrationNavigationRoute
+import com.wap.wapp.feature.management.survey.navigation.ManagementSurveyRoute
+import com.wap.wapp.feature.profile.profilesetting.navigation.profileSettingNavigationRoute
+import com.wap.wapp.feature.splash.navigation.splashNavigationRoute
+import com.wap.wapp.feature.survey.check.navigation.SurveyCheckRoute
+import com.wap.wapp.feature.survey.check.navigation.SurveyCheckRoute.surveyCheckRoute
+import com.wap.wapp.feature.survey.navigation.SurveyRoute
+import com.wap.wapp.navigation.TopLevelDestination
+import com.wap.wapp.navigation.WappNavHost
+import dagger.hilt.android.AndroidEntryPoint
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class MainActivity : ComponentActivity() {
+
+ @Inject
+ lateinit var signInUseCase: SignInUseCase
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ setSystemBarStyle()
+ super.onCreate(savedInstanceState)
+ setContent {
+ WappTheme {
+ val navController = rememberNavController()
+
+ Scaffold(
+ containerColor = WappTheme.colors.backgroundBlack,
+ bottomBar = {
+ val navBackStackEntry by
+ navController.currentBackStackEntryAsState()
+
+ val currentRoute = navBackStackEntry?.destination?.route
+ var bottomBarState by rememberSaveable { mutableStateOf(false) }
+
+ handleBottomBarState(
+ currentRoute,
+ setBottomBarState = { boolean ->
+ bottomBarState = boolean
+ },
+ )
+
+ WappBottomBar(
+ currentRoute = currentRoute,
+ bottomBarState = bottomBarState,
+ onNavigateToDestination = { destination ->
+ navigateToTopLevelDestination(
+ navController,
+ destination,
+ )
+ },
+ modifier = Modifier.height(70.dp),
+ )
+ },
+ modifier = Modifier
+ .windowInsetsPadding(WindowInsets.navigationBars)
+ .fillMaxSize(),
+ ) { innerPadding ->
+ WappNavHost(
+ signInUseCase = signInUseCase,
+ navController = navController,
+ modifier = Modifier.padding(innerPadding),
+ )
+ }
+ }
+ }
+ }
+}
+
+private fun ComponentActivity.setSystemBarStyle() = enableEdgeToEdge(
+ statusBarStyle = SystemBarStyle.light(getColor(R.color.yellow34), getColor(R.color.yellow34)),
+ navigationBarStyle = SystemBarStyle.light(getColor(R.color.black25), getColor(R.color.black25)),
+)
+
+private fun handleBottomBarState(
+ currentRoute: String?,
+ setBottomBarState: (Boolean) -> Unit,
+): Unit = when (currentRoute) {
+ null -> setBottomBarState(false)
+ signInNavigationRoute -> setBottomBarState(false)
+ signUpNavigationRoute -> setBottomBarState(false)
+ splashNavigationRoute -> setBottomBarState(false)
+ profileSettingNavigationRoute -> setBottomBarState(false)
+ attendanceManagementNavigationRoute -> setBottomBarState(false)
+ ManagementSurveyRoute.surveyFormRegistrationRoute -> setBottomBarState(false)
+ ManagementSurveyRoute.surveyFormEditRoute("{id}") -> setBottomBarState(false)
+ eventRegistrationNavigationRoute -> setBottomBarState(false)
+ eventEditNavigationRoute -> setBottomBarState(false)
+ SurveyRoute.answerRoute("{id}") -> setBottomBarState(false)
+ surveyCheckRoute -> setBottomBarState(false)
+ SurveyCheckRoute.surveyDetailRoute("{id}", "{backStack}") -> setBottomBarState(false)
+ else -> setBottomBarState(true)
+}
+
+private fun navigateToTopLevelDestination(
+ navController: NavController,
+ destination: TopLevelDestination,
+) {
+ navController.navigate(route = destination.route) {
+ popUpTo(navController.graph.findStartDestination().id) {
+ saveState = true
+ }
+ launchSingleTop = true
+ restoreState = true
+ }
+}
diff --git a/app/src/main/java/com/wap/wapp/WappApplication.kt b/app/src/main/java/com/wap/wapp/WappApplication.kt
new file mode 100644
index 000000000..c99cbd413
--- /dev/null
+++ b/app/src/main/java/com/wap/wapp/WappApplication.kt
@@ -0,0 +1,7 @@
+package com.wap.wapp
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class WappApplication : Application()
diff --git a/app/src/main/java/com/wap/wapp/component/WappBottomBar.kt b/app/src/main/java/com/wap/wapp/component/WappBottomBar.kt
new file mode 100644
index 000000000..c5606df4a
--- /dev/null
+++ b/app/src/main/java/com/wap/wapp/component/WappBottomBar.kt
@@ -0,0 +1,72 @@
+package com.wap.wapp.component
+
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutVertically
+import androidx.compose.animation.with
+import androidx.compose.material.BottomNavigation
+import androidx.compose.material.BottomNavigationItem
+import androidx.compose.material.Icon
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.sp
+import com.wap.designsystem.WappTheme
+import com.wap.wapp.navigation.TopLevelDestination
+
+@OptIn(ExperimentalAnimationApi::class)
+@Composable
+internal fun WappBottomBar(
+ modifier: Modifier = Modifier,
+ currentRoute: String?,
+ onNavigateToDestination: (TopLevelDestination) -> Unit,
+ bottomBarState: Boolean,
+) {
+ AnimatedContent(
+ targetState = bottomBarState,
+ label = "",
+ transitionSpec = {
+ slideInVertically { height -> height } with
+ slideOutVertically { height -> height }
+ },
+ ) { isVisible ->
+ if (isVisible) {
+ BottomNavigation(
+ backgroundColor = WappTheme.colors.black25,
+ modifier = modifier,
+ ) {
+ TopLevelDestination.entries.forEach { destination ->
+ val isSelect = currentRoute == destination.route
+ BottomNavigationItem(
+ selected = isSelect,
+ onClick = { onNavigateToDestination(destination) },
+ selectedContentColor = WappTheme.colors.yellow34,
+ unselectedContentColor = WappTheme.colors.grayA2,
+ icon = {
+ Icon(
+ painter = painterResource(id = destination.iconDrawableId),
+ contentDescription = null,
+ )
+ },
+ label = {
+ val labelColor = if (isSelect) {
+ WappTheme.colors.yellow34
+ } else {
+ WappTheme.colors.grayA2
+ }
+
+ Text(
+ text = stringResource(id = destination.labelTextId),
+ style = WappTheme.typography.labelMedium.copy(fontSize = 10.sp),
+ color = labelColor,
+ )
+ },
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/wap/wapp/navigation/TopLevelDestination.kt b/app/src/main/java/com/wap/wapp/navigation/TopLevelDestination.kt
new file mode 100644
index 000000000..fa4761848
--- /dev/null
+++ b/app/src/main/java/com/wap/wapp/navigation/TopLevelDestination.kt
@@ -0,0 +1,43 @@
+package com.wap.wapp.navigation
+
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+import com.wap.wapp.R
+import com.wap.wapp.core.designresource.R.string
+import com.wap.wapp.feature.attendance.navigation.attendanceNavigationRoute
+import com.wap.wapp.feature.management.navigation.managementNavigationRoute
+import com.wap.wapp.feature.notice.navigation.noticeNavigationRoute
+import com.wap.wapp.feature.profile.navigation.profileNavigationRoute
+import com.wap.wapp.feature.survey.navigation.SurveyRoute
+
+enum class TopLevelDestination(
+ val route: String,
+ @DrawableRes val iconDrawableId: Int,
+ @StringRes val labelTextId: Int,
+) {
+ NOTICE(
+ route = noticeNavigationRoute,
+ iconDrawableId = R.drawable.ic_notice,
+ labelTextId = string.notice,
+ ),
+ SURVEY(
+ route = SurveyRoute.route,
+ iconDrawableId = R.drawable.ic_survey,
+ labelTextId = string.survey,
+ ),
+ ATTENDANCE(
+ route = attendanceNavigationRoute,
+ iconDrawableId = com.wap.wapp.core.designresource.R.drawable.ic_check,
+ labelTextId = com.wap.wapp.feature.attendance.R.string.attendance,
+ ),
+ PROFILE(
+ route = profileNavigationRoute,
+ iconDrawableId = R.drawable.ic_profile,
+ labelTextId = string.profile,
+ ),
+ MANAGEMENT(
+ route = managementNavigationRoute,
+ iconDrawableId = R.drawable.ic_management,
+ labelTextId = string.management,
+ ),
+}
diff --git a/app/src/main/java/com/wap/wapp/navigation/WappNavHost.kt b/app/src/main/java/com/wap/wapp/navigation/WappNavHost.kt
new file mode 100644
index 000000000..e2da0bd70
--- /dev/null
+++ b/app/src/main/java/com/wap/wapp/navigation/WappNavHost.kt
@@ -0,0 +1,159 @@
+package com.wap.wapp.navigation
+
+import androidx.compose.foundation.background
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.NavHost
+import androidx.navigation.navOptions
+import com.wap.designsystem.WappTheme
+import com.wap.wapp.core.domain.usecase.auth.SignInUseCase
+import com.wap.wapp.feature.attendance.management.navigation.attendanceManagementScreen
+import com.wap.wapp.feature.attendance.management.navigation.navigateToAttendanceManagement
+import com.wap.wapp.feature.attendance.navigation.attendanceNavigationRoute
+import com.wap.wapp.feature.attendance.navigation.attendanceScreen
+import com.wap.wapp.feature.attendance.navigation.navigateToAttendance
+import com.wap.wapp.feature.auth.signin.navigation.navigateToSignIn
+import com.wap.wapp.feature.auth.signin.navigation.signInNavigationRoute
+import com.wap.wapp.feature.auth.signin.navigation.signInScreen
+import com.wap.wapp.feature.auth.signup.navigation.navigateToSignUp
+import com.wap.wapp.feature.auth.signup.navigation.signUpScreen
+import com.wap.wapp.feature.management.event.navigation.managementEventNavGraph
+import com.wap.wapp.feature.management.event.navigation.navigateToEventEdit
+import com.wap.wapp.feature.management.event.navigation.navigateToEventRegistration
+import com.wap.wapp.feature.management.navigation.managementScreen
+import com.wap.wapp.feature.management.navigation.navigateToManagement
+import com.wap.wapp.feature.management.survey.navigation.managementSurveyNavGraph
+import com.wap.wapp.feature.management.survey.navigation.navigateToSurveyFormEdit
+import com.wap.wapp.feature.management.survey.navigation.navigateToSurveyFormRegistration
+import com.wap.wapp.feature.notice.navigation.navigateToNotice
+import com.wap.wapp.feature.notice.navigation.noticeScreen
+import com.wap.wapp.feature.profile.navigation.navigateToProfile
+import com.wap.wapp.feature.profile.navigation.profileNavigationRoute
+import com.wap.wapp.feature.profile.navigation.profileScreen
+import com.wap.wapp.feature.profile.profilesetting.navigation.navigateToProfileSetting
+import com.wap.wapp.feature.profile.profilesetting.navigation.profileSettingNavigationRoute
+import com.wap.wapp.feature.profile.profilesetting.navigation.profileSettingScreen
+import com.wap.wapp.feature.splash.navigation.splashNavigationRoute
+import com.wap.wapp.feature.splash.navigation.splashScreen
+import com.wap.wapp.feature.survey.check.navigation.SurveyCheckRoute.surveyCheckRoute
+import com.wap.wapp.feature.survey.check.navigation.SurveyDetailBackStack
+import com.wap.wapp.feature.survey.check.navigation.navigateToSurveyCheck
+import com.wap.wapp.feature.survey.check.navigation.navigateToSurveyDetail
+import com.wap.wapp.feature.survey.check.navigation.surveyCheckNavGraph
+import com.wap.wapp.feature.survey.navigation.navigateToSurvey
+import com.wap.wapp.feature.survey.navigation.navigateToSurveyAnswer
+import com.wap.wapp.feature.survey.navigation.surveyNavGraph
+
+@Composable
+fun WappNavHost(
+ navController: NavHostController,
+ modifier: Modifier = Modifier,
+ signInUseCase: SignInUseCase,
+ startDestination: String = splashNavigationRoute,
+) {
+ NavHost(
+ navController = navController,
+ startDestination = startDestination,
+ modifier = modifier.background(WappTheme.colors.black25),
+ ) {
+ splashScreen(
+ navigateToAuth = {
+ navController.navigateToSignIn(
+ navOptions {
+ popUpTo(splashNavigationRoute) { inclusive = true }
+ },
+ )
+ },
+ navigateToNotice = {
+ navController.navigateToNotice(
+ navOptions { popUpTo(splashNavigationRoute) { inclusive = true } },
+ )
+ },
+ )
+ signInScreen(
+ signInUseCase = signInUseCase,
+ navigateToNotice = navController::navigateToNotice,
+ navigateToSignUp = navController::navigateToSignUp,
+ )
+ signUpScreen(
+ navigateToNotice = {
+ navController.navigateToNotice(
+ navOptions { popUpTo(navController.graph.id) { inclusive = true } },
+ )
+ },
+ navigateToSignIn = navController::navigateToSignIn,
+ )
+ noticeScreen()
+ surveyNavGraph(
+ navigateToSurvey = navController::navigateToSurvey,
+ navigateToSurveyAnswer = navController::navigateToSurveyAnswer,
+ navigateToSignIn = navController::navigateToSignIn,
+ navigateToSurveyCheck = navController::navigateToSurveyCheck,
+ )
+ surveyCheckNavGraph(
+ navigateToSurveyCheck = {
+ navController.navigateToSurveyCheck(
+ navOptions { popUpTo(surveyCheckRoute) { inclusive = true } },
+ )
+ },
+ navigateToSurveyDetail = navController::navigateToSurveyDetail,
+ navigateToSurvey = {
+ navController.navigateToSurvey(
+ navOptions { popUpTo(surveyCheckRoute) { inclusive = true } },
+ )
+ },
+ navigateToProfile = navController::navigateToProfile,
+ )
+ managementSurveyNavGraph(
+ navigateToManagement = navController::navigateToManagement,
+ )
+ managementEventNavGraph(
+ navigateToManagement = navController::navigateToManagement,
+ )
+ profileScreen(
+ navigateToProfileSetting = navController::navigateToProfileSetting,
+ navigateToAttendance = navController::navigateToAttendance,
+ navigateToSignIn = {
+ navController.navigateToSignIn(navOptions { popUpTo(profileNavigationRoute) })
+ },
+ navigateToSurveyDetail = { surveyId ->
+ navController.navigateToSurveyDetail(
+ surveyId = surveyId,
+ backStack = SurveyDetailBackStack.PROFILE,
+ navOptions = navOptions { popUpTo(profileNavigationRoute) },
+ )
+ },
+ )
+ attendanceScreen(
+ navigateToSignIn = {
+ navController.navigateToSignIn(navOptions { popUpTo(attendanceNavigationRoute) })
+ },
+ navigateToAttendanceManagement = navController::navigateToAttendanceManagement,
+ )
+ attendanceManagementScreen(navigateToAttendance = navController::navigateToAttendance)
+ profileSettingScreen(
+ navigateToSignIn = {
+ navController.navigateToSignIn(
+ navOptions {
+ popUpTo(signInNavigationRoute) { inclusive = true }
+ },
+ )
+ },
+ navigateToProfile = {
+ navController.navigateToProfile(
+ navOptions {
+ popUpTo(profileSettingNavigationRoute) { inclusive = true }
+ },
+ )
+ },
+ )
+ managementScreen(
+ navigateToSurveyRegistration = navController::navigateToSurveyFormRegistration,
+ navigateToEventRegistration = navController::navigateToEventRegistration,
+ navigateToEventEdit = navController::navigateToEventEdit,
+ navigateToSurveyFormEdit = navController::navigateToSurveyFormEdit,
+ navigateToSignIn = navController::navigateToSignIn,
+ )
+ }
+}
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 000000000..ca3826a46
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 000000000..2b068d114
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_management.xml b/app/src/main/res/drawable/ic_management.xml
new file mode 100644
index 000000000..c7f63fc14
--- /dev/null
+++ b/app/src/main/res/drawable/ic_management.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_notice.xml b/app/src/main/res/drawable/ic_notice.xml
new file mode 100644
index 000000000..e9e90a937
--- /dev/null
+++ b/app/src/main/res/drawable/ic_notice.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_profile.xml b/app/src/main/res/drawable/ic_profile.xml
new file mode 100644
index 000000000..b75592523
--- /dev/null
+++ b/app/src/main/res/drawable/ic_profile.xml
@@ -0,0 +1,17 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_survey.xml b/app/src/main/res/drawable/ic_survey.xml
new file mode 100644
index 000000000..c879550e5
--- /dev/null
+++ b/app/src/main/res/drawable/ic_survey.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 000000000..c4a603d4c
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 000000000..c4a603d4c
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 000000000..e060f1c4e
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
new file mode 100644
index 000000000..14899e84c
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..8e82cb0d2
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 000000000..a34418d04
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
new file mode 100644
index 000000000..a3c069e7f
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..244936489
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 000000000..f6cc81192
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
new file mode 100644
index 000000000..7d36d0f35
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..03f2913d5
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 000000000..e053305a8
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
new file mode 100644
index 000000000..d238d0ccc
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..9edf632f9
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 000000000..b71ca9092
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
new file mode 100644
index 000000000..ea4e5e26b
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..79f87fe72
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
new file mode 100644
index 000000000..34844f3a2
--- /dev/null
+++ b/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 000000000..667fddc68
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,7 @@
+
+
+ #FF000000
+ #FFFFFFFF
+ #FFFBCF34
+ #FF252424
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 000000000..2a9197597
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ WAPP
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 000000000..0ed774e88
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 000000000..fa0f996d2
--- /dev/null
+++ b/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 000000000..9ee9997b0
--- /dev/null
+++ b/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/test/java/com/wap/wapp/ExampleUnitTest.kt b/app/src/test/java/com/wap/wapp/ExampleUnitTest.kt
new file mode 100644
index 000000000..c1daaaa99
--- /dev/null
+++ b/app/src/test/java/com/wap/wapp/ExampleUnitTest.kt
@@ -0,0 +1,16 @@
+package com.wap.wapp
+
+import junit.framework.TestCase.assertEquals
+import org.junit.Test
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/build-logic/convention/.gitignore b/build-logic/convention/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/build-logic/convention/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts
new file mode 100644
index 000000000..1234dd2b2
--- /dev/null
+++ b/build-logic/convention/build.gradle.kts
@@ -0,0 +1,52 @@
+plugins{
+ `kotlin-dsl`
+}
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+}
+
+kotlin {
+ jvmToolchain {
+ languageVersion.set(JavaLanguageVersion.of("17"))
+ }
+}
+
+dependencies {
+ compileOnly(libs.android.build)
+ compileOnly(libs.kotlin.gradle)
+}
+
+gradlePlugin {
+ plugins {
+ create("androidApplication") {
+ id = "com.wap.wapp.application"
+ implementationClass = "com.wap.wapp.plugin.AndroidApplicationPlugin"
+ }
+ create("androidLibrary") {
+ id = "com.wap.wapp.library"
+ implementationClass = "com.wap.wapp.plugin.AndroidLibraryPlugin"
+ }
+ create("androidFirebase") {
+ id = "com.wap.wapp.firebase"
+ implementationClass = "com.wap.wapp.plugin.AndroidApplicationFirebasePlugin"
+ }
+ create("androidFeatureConvention") {
+ id = "com.wap.wapp.feature"
+ implementationClass = "com.wap.wapp.plugin.AndroidFeatureConventionPlugin"
+ }
+ create("androidCompose") {
+ id = "com.wap.wapp.compose"
+ implementationClass = "com.wap.wapp.plugin.AndroidComposePlugin"
+ }
+ create("androidHilt") {
+ id = "com.wap.wapp.hilt"
+ implementationClass = "com.wap.wapp.plugin.AndroidHiltPlugin"
+ }
+ create("androidNavigation") {
+ id = "com.wap.wapp.navigation"
+ implementationClass = "com.wap.wapp.plugin.AndroidNavigationPlugin"
+ }
+ }
+}
diff --git a/build-logic/convention/consumer-rules.pro b/build-logic/convention/consumer-rules.pro
new file mode 100644
index 000000000..e69de29bb
diff --git a/build-logic/convention/proguard-rules.pro b/build-logic/convention/proguard-rules.pro
new file mode 100644
index 000000000..481bb4348
--- /dev/null
+++ b/build-logic/convention/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/build-logic/convention/src/main/java/com/wap/wapp/plugin/AndroidApplicationFirebasePlugin.kt b/build-logic/convention/src/main/java/com/wap/wapp/plugin/AndroidApplicationFirebasePlugin.kt
new file mode 100644
index 000000000..9da01a7ef
--- /dev/null
+++ b/build-logic/convention/src/main/java/com/wap/wapp/plugin/AndroidApplicationFirebasePlugin.kt
@@ -0,0 +1,26 @@
+package com.wap.wapp.plugin
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.artifacts.VersionCatalogsExtension
+import org.gradle.kotlin.dsl.dependencies
+import org.gradle.kotlin.dsl.getByType
+
+class AndroidApplicationFirebasePlugin: Plugin {
+ override fun apply(target: Project) {
+ with(target) {
+ with(pluginManager){
+ apply("com.google.gms.google-services")
+ apply("com.google.firebase.crashlytics")
+ }
+
+ val libs = extensions.getByType().named("libs")
+
+ dependencies {
+ "implementation"(platform(libs.findLibrary("firebase-bom").get()))
+ "implementation"(libs.findLibrary("firebase-analytics").get())
+ "implementation"(libs.findLibrary("firebase-crashlytics").get())
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/build-logic/convention/src/main/java/com/wap/wapp/plugin/AndroidApplicationPlugin.kt b/build-logic/convention/src/main/java/com/wap/wapp/plugin/AndroidApplicationPlugin.kt
new file mode 100644
index 000000000..334271feb
--- /dev/null
+++ b/build-logic/convention/src/main/java/com/wap/wapp/plugin/AndroidApplicationPlugin.kt
@@ -0,0 +1,19 @@
+package com.wap.wapp.plugin
+
+import com.wap.wapp.plugin.configure.configureApplicationVersion
+import com.wap.wapp.plugin.configure.configureKotlinAndroid
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+
+class AndroidApplicationPlugin : Plugin {
+ override fun apply(target: Project) {
+ with(target) {
+ with(pluginManager) {
+ apply("com.android.application")
+ apply("org.jetbrains.kotlin.android")
+ }
+ configureKotlinAndroid()
+ configureApplicationVersion()
+ }
+ }
+}
diff --git a/build-logic/convention/src/main/java/com/wap/wapp/plugin/AndroidComposePlugin.kt b/build-logic/convention/src/main/java/com/wap/wapp/plugin/AndroidComposePlugin.kt
new file mode 100644
index 000000000..a5b62798b
--- /dev/null
+++ b/build-logic/convention/src/main/java/com/wap/wapp/plugin/AndroidComposePlugin.kt
@@ -0,0 +1,22 @@
+package com.wap.wapp.plugin
+
+import com.wap.wapp.plugin.configure.configureAndroidCompose
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.artifacts.VersionCatalogsExtension
+import org.gradle.kotlin.dsl.getByType
+import org.gradle.kotlin.dsl.dependencies
+
+class AndroidComposePlugin: Plugin {
+ override fun apply(target: Project) {
+ with(target){
+ val libs = extensions.getByType().named("libs")
+
+ dependencies{
+ "implementation"(libs.findBundle("compose").get())
+ "debugImplementation"(libs.findLibrary("compose-ui-tooling").get())
+ }
+ configureAndroidCompose()
+ }
+ }
+}
\ No newline at end of file
diff --git a/build-logic/convention/src/main/java/com/wap/wapp/plugin/AndroidFeatureConventionPlugin.kt b/build-logic/convention/src/main/java/com/wap/wapp/plugin/AndroidFeatureConventionPlugin.kt
new file mode 100644
index 000000000..f0343fc69
--- /dev/null
+++ b/build-logic/convention/src/main/java/com/wap/wapp/plugin/AndroidFeatureConventionPlugin.kt
@@ -0,0 +1,17 @@
+package com.wap.wapp.plugin
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+
+class AndroidFeatureConventionPlugin: Plugin{
+ override fun apply(target: Project) {
+ with(target){
+ with(pluginManager){
+ apply("com.wap.wapp.library")
+ apply("com.wap.wapp.compose")
+ apply("com.wap.wapp.hilt")
+ apply("com.wap.wapp.navigation")
+ }
+ }
+ }
+}
diff --git a/build-logic/convention/src/main/java/com/wap/wapp/plugin/AndroidHiltPlugin.kt b/build-logic/convention/src/main/java/com/wap/wapp/plugin/AndroidHiltPlugin.kt
new file mode 100644
index 000000000..39aab5d6b
--- /dev/null
+++ b/build-logic/convention/src/main/java/com/wap/wapp/plugin/AndroidHiltPlugin.kt
@@ -0,0 +1,23 @@
+package com.wap.wapp.plugin
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.artifacts.VersionCatalogsExtension
+import org.gradle.kotlin.dsl.dependencies
+import org.gradle.kotlin.dsl.getByType
+
+class AndroidHiltPlugin: Plugin {
+ override fun apply(target: Project) {
+ with(target){
+ pluginManager.apply("com.google.dagger.hilt.android")
+ pluginManager.apply("com.google.devtools.ksp")
+
+ val libs = extensions.getByType().named("libs")
+
+ dependencies {
+ "implementation"(libs.findLibrary("hilt").get())
+ "ksp"(libs.findLibrary("hilt.ksp").get())
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/build-logic/convention/src/main/java/com/wap/wapp/plugin/AndroidLibraryPlugin.kt b/build-logic/convention/src/main/java/com/wap/wapp/plugin/AndroidLibraryPlugin.kt
new file mode 100644
index 000000000..812979f86
--- /dev/null
+++ b/build-logic/convention/src/main/java/com/wap/wapp/plugin/AndroidLibraryPlugin.kt
@@ -0,0 +1,17 @@
+package com.wap.wapp.plugin
+
+import com.wap.wapp.plugin.configure.configureKotlinAndroid
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+
+class AndroidLibraryPlugin : Plugin {
+ override fun apply(target: Project) {
+ with(target) {
+ with(pluginManager) {
+ apply("com.android.library")
+ apply("org.jetbrains.kotlin.android")
+ }
+ configureKotlinAndroid()
+ }
+ }
+}
\ No newline at end of file
diff --git a/build-logic/convention/src/main/java/com/wap/wapp/plugin/AndroidNavigationPlugin.kt b/build-logic/convention/src/main/java/com/wap/wapp/plugin/AndroidNavigationPlugin.kt
new file mode 100644
index 000000000..e201c6e19
--- /dev/null
+++ b/build-logic/convention/src/main/java/com/wap/wapp/plugin/AndroidNavigationPlugin.kt
@@ -0,0 +1,25 @@
+package com.wap.wapp.plugin
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.artifacts.VersionCatalogsExtension
+import org.gradle.kotlin.dsl.dependencies
+import org.gradle.kotlin.dsl.getByType
+
+class AndroidNavigationPlugin: Plugin {
+ override fun apply(target: Project) {
+ with(target) {
+ with(pluginManager) {
+ apply("androidx.navigation.safeargs")
+ }
+
+ val libs = extensions.getByType().named("libs")
+
+ dependencies{
+ "implementation"(libs.findLibrary("androidx-navigation-fragment-ktx").get())
+ "implementation"(libs.findLibrary("androidx-navigation-ui-ktx").get())
+ "implementation"(libs.findLibrary("androidx-navigation-compose").get())
+ }
+ }
+ }
+}
diff --git a/build-logic/convention/src/main/java/com/wap/wapp/plugin/configure/AndroidCompose.kt b/build-logic/convention/src/main/java/com/wap/wapp/plugin/configure/AndroidCompose.kt
new file mode 100644
index 000000000..69d5c4912
--- /dev/null
+++ b/build-logic/convention/src/main/java/com/wap/wapp/plugin/configure/AndroidCompose.kt
@@ -0,0 +1,17 @@
+package com.wap.wapp.plugin.configure
+
+import com.android.build.gradle.BaseExtension
+import org.gradle.api.Project
+import org.gradle.api.artifacts.VersionCatalogsExtension
+import org.gradle.kotlin.dsl.getByType
+
+internal fun Project.configureAndroidCompose(){
+ val libs = extensions.getByType().named("libs")
+
+ extensions.getByType().apply {
+ buildFeatures.compose = true
+
+ composeOptions.kotlinCompilerExtensionVersion =
+ libs.findVersion("compose-compiler").get().toString()
+ }
+}
diff --git a/build-logic/convention/src/main/java/com/wap/wapp/plugin/configure/AndroidKotlin.kt b/build-logic/convention/src/main/java/com/wap/wapp/plugin/configure/AndroidKotlin.kt
new file mode 100644
index 000000000..718f088c5
--- /dev/null
+++ b/build-logic/convention/src/main/java/com/wap/wapp/plugin/configure/AndroidKotlin.kt
@@ -0,0 +1,32 @@
+package com.wap.wapp.plugin.configure
+
+import com.android.build.gradle.BaseExtension
+import org.gradle.api.JavaVersion
+import org.gradle.api.Project
+import org.gradle.api.artifacts.VersionCatalogsExtension
+import org.gradle.api.plugins.ExtensionAware
+import org.gradle.kotlin.dsl.configure
+import org.gradle.kotlin.dsl.getByType
+import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions
+
+internal fun Project.configureKotlinAndroid(){
+ val libs = extensions.getByType().named("libs")
+
+ extensions.getByType().apply {
+ setCompileSdkVersion(libs.findVersion("compileSdk").get().requiredVersion.toInt())
+
+ defaultConfig {
+ minSdk = libs.findVersion("minSdk").get().requiredVersion.toInt()
+ targetSdk = libs.findVersion("targetSdk").get().requiredVersion.toInt()
+ }
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+
+ (this as ExtensionAware).configure {
+ jvmTarget = "17"
+ }
+ }
+}
\ No newline at end of file
diff --git a/build-logic/convention/src/main/java/com/wap/wapp/plugin/configure/AndroidVersion.kt b/build-logic/convention/src/main/java/com/wap/wapp/plugin/configure/AndroidVersion.kt
new file mode 100644
index 000000000..5f83d1cf3
--- /dev/null
+++ b/build-logic/convention/src/main/java/com/wap/wapp/plugin/configure/AndroidVersion.kt
@@ -0,0 +1,17 @@
+package com.wap.wapp.plugin.configure
+
+import com.android.build.gradle.BaseExtension
+import org.gradle.api.Project
+import org.gradle.api.artifacts.VersionCatalogsExtension
+import org.gradle.kotlin.dsl.getByType
+
+internal fun Project.configureApplicationVersion() {
+ val libs = extensions.getByType().named("libs")
+
+ extensions.getByType().apply {
+ defaultConfig {
+ versionCode = libs.findVersion("versionCode").get().requiredVersion.toInt()
+ versionName = libs.findVersion("versionName").get().requiredVersion
+ }
+ }
+}
diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts
new file mode 100644
index 000000000..2907fbfb8
--- /dev/null
+++ b/build-logic/settings.gradle.kts
@@ -0,0 +1,14 @@
+dependencyResolutionManagement {
+ repositories {
+ google()
+ mavenCentral()
+ }
+ versionCatalogs {
+ create("libs") {
+ from(files("../gradle/libs.versions.toml"))
+ }
+ }
+}
+
+rootProject.name = "build-logic"
+include(":convention")
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 000000000..87f258520
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,41 @@
+buildscript {
+ repositories {
+ google()
+ mavenCentral()
+ }
+ dependencies {
+ classpath(libs.android.build)
+ classpath(libs.kotlin.gradle)
+ classpath(libs.androidx.navigation.safeargs)
+ classpath(libs.hilt.gradle)
+ classpath(libs.google.services.gradle)
+ classpath(libs.firebase.crashlytics.gradle)
+ }
+}
+
+plugins {
+ alias(libs.plugins.android.application) apply false
+ alias(libs.plugins.android.library) apply false
+ alias(libs.plugins.kotlin.android) apply false
+ alias(libs.plugins.androidx.navigation.safeargs) apply false
+ alias(libs.plugins.dagger.hilt) apply false
+ alias(libs.plugins.google.services) apply false
+ alias(libs.plugins.firebase.crashlytics) apply false
+ alias(libs.plugins.ksp) apply false
+ alias(libs.plugins.ktlint)
+}
+
+allprojects {
+ apply {
+ plugin(rootProject.libs.plugins.ktlint.get().pluginId)
+ }
+
+ ktlint {
+ android.set(true)
+ verbose.set(true)
+ outputToConsole.set(true)
+ reporters {
+ reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.PLAIN)
+ }
+ }
+}
diff --git a/core/build/intermediates/ktLint/reporterProviders.bin b/core/build/intermediates/ktLint/reporterProviders.bin
new file mode 100644
index 000000000..11e8bd367
Binary files /dev/null and b/core/build/intermediates/ktLint/reporterProviders.bin differ
diff --git a/core/build/intermediates/ktLint/reporters.bin b/core/build/intermediates/ktLint/reporters.bin
new file mode 100644
index 000000000..d5a0d6164
Binary files /dev/null and b/core/build/intermediates/ktLint/reporters.bin differ
diff --git a/core/common/.gitignore b/core/common/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/core/common/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts
new file mode 100644
index 000000000..b0380e5f1
--- /dev/null
+++ b/core/common/build.gradle.kts
@@ -0,0 +1,34 @@
+plugins {
+ id("com.wap.wapp.feature")
+}
+
+android {
+ namespace = "com.wap.wapp.core.base"
+
+ defaultConfig {
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro",
+ )
+ }
+ }
+}
+
+dependencies {
+ implementation(libs.bundles.androidx)
+ implementation(platform(libs.firebase.bom))
+ implementation(libs.firebase.auth)
+ implementation(libs.firebase.firestore)
+ implementation(libs.material)
+
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.test.junit)
+ androidTestImplementation(libs.androidx.test.espresso)
+}
diff --git a/core/common/consumer-rules.pro b/core/common/consumer-rules.pro
new file mode 100644
index 000000000..e69de29bb
diff --git a/core/common/proguard-rules.pro b/core/common/proguard-rules.pro
new file mode 100644
index 000000000..481bb4348
--- /dev/null
+++ b/core/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/core/common/src/androidTest/java/com/wap/wapp/core/base/ExampleInstrumentedTest.kt b/core/common/src/androidTest/java/com/wap/wapp/core/base/ExampleInstrumentedTest.kt
new file mode 100644
index 000000000..358e2475e
--- /dev/null
+++ b/core/common/src/androidTest/java/com/wap/wapp/core/base/ExampleInstrumentedTest.kt
@@ -0,0 +1,22 @@
+package com.wap.wapp.core.base
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.wap.wapp.core.base.test", appContext.packageName)
+ }
+}
diff --git a/core/common/src/main/AndroidManifest.xml b/core/common/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..a5918e68a
--- /dev/null
+++ b/core/common/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/core/common/src/main/java/com/wap/wapp/core/commmon/extensions/ContextExtensions.kt b/core/common/src/main/java/com/wap/wapp/core/commmon/extensions/ContextExtensions.kt
new file mode 100644
index 000000000..e2c81e41f
--- /dev/null
+++ b/core/common/src/main/java/com/wap/wapp/core/commmon/extensions/ContextExtensions.kt
@@ -0,0 +1,12 @@
+package com.wap.wapp.core.commmon.extensions
+
+import android.app.Activity
+import android.widget.Toast
+
+fun Activity.showToast(message: String) {
+ Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
+}
+
+fun Activity.showLongToast(message: String) {
+ Toast.makeText(this, message, Toast.LENGTH_LONG).show()
+}
diff --git a/core/common/src/main/java/com/wap/wapp/core/commmon/extensions/CoroutinesExtension.kt b/core/common/src/main/java/com/wap/wapp/core/commmon/extensions/CoroutinesExtension.kt
new file mode 100644
index 000000000..0d79ada7f
--- /dev/null
+++ b/core/common/src/main/java/com/wap/wapp/core/commmon/extensions/CoroutinesExtension.kt
@@ -0,0 +1,14 @@
+package com.wap.wapp.core.commmon.extensions
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+fun LifecycleOwner.repeatOnStarted(block: suspend CoroutineScope.() -> Unit) {
+ lifecycleScope.launch {
+ lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED, block)
+ }
+}
diff --git a/core/common/src/main/java/com/wap/wapp/core/commmon/extensions/ThrowableExtensions.kt b/core/common/src/main/java/com/wap/wapp/core/commmon/extensions/ThrowableExtensions.kt
new file mode 100644
index 000000000..be1606f21
--- /dev/null
+++ b/core/common/src/main/java/com/wap/wapp/core/commmon/extensions/ThrowableExtensions.kt
@@ -0,0 +1,37 @@
+package com.wap.wapp.core.commmon.extensions
+
+import com.google.firebase.auth.FirebaseAuthException
+import com.google.firebase.firestore.FirebaseFirestoreException
+import java.net.UnknownHostException
+
+fun Throwable.toSupportingText(): String {
+ return when (this) {
+ is UnknownHostException -> "๋คํธ์ํฌ ์ฐ๊ฒฐ์ด ์ํํ์ง ์์ต๋๋ค."
+
+ is FirebaseAuthException -> this.toSupportingText()
+
+ is FirebaseFirestoreException -> this.toSupportingText()
+
+ is IllegalStateException -> this.message.toString()
+
+ else -> "์ ์ ์๋ ์ค๋ฅ๊ฐ ๋ฐ์ํ์์ต๋๋ค."
+ }
+}
+
+fun FirebaseAuthException.toSupportingText(): String {
+ return when (this.errorCode) {
+ "ERROR_WEB_CONTEXT_CANCELED", "ERROR_USER_CANCELLED" -> "๋ค์ ์๋ํด ์ฃผ์ธ์."
+
+ else -> "์ ์ ์๋ ์ค๋ฅ๊ฐ ๋ฐ์ํ์์ต๋๋ค."
+ }
+}
+
+fun FirebaseFirestoreException.toSupportingText(): String {
+ return when (this.code.value()) {
+ 7 -> "์ ๊ทผ ๊ถํ์ด ์์ต๋๋ค."
+
+ 16 -> "ํ์์ด ๋ง๋ฃ๋์์ต๋๋ค. ๋ค์ ๋ก๊ทธ์ธ ํด์ฃผ์ธ์."
+
+ else -> "์ ์ ์๋ ์ค๋ฅ๊ฐ ๋ฐ์ํ์์ต๋๋ค."
+ }
+}
diff --git a/core/common/src/main/java/com/wap/wapp/core/commmon/util/DateUtil.kt b/core/common/src/main/java/com/wap/wapp/core/commmon/util/DateUtil.kt
new file mode 100644
index 000000000..0860bf954
--- /dev/null
+++ b/core/common/src/main/java/com/wap/wapp/core/commmon/util/DateUtil.kt
@@ -0,0 +1,44 @@
+package com.wap.wapp.core.commmon.util
+
+import java.time.LocalDate
+import java.time.LocalDateTime
+import java.time.LocalTime
+import java.time.ZoneId
+import java.time.format.DateTimeFormatter
+
+object DateUtil {
+
+ fun generateNowTime(zoneId: ZoneId = ZoneId.of("Asia/Seoul")): LocalTime = LocalTime.now(zoneId)
+
+ fun generateNowDate(zoneId: ZoneId = ZoneId.of("Asia/Seoul")): LocalDate = LocalDate.now(zoneId)
+
+ fun generateNowDateTime(zoneId: ZoneId = ZoneId.of("Asia/Seoul")): LocalDateTime =
+ LocalDateTime.now(zoneId)
+
+ const val YEAR_MONTH_START_INDEX = 0
+ const val YEAR_MONTH_END_INDEX = 7
+ const val MONTH_DATE_START_INDEX = 5
+ const val DAYS_IN_WEEK = 7
+
+ // ํ์ฌ ๋ ์ง์์ ์๊ฐ, ๋ถ๋ง ๋ฐํํด์ฃผ๋ ํฌ๋งท ex 19:00
+ val HHmmFormatter = DateTimeFormatter.ofPattern("HH:mm")
+
+ // ํ์ฌ ๋ ์ง์์ ์ผ๋ง ๋ฐํํด์ฃผ๋ ํฌ๋งท ex 2023-11-20 -> 20
+ val ddFormatter = DateTimeFormatter.ofPattern("dd")
+
+ // ํ์ฌ ๋ ์ง๋ฅผ ๋
-์-์ผ ํ์์ผ๋ก ๋ฐํํด์ฃผ๋ ํฌ๋งท. ex 2023-11-20
+ val yyyyMMddFormatter = DateTimeFormatter.ofPattern("yyyy.MM.dd")
+
+ // ํ์ฌ ๋ ์ง๋ฅผ ์-์ผ ํ์์ผ๋ก ๋ฐํํด์ฃผ๋ ํฌ๋งท. ex 11์ 20์ผ
+ val MMddFormatter = DateTimeFormatter.ofPattern("MM์ dd์ผ")
+
+ enum class DaysOfWeek(val displayName: String) {
+ SUNDAY("์ผ"),
+ MONDAY("์"),
+ TUESDAY("ํ"),
+ WEDNESDAY("์"),
+ THURSDAY("๋ชฉ"),
+ FRIDAY("๊ธ"),
+ SATURDAY("ํ "),
+ }
+}
diff --git a/core/common/src/test/java/com/wap/wapp/core/base/ExampleUnitTest.kt b/core/common/src/test/java/com/wap/wapp/core/base/ExampleUnitTest.kt
new file mode 100644
index 000000000..4e26d482f
--- /dev/null
+++ b/core/common/src/test/java/com/wap/wapp/core/base/ExampleUnitTest.kt
@@ -0,0 +1,16 @@
+package com.wap.wapp.core.base
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/core/data/.gitignore b/core/data/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/core/data/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core/data/build.gradle.kts b/core/data/build.gradle.kts
new file mode 100644
index 000000000..36e452b4e
--- /dev/null
+++ b/core/data/build.gradle.kts
@@ -0,0 +1,31 @@
+plugins {
+ id("com.wap.wapp.library")
+ id("com.wap.wapp.hilt")
+}
+
+android {
+ namespace = "com.wap.wapp.core.data"
+
+ defaultConfig {
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro",
+ )
+ }
+ }
+}
+
+dependencies {
+ implementation(project(":core:model"))
+ implementation(project(":core:network"))
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.test.junit)
+ androidTestImplementation(libs.androidx.test.espresso)
+}
diff --git a/core/data/consumer-rules.pro b/core/data/consumer-rules.pro
new file mode 100644
index 000000000..e69de29bb
diff --git a/core/data/proguard-rules.pro b/core/data/proguard-rules.pro
new file mode 100644
index 000000000..481bb4348
--- /dev/null
+++ b/core/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/core/data/src/androidTest/java/com/wap/wapp/core/data/ExampleInstrumentedTest.kt b/core/data/src/androidTest/java/com/wap/wapp/core/data/ExampleInstrumentedTest.kt
new file mode 100644
index 000000000..3b7ac9386
--- /dev/null
+++ b/core/data/src/androidTest/java/com/wap/wapp/core/data/ExampleInstrumentedTest.kt
@@ -0,0 +1,22 @@
+package com.wap.wapp.core.data
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.wap.wapp.core.data.test", appContext.packageName)
+ }
+}
diff --git a/core/data/src/main/java/com/wap/wapp/core/data/di/DataModule.kt b/core/data/src/main/java/com/wap/wapp/core/data/di/DataModule.kt
new file mode 100644
index 000000000..cdd681ece
--- /dev/null
+++ b/core/data/src/main/java/com/wap/wapp/core/data/di/DataModule.kt
@@ -0,0 +1,75 @@
+package com.wap.wapp.core.data.di
+
+import com.wap.wapp.core.data.repository.attendance.AttendanceRepository
+import com.wap.wapp.core.data.repository.attendance.AttendanceRepositoryImpl
+import com.wap.wapp.core.data.repository.attendancestatus.AttendanceStatusRepository
+import com.wap.wapp.core.data.repository.attendancestatus.AttendanceStatusRepositoryImpl
+import com.wap.wapp.core.data.repository.auth.AuthRepository
+import com.wap.wapp.core.data.repository.auth.AuthRepositoryImpl
+import com.wap.wapp.core.data.repository.event.EventRepository
+import com.wap.wapp.core.data.repository.event.EventRepositoryImpl
+import com.wap.wapp.core.data.repository.management.ManagementRepository
+import com.wap.wapp.core.data.repository.management.ManagementRepositoryImpl
+import com.wap.wapp.core.data.repository.survey.SurveyFormRepository
+import com.wap.wapp.core.data.repository.survey.SurveyFormRepositoryImpl
+import com.wap.wapp.core.data.repository.survey.SurveyRepository
+import com.wap.wapp.core.data.repository.survey.SurveyRepositoryImpl
+import com.wap.wapp.core.data.repository.user.UserRepository
+import com.wap.wapp.core.data.repository.user.UserRepositoryImpl
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+abstract class DataModule {
+ @Binds
+ @Singleton
+ abstract fun bindsAuthRepository(
+ authRepositoryImpl: AuthRepositoryImpl,
+ ): AuthRepository
+
+ @Binds
+ @Singleton
+ abstract fun bindsUserRepository(
+ userRepositoryImpl: UserRepositoryImpl,
+ ): UserRepository
+
+ @Binds
+ @Singleton
+ abstract fun bindsManagementRepository(
+ managementRepositoryImpl: ManagementRepositoryImpl,
+ ): ManagementRepository
+
+ @Binds
+ @Singleton
+ abstract fun bindsSurveyRepository(
+ surveyRepositoryImpl: SurveyRepositoryImpl,
+ ): SurveyRepository
+
+ @Binds
+ @Singleton
+ abstract fun bindsSurveyFormRepository(
+ surveyFormRepositoryImpl: SurveyFormRepositoryImpl,
+ ): SurveyFormRepository
+
+ @Binds
+ @Singleton
+ abstract fun bindsEventRepository(
+ eventRepositoryImpl: EventRepositoryImpl,
+ ): EventRepository
+
+ @Binds
+ @Singleton
+ abstract fun bindsAttendanceRepository(
+ attendanceRepositoryImpl: AttendanceRepositoryImpl,
+ ): AttendanceRepository
+
+ @Binds
+ @Singleton
+ abstract fun bindsAttendanceStatusRepository(
+ attendanceStatusRepositoryImpl: AttendanceStatusRepositoryImpl,
+ ): AttendanceStatusRepository
+}
diff --git a/core/data/src/main/java/com/wap/wapp/core/data/di/SignInRepositoryModule.kt b/core/data/src/main/java/com/wap/wapp/core/data/di/SignInRepositoryModule.kt
new file mode 100644
index 000000000..ae47ce820
--- /dev/null
+++ b/core/data/src/main/java/com/wap/wapp/core/data/di/SignInRepositoryModule.kt
@@ -0,0 +1,19 @@
+package com.wap.wapp.core.data.di
+
+import com.wap.wapp.core.data.repository.auth.SignInRepository
+import com.wap.wapp.core.data.repository.auth.SignInRepositoryImpl
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.android.components.ActivityComponent
+import dagger.hilt.android.scopes.ActivityScoped
+
+@Module
+@InstallIn(ActivityComponent::class)
+abstract class SignInRepositoryModule {
+ @Binds
+ @ActivityScoped
+ abstract fun bindsAuthRepository(
+ signInRepositoryImpl: SignInRepositoryImpl,
+ ): SignInRepository
+}
diff --git a/core/data/src/main/java/com/wap/wapp/core/data/repository/attendance/AttendanceRepository.kt b/core/data/src/main/java/com/wap/wapp/core/data/repository/attendance/AttendanceRepository.kt
new file mode 100644
index 000000000..4fdcb48b6
--- /dev/null
+++ b/core/data/src/main/java/com/wap/wapp/core/data/repository/attendance/AttendanceRepository.kt
@@ -0,0 +1,14 @@
+package com.wap.wapp.core.data.repository.attendance
+
+import com.wap.wapp.core.model.attendance.Attendance
+import java.time.LocalDateTime
+
+interface AttendanceRepository {
+ suspend fun getAttendance(eventId: String): Result
+
+ suspend fun postAttendance(
+ eventId: String,
+ code: String,
+ deadline: LocalDateTime,
+ ): Result
+}
diff --git a/core/data/src/main/java/com/wap/wapp/core/data/repository/attendance/AttendanceRepositoryImpl.kt b/core/data/src/main/java/com/wap/wapp/core/data/repository/attendance/AttendanceRepositoryImpl.kt
new file mode 100644
index 000000000..c087b4d9c
--- /dev/null
+++ b/core/data/src/main/java/com/wap/wapp/core/data/repository/attendance/AttendanceRepositoryImpl.kt
@@ -0,0 +1,27 @@
+package com.wap.wapp.core.data.repository.attendance
+
+import com.wap.wapp.core.data.utils.toISOLocalDateTimeString
+import com.wap.wapp.core.model.attendance.Attendance
+import com.wap.wapp.core.network.source.attendance.AttendanceDataSource
+import java.time.LocalDateTime
+import javax.inject.Inject
+
+class AttendanceRepositoryImpl @Inject constructor(
+ private val attendanceDataSource: AttendanceDataSource,
+) : AttendanceRepository {
+ override suspend fun getAttendance(eventId: String): Result =
+ attendanceDataSource.getAttendance(eventId).mapCatching { attendanceResponse ->
+ attendanceResponse.toDomain()
+ }
+
+ override suspend fun postAttendance(
+ eventId: String,
+ code: String,
+ deadline: LocalDateTime,
+ ): Result =
+ attendanceDataSource.postAttendance(
+ eventId = eventId,
+ code = code,
+ deadline = deadline.toISOLocalDateTimeString(),
+ )
+}
diff --git a/core/data/src/main/java/com/wap/wapp/core/data/repository/attendancestatus/AttendanceStatusRepository.kt b/core/data/src/main/java/com/wap/wapp/core/data/repository/attendancestatus/AttendanceStatusRepository.kt
new file mode 100644
index 000000000..1c48c6fe2
--- /dev/null
+++ b/core/data/src/main/java/com/wap/wapp/core/data/repository/attendancestatus/AttendanceStatusRepository.kt
@@ -0,0 +1,12 @@
+package com.wap.wapp.core.data.repository.attendancestatus
+
+import com.wap.wapp.core.model.attendancestatus.AttendanceStatus
+
+interface AttendanceStatusRepository {
+ suspend fun getAttendanceStatus(eventId: String, userId: String): Result
+
+ suspend fun postAttendance(
+ eventId: String,
+ userId: String,
+ ): Result
+}
diff --git a/core/data/src/main/java/com/wap/wapp/core/data/repository/attendancestatus/AttendanceStatusRepositoryImpl.kt b/core/data/src/main/java/com/wap/wapp/core/data/repository/attendancestatus/AttendanceStatusRepositoryImpl.kt
new file mode 100644
index 000000000..4050ca10c
--- /dev/null
+++ b/core/data/src/main/java/com/wap/wapp/core/data/repository/attendancestatus/AttendanceStatusRepositoryImpl.kt
@@ -0,0 +1,20 @@
+package com.wap.wapp.core.data.repository.attendancestatus
+
+import com.wap.wapp.core.model.attendancestatus.AttendanceStatus
+import com.wap.wapp.core.network.source.attendancestatus.AttendanceStatusDataSource
+import javax.inject.Inject
+
+class AttendanceStatusRepositoryImpl @Inject constructor(
+ private val attendanceStatusDataSource: AttendanceStatusDataSource,
+) : AttendanceStatusRepository {
+ override suspend fun getAttendanceStatus(
+ eventId: String,
+ userId: String,
+ ): Result = attendanceStatusDataSource.getAttendanceStatus(
+ eventId = eventId,
+ userId = userId,
+ ).mapCatching { it.toDomain() }
+
+ override suspend fun postAttendance(eventId: String, userId: String): Result =
+ attendanceStatusDataSource.postAttendanceStatus(eventId = eventId, userId = userId)
+}
diff --git a/core/data/src/main/java/com/wap/wapp/core/data/repository/auth/AuthRepository.kt b/core/data/src/main/java/com/wap/wapp/core/data/repository/auth/AuthRepository.kt
new file mode 100644
index 000000000..ac988f856
--- /dev/null
+++ b/core/data/src/main/java/com/wap/wapp/core/data/repository/auth/AuthRepository.kt
@@ -0,0 +1,11 @@
+package com.wap.wapp.core.data.repository.auth
+
+interface AuthRepository {
+ suspend fun signOut(): Result
+
+ suspend fun deleteUser(): Result
+
+ suspend fun isUserSignIn(): Result
+
+ suspend fun checkMemberCode(code: String): Result
+}
diff --git a/core/data/src/main/java/com/wap/wapp/core/data/repository/auth/AuthRepositoryImpl.kt b/core/data/src/main/java/com/wap/wapp/core/data/repository/auth/AuthRepositoryImpl.kt
new file mode 100644
index 000000000..d79a21bab
--- /dev/null
+++ b/core/data/src/main/java/com/wap/wapp/core/data/repository/auth/AuthRepositoryImpl.kt
@@ -0,0 +1,17 @@
+package com.wap.wapp.core.data.repository.auth
+
+import com.wap.wapp.core.network.source.auth.AuthDataSource
+import javax.inject.Inject
+
+class AuthRepositoryImpl @Inject constructor(
+ private val authDataSource: AuthDataSource,
+) : AuthRepository {
+ override suspend fun signOut(): Result = authDataSource.signOut()
+
+ override suspend fun deleteUser(): Result = authDataSource.deleteUser()
+
+ override suspend fun isUserSignIn(): Result = authDataSource.isUserSignIn()
+
+ override suspend fun checkMemberCode(code: String): Result =
+ authDataSource.checkMemberCode(code)
+}
diff --git a/core/data/src/main/java/com/wap/wapp/core/data/repository/auth/SignInRepository.kt b/core/data/src/main/java/com/wap/wapp/core/data/repository/auth/SignInRepository.kt
new file mode 100644
index 000000000..fc4418bca
--- /dev/null
+++ b/core/data/src/main/java/com/wap/wapp/core/data/repository/auth/SignInRepository.kt
@@ -0,0 +1,5 @@
+package com.wap.wapp.core.data.repository.auth
+
+interface SignInRepository {
+ suspend fun signIn(email: String): Result
+}
diff --git a/core/data/src/main/java/com/wap/wapp/core/data/repository/auth/SignInRepositoryImpl.kt b/core/data/src/main/java/com/wap/wapp/core/data/repository/auth/SignInRepositoryImpl.kt
new file mode 100644
index 000000000..aa3157116
--- /dev/null
+++ b/core/data/src/main/java/com/wap/wapp/core/data/repository/auth/SignInRepositoryImpl.kt
@@ -0,0 +1,10 @@
+package com.wap.wapp.core.data.repository.auth
+
+import com.wap.wapp.core.network.source.auth.SignInDataSource
+import javax.inject.Inject
+
+class SignInRepositoryImpl @Inject constructor(
+ private val signInDataSource: SignInDataSource,
+) : SignInRepository {
+ override suspend fun signIn(email: String): Result = signInDataSource.signIn(email)
+}
diff --git a/core/data/src/main/java/com/wap/wapp/core/data/repository/event/EventRepository.kt b/core/data/src/main/java/com/wap/wapp/core/data/repository/event/EventRepository.kt
new file mode 100644
index 000000000..0ac05a60b
--- /dev/null
+++ b/core/data/src/main/java/com/wap/wapp/core/data/repository/event/EventRepository.kt
@@ -0,0 +1,36 @@
+package com.wap.wapp.core.data.repository.event
+
+import com.wap.wapp.core.model.event.Event
+import java.time.LocalDate
+import java.time.LocalDateTime
+
+interface EventRepository {
+ suspend fun getMonthEventList(date: LocalDate): Result>
+
+ suspend fun getDateEventList(date: LocalDate): Result>
+
+ suspend fun getEventListFromDate(date: LocalDate): Result>
+
+ suspend fun getEventList(): Result>
+
+ suspend fun getEvent(eventId: String): Result
+
+ suspend fun deleteEvent(eventId: String): Result
+
+ suspend fun postEvent(
+ title: String,
+ content: String,
+ location: String,
+ startDateTime: LocalDateTime,
+ endDateTime: LocalDateTime,
+ ): Result
+
+ suspend fun updateEvent(
+ eventId: String,
+ title: String,
+ content: String,
+ location: String,
+ startDateTime: LocalDateTime,
+ endDateTime: LocalDateTime,
+ ): Result
+}
diff --git a/core/data/src/main/java/com/wap/wapp/core/data/repository/event/EventRepositoryImpl.kt b/core/data/src/main/java/com/wap/wapp/core/data/repository/event/EventRepositoryImpl.kt
new file mode 100644
index 000000000..0c05b2513
--- /dev/null
+++ b/core/data/src/main/java/com/wap/wapp/core/data/repository/event/EventRepositoryImpl.kt
@@ -0,0 +1,78 @@
+package com.wap.wapp.core.data.repository.event
+
+import com.wap.wapp.core.data.utils.toISOLocalDateTimeString
+import com.wap.wapp.core.model.event.Event
+import com.wap.wapp.core.network.source.event.EventDataSource
+import java.time.LocalDate
+import java.time.LocalDateTime
+import javax.inject.Inject
+
+class EventRepositoryImpl @Inject constructor(
+ private val eventDataSource: EventDataSource,
+) : EventRepository {
+ override suspend fun getMonthEventList(date: LocalDate): Result> =
+ eventDataSource.getMonthEventList(date).mapCatching { eventResponses ->
+ eventResponses.map { eventResponse ->
+ eventResponse.toDomain()
+ }.sortedBy { it.startDateTime }
+ }
+
+ override suspend fun getEventList(): Result> =
+ eventDataSource.getEventList().mapCatching { eventResponses ->
+ eventResponses.map { eventResponse ->
+ eventResponse.toDomain()
+ }.sortedBy { it.startDateTime }
+ }
+
+ override suspend fun getDateEventList(date: LocalDate): Result> =
+ eventDataSource.getDateEventList(date).mapCatching { eventResponses ->
+ eventResponses.map { eventResponse ->
+ eventResponse.toDomain()
+ }.sortedBy { it.startDateTime }
+ }
+
+ override suspend fun getEventListFromDate(date: LocalDate): Result> =
+ eventDataSource.getEventListFromDate(date).mapCatching { eventResponses ->
+ eventResponses.map { eventResponse ->
+ eventResponse.toDomain()
+ }.sortedBy { it.startDateTime }
+ }
+
+ override suspend fun getEvent(eventId: String): Result =
+ eventDataSource.getEvent(eventId).mapCatching { eventResponse -> eventResponse.toDomain() }
+
+ override suspend fun deleteEvent(eventId: String): Result =
+ eventDataSource.deleteEvent(eventId)
+
+ override suspend fun postEvent(
+ title: String,
+ content: String,
+ location: String,
+ startDateTime: LocalDateTime,
+ endDateTime: LocalDateTime,
+ ): Result =
+ eventDataSource.postEvent(
+ title = title,
+ content = content,
+ location = location,
+ startDateTime = startDateTime.toISOLocalDateTimeString(),
+ endDateTime = endDateTime.toISOLocalDateTimeString(),
+ )
+
+ override suspend fun updateEvent(
+ eventId: String,
+ title: String,
+ content: String,
+ location: String,
+ startDateTime: LocalDateTime,
+ endDateTime: LocalDateTime,
+ ): Result =
+ eventDataSource.updateEvent(
+ eventId = eventId,
+ title = title,
+ content = content,
+ location = location,
+ startDateTime = startDateTime.toISOLocalDateTimeString(),
+ endDateTime = endDateTime.toISOLocalDateTimeString(),
+ )
+}
diff --git a/core/data/src/main/java/com/wap/wapp/core/data/repository/management/ManagementRepository.kt b/core/data/src/main/java/com/wap/wapp/core/data/repository/management/ManagementRepository.kt
new file mode 100644
index 000000000..8c6141546
--- /dev/null
+++ b/core/data/src/main/java/com/wap/wapp/core/data/repository/management/ManagementRepository.kt
@@ -0,0 +1,11 @@
+package com.wap.wapp.core.data.repository.management
+
+interface ManagementRepository {
+ suspend fun isManager(userId: String): Result
+
+ suspend fun postManager(userId: String): Result
+
+ suspend fun checkManagementCode(code: String): Result
+
+ suspend fun deleteManager(userId: String): Result
+}
diff --git a/core/data/src/main/java/com/wap/wapp/core/data/repository/management/ManagementRepositoryImpl.kt b/core/data/src/main/java/com/wap/wapp/core/data/repository/management/ManagementRepositoryImpl.kt
new file mode 100644
index 000000000..440ae1566
--- /dev/null
+++ b/core/data/src/main/java/com/wap/wapp/core/data/repository/management/ManagementRepositoryImpl.kt
@@ -0,0 +1,20 @@
+package com.wap.wapp.core.data.repository.management
+
+import com.wap.wapp.core.network.source.management.ManagementDataSource
+import javax.inject.Inject
+
+class ManagementRepositoryImpl @Inject constructor(
+ private val managementDataSource: ManagementDataSource,
+) : ManagementRepository {
+ override suspend fun isManager(userId: String): Result =
+ managementDataSource.isManager(userId)
+
+ override suspend fun postManager(userId: String): Result =
+ managementDataSource.postManager(userId)
+
+ override suspend fun checkManagementCode(code: String): Result =
+ managementDataSource.checkManagementCode(code)
+
+ override suspend fun deleteManager(userId: String): Result =
+ managementDataSource.deleteManager(userId)
+}
diff --git a/core/data/src/main/java/com/wap/wapp/core/data/repository/survey/SurveyFormRepository.kt b/core/data/src/main/java/com/wap/wapp/core/data/repository/survey/SurveyFormRepository.kt
new file mode 100644
index 000000000..44bd85301
--- /dev/null
+++ b/core/data/src/main/java/com/wap/wapp/core/data/repository/survey/SurveyFormRepository.kt
@@ -0,0 +1,25 @@
+package com.wap.wapp.core.data.repository.survey
+
+import com.wap.wapp.core.model.survey.SurveyForm
+import com.wap.wapp.core.model.survey.SurveyQuestion
+import java.time.LocalDateTime
+
+interface SurveyFormRepository {
+ suspend fun getSurveyForm(surveyFormId: String): Result
+
+ suspend fun getSurveyFormList(): Result>
+
+ suspend fun getSurveyFormListByEventId(eventId: String): Result>
+
+ suspend fun deleteSurveyForm(surveyFormId: String): Result
+
+ suspend fun postSurveyForm(
+ eventId: String,
+ title: String,
+ content: String,
+ surveyQuestionList: List,
+ deadline: LocalDateTime,
+ ): Result
+
+ suspend fun updateSurveyForm(surveyForm: SurveyForm): Result
+}
diff --git a/core/data/src/main/java/com/wap/wapp/core/data/repository/survey/SurveyFormRepositoryImpl.kt b/core/data/src/main/java/com/wap/wapp/core/data/repository/survey/SurveyFormRepositoryImpl.kt
new file mode 100644
index 000000000..0f11583a6
--- /dev/null
+++ b/core/data/src/main/java/com/wap/wapp/core/data/repository/survey/SurveyFormRepositoryImpl.kt
@@ -0,0 +1,62 @@
+package com.wap.wapp.core.data.repository.survey
+
+import com.wap.wapp.core.data.utils.toISOLocalDateTimeString
+import com.wap.wapp.core.model.survey.SurveyForm
+import com.wap.wapp.core.model.survey.SurveyQuestion
+import com.wap.wapp.core.network.model.survey.form.SurveyFormRequest
+import com.wap.wapp.core.network.source.survey.SurveyFormDataSource
+import java.time.LocalDateTime
+import javax.inject.Inject
+
+class SurveyFormRepositoryImpl @Inject constructor(
+ private val surveyFormDataSource: SurveyFormDataSource,
+) : SurveyFormRepository {
+ override suspend fun getSurveyForm(surveyFormId: String): Result =
+ surveyFormDataSource.getSurveyForm(surveyFormId).mapCatching { surveyFormResponse ->
+ surveyFormResponse.toDomain()
+ }
+
+ override suspend fun getSurveyFormList(): Result> =
+ surveyFormDataSource.getSurveyFormList().mapCatching { surveyFormResponseList ->
+ surveyFormResponseList.map { surveyFormResponse ->
+ surveyFormResponse.toDomain()
+ }
+ }
+
+ override suspend fun deleteSurveyForm(surveyFormId: String): Result =
+ surveyFormDataSource.deleteSurveyForm(surveyFormId)
+
+ override suspend fun getSurveyFormListByEventId(eventId: String): Result> =
+ surveyFormDataSource.getSurveyFormListByEventId(eventId)
+ .mapCatching { surveyFormResponseList ->
+ surveyFormResponseList.map { surveyFormResponse ->
+ surveyFormResponse.toDomain()
+ }
+ }
+
+ override suspend fun postSurveyForm(
+ eventId: String,
+ title: String,
+ content: String,
+ surveyQuestionList: List,
+ deadline: LocalDateTime,
+ ): Result = surveyFormDataSource.postSurveyForm(
+ eventId = eventId,
+ title = title,
+ content = content,
+ surveyQuestionList = surveyQuestionList,
+ deadline = deadline.toISOLocalDateTimeString(),
+ )
+
+ override suspend fun updateSurveyForm(surveyForm: SurveyForm): Result =
+ surveyFormDataSource.updateSurveyForm(
+ surveyFormRequest = SurveyFormRequest(
+ surveyFormId = surveyForm.surveyFormId,
+ eventId = surveyForm.eventId,
+ title = surveyForm.title,
+ content = surveyForm.content,
+ surveyQuestionList = surveyForm.surveyQuestionList,
+ deadline = surveyForm.deadline.toISOLocalDateTimeString(),
+ ),
+ )
+}
diff --git a/core/data/src/main/java/com/wap/wapp/core/data/repository/survey/SurveyRepository.kt b/core/data/src/main/java/com/wap/wapp/core/data/repository/survey/SurveyRepository.kt
new file mode 100644
index 000000000..b48a6f944
--- /dev/null
+++ b/core/data/src/main/java/com/wap/wapp/core/data/repository/survey/SurveyRepository.kt
@@ -0,0 +1,31 @@
+package com.wap.wapp.core.data.repository.survey
+
+import com.wap.wapp.core.model.survey.Survey
+import com.wap.wapp.core.model.survey.SurveyAnswer
+import java.time.LocalDateTime
+
+interface SurveyRepository {
+ suspend fun getSurveyList(): Result>
+
+ suspend fun getSurveyListByEventId(eventId: String): Result>
+
+ suspend fun getSurveyListBySurveyFormId(surveyFormId: String): Result>
+
+ suspend fun getUserRespondedSurveyList(userId: String): Result>
+
+ suspend fun getSurvey(surveyId: String): Result
+
+ suspend fun deleteSurvey(surveyId: String): Result
+
+ suspend fun postSurvey(
+ surveyFormId: String,
+ eventId: String,
+ userId: String,
+ title: String,
+ content: String,
+ surveyAnswerList: List,
+ surveyedAt: LocalDateTime,
+ ): Result
+
+ suspend fun isSubmittedSurvey(surveyFormId: String, userId: String): Result
+}
diff --git a/core/data/src/main/java/com/wap/wapp/core/data/repository/survey/SurveyRepositoryImpl.kt b/core/data/src/main/java/com/wap/wapp/core/data/repository/survey/SurveyRepositoryImpl.kt
new file mode 100644
index 000000000..562ca57de
--- /dev/null
+++ b/core/data/src/main/java/com/wap/wapp/core/data/repository/survey/SurveyRepositoryImpl.kt
@@ -0,0 +1,123 @@
+package com.wap.wapp.core.data.repository.survey
+
+import com.wap.wapp.core.data.utils.toISOLocalDateTimeString
+import com.wap.wapp.core.model.survey.Survey
+import com.wap.wapp.core.model.survey.SurveyAnswer
+import com.wap.wapp.core.network.source.event.EventDataSource
+import com.wap.wapp.core.network.source.survey.SurveyDataSource
+import com.wap.wapp.core.network.source.user.UserDataSource
+import java.time.LocalDateTime
+import javax.inject.Inject
+
+class SurveyRepositoryImpl @Inject constructor(
+ private val surveyDataSource: SurveyDataSource,
+ private val userDataSource: UserDataSource,
+ private val eventDataSource: EventDataSource,
+) : SurveyRepository {
+ override suspend fun getSurveyList(): Result> =
+ surveyDataSource.getSurveyList().mapCatching { surveyList ->
+ surveyList.map { surveyResponse ->
+ userDataSource.getUserProfile(userId = surveyResponse.userId)
+ .mapCatching { userProfileResponse ->
+ val userName = userProfileResponse.toDomain().userName
+
+ eventDataSource.getEvent(eventId = surveyResponse.eventId)
+ .mapCatching { eventResponse ->
+ val eventName = eventResponse.toDomain().title
+
+ surveyResponse.toDomain(userName = userName, eventName = eventName)
+ }.getOrThrow()
+ }.getOrThrow()
+ }
+ }
+
+ override suspend fun getSurveyListByEventId(eventId: String): Result> =
+ surveyDataSource.getSurveyListByEventId(eventId).mapCatching { surveyList ->
+ surveyList.map { surveyResponse ->
+ userDataSource.getUserProfile(userId = surveyResponse.userId)
+ .mapCatching { userProfileResponse ->
+ val userName = userProfileResponse.toDomain().userName
+
+ eventDataSource.getEvent(eventId = eventId)
+ .mapCatching { eventResponse ->
+ val eventName = eventResponse.toDomain().title
+
+ surveyResponse.toDomain(userName = userName, eventName = eventName)
+ }.getOrThrow()
+ }.getOrThrow()
+ }
+ }
+
+ override suspend fun getSurveyListBySurveyFormId(surveyFormId: String): Result> =
+ surveyDataSource.getSurveyListByEventId(surveyFormId).mapCatching { surveyList ->
+ surveyList.map { surveyResponse ->
+ userDataSource.getUserProfile(userId = surveyResponse.userId)
+ .mapCatching { userProfileResponse ->
+ val userName = userProfileResponse.toDomain().userName
+
+ eventDataSource.getEvent(eventId = surveyResponse.eventId)
+ .mapCatching { eventResponse ->
+ val eventName = eventResponse.toDomain().title
+
+ surveyResponse.toDomain(userName = userName, eventName = eventName)
+ }.getOrThrow()
+ }.getOrThrow()
+ }
+ }
+
+ override suspend fun getUserRespondedSurveyList(userId: String): Result> =
+ surveyDataSource.getUserRespondedSurveyList(userId).mapCatching { surveyList ->
+ surveyList.map { surveyResponse ->
+ userDataSource.getUserProfile(userId = surveyResponse.userId)
+ .mapCatching { userProfileResponse ->
+ val userName = userProfileResponse.toDomain().userName
+
+ eventDataSource.getEvent(eventId = surveyResponse.eventId)
+ .mapCatching { eventResponse ->
+ val eventName = eventResponse.toDomain().title
+
+ surveyResponse.toDomain(userName = userName, eventName = eventName)
+ }.getOrThrow()
+ }.getOrThrow()
+ }
+ }
+
+ override suspend fun getSurvey(surveyId: String): Result =
+ surveyDataSource.getSurvey(surveyId).mapCatching { surveyResponse ->
+ userDataSource.getUserProfile(userId = surveyResponse.userId)
+ .mapCatching { userProfileResponse ->
+ val userName = userProfileResponse.toDomain().userName
+
+ eventDataSource.getEvent(eventId = surveyResponse.eventId)
+ .mapCatching { eventResponse ->
+ val eventName = eventResponse.toDomain().title
+
+ surveyResponse.toDomain(userName = userName, eventName = eventName)
+ }.getOrThrow()
+ }.getOrThrow()
+ }
+
+ override suspend fun deleteSurvey(surveyId: String): Result =
+ surveyDataSource.deleteSurvey(surveyId)
+
+ override suspend fun postSurvey(
+ surveyFormId: String,
+ eventId: String,
+ userId: String,
+ title: String,
+ content: String,
+ surveyAnswerList: List,
+ surveyedAt: LocalDateTime,
+ ): Result = surveyDataSource.postSurvey(
+ surveyFormId = surveyFormId,
+ eventId = eventId,
+ userId = userId,
+ title = title,
+ content = content,
+ surveyAnswerList = surveyAnswerList,
+ surveyedAt = surveyedAt.toISOLocalDateTimeString(),
+ )
+
+ override suspend fun isSubmittedSurvey(surveyFormId: String, userId: String): Result =
+ surveyDataSource.isSubmittedSurvey(surveyFormId, userId)
+}
diff --git a/core/data/src/main/java/com/wap/wapp/core/data/repository/user/UserRepository.kt b/core/data/src/main/java/com/wap/wapp/core/data/repository/user/UserRepository.kt
new file mode 100644
index 000000000..973161632
--- /dev/null
+++ b/core/data/src/main/java/com/wap/wapp/core/data/repository/user/UserRepository.kt
@@ -0,0 +1,18 @@
+package com.wap.wapp.core.data.repository.user
+
+import com.wap.wapp.core.model.user.UserProfile
+
+interface UserRepository {
+ suspend fun getUserProfile(userId: String): Result
+
+ suspend fun getUserId(): Result
+
+ suspend fun postUserProfile(
+ userId: String,
+ userName: String,
+ studentId: String,
+ registeredAt: String,
+ ): Result
+
+ suspend fun deleteUserProfile(userId: String): Result
+}
diff --git a/core/data/src/main/java/com/wap/wapp/core/data/repository/user/UserRepositoryImpl.kt b/core/data/src/main/java/com/wap/wapp/core/data/repository/user/UserRepositoryImpl.kt
new file mode 100644
index 000000000..ae1e0f260
--- /dev/null
+++ b/core/data/src/main/java/com/wap/wapp/core/data/repository/user/UserRepositoryImpl.kt
@@ -0,0 +1,34 @@
+package com.wap.wapp.core.data.repository.user
+
+import com.wap.wapp.core.model.user.UserProfile
+import com.wap.wapp.core.network.model.user.UserProfileRequest
+import com.wap.wapp.core.network.source.user.UserDataSource
+import javax.inject.Inject
+
+class UserRepositoryImpl @Inject constructor(
+ private val userDataSource: UserDataSource,
+) : UserRepository {
+ override suspend fun getUserProfile(userId: String): Result =
+ userDataSource.getUserProfile(userId).mapCatching { response ->
+ response.toDomain()
+ }
+
+ override suspend fun getUserId(): Result = userDataSource.getUserId()
+
+ override suspend fun postUserProfile(
+ userId: String,
+ userName: String,
+ studentId: String,
+ registeredAt: String,
+ ): Result = userDataSource.postUserProfile(
+ UserProfileRequest(
+ userId = userId,
+ userName = userName,
+ studentId = studentId,
+ registeredAt = registeredAt,
+ ),
+ )
+
+ override suspend fun deleteUserProfile(userId: String): Result =
+ userDataSource.deleteUserProfile(userId)
+}
diff --git a/core/data/src/main/java/com/wap/wapp/core/data/utils/LocalDateTime.kt b/core/data/src/main/java/com/wap/wapp/core/data/utils/LocalDateTime.kt
new file mode 100644
index 000000000..b80ec5bc4
--- /dev/null
+++ b/core/data/src/main/java/com/wap/wapp/core/data/utils/LocalDateTime.kt
@@ -0,0 +1,7 @@
+package com.wap.wapp.core.data.utils
+
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatter
+
+internal fun LocalDateTime.toISOLocalDateTimeString(): String =
+ this.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
diff --git a/core/data/src/test/java/com/wap/wapp/core/data/ExampleUnitTest.kt b/core/data/src/test/java/com/wap/wapp/core/data/ExampleUnitTest.kt
new file mode 100644
index 000000000..4315f1728
--- /dev/null
+++ b/core/data/src/test/java/com/wap/wapp/core/data/ExampleUnitTest.kt
@@ -0,0 +1,16 @@
+package com.wap.wapp.core.data
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/core/designresource/.gitignore b/core/designresource/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/core/designresource/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core/designresource/build.gradle.kts b/core/designresource/build.gradle.kts
new file mode 100644
index 000000000..f46ee1bae
--- /dev/null
+++ b/core/designresource/build.gradle.kts
@@ -0,0 +1,25 @@
+plugins {
+ id("com.wap.wapp.library")
+}
+
+android {
+ namespace = "com.wap.wapp.core.designresource"
+
+ defaultConfig {
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro",
+ )
+ }
+ }
+}
+
+dependencies {
+ implementation(libs.material)
+}
diff --git a/core/designresource/consumer-rules.pro b/core/designresource/consumer-rules.pro
new file mode 100644
index 000000000..e69de29bb
diff --git a/core/designresource/proguard-rules.pro b/core/designresource/proguard-rules.pro
new file mode 100644
index 000000000..481bb4348
--- /dev/null
+++ b/core/designresource/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/core/designresource/src/main/res/drawable/ic_absent.xml b/core/designresource/src/main/res/drawable/ic_absent.xml
new file mode 100644
index 000000000..ed7c59bcf
--- /dev/null
+++ b/core/designresource/src/main/res/drawable/ic_absent.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/core/designresource/src/main/res/drawable/ic_account_setting.xml b/core/designresource/src/main/res/drawable/ic_account_setting.xml
new file mode 100644
index 000000000..fcccf4456
--- /dev/null
+++ b/core/designresource/src/main/res/drawable/ic_account_setting.xml
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/core/designresource/src/main/res/drawable/ic_add_question.xml b/core/designresource/src/main/res/drawable/ic_add_question.xml
new file mode 100644
index 000000000..27bcc74f6
--- /dev/null
+++ b/core/designresource/src/main/res/drawable/ic_add_question.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/core/designresource/src/main/res/drawable/ic_attendance.xml b/core/designresource/src/main/res/drawable/ic_attendance.xml
new file mode 100644
index 000000000..fe6be009f
--- /dev/null
+++ b/core/designresource/src/main/res/drawable/ic_attendance.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/core/designresource/src/main/res/drawable/ic_back.xml b/core/designresource/src/main/res/drawable/ic_back.xml
new file mode 100644
index 000000000..0aaf65619
--- /dev/null
+++ b/core/designresource/src/main/res/drawable/ic_back.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/core/designresource/src/main/res/drawable/ic_balloon.xml b/core/designresource/src/main/res/drawable/ic_balloon.xml
new file mode 100644
index 000000000..096fddade
--- /dev/null
+++ b/core/designresource/src/main/res/drawable/ic_balloon.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
diff --git a/core/designresource/src/main/res/drawable/ic_check.xml b/core/designresource/src/main/res/drawable/ic_check.xml
new file mode 100644
index 000000000..bf50854fc
--- /dev/null
+++ b/core/designresource/src/main/res/drawable/ic_check.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/core/designresource/src/main/res/drawable/ic_close.xml b/core/designresource/src/main/res/drawable/ic_close.xml
new file mode 100644
index 000000000..28133fae8
--- /dev/null
+++ b/core/designresource/src/main/res/drawable/ic_close.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/core/designresource/src/main/res/drawable/ic_forward.xml b/core/designresource/src/main/res/drawable/ic_forward.xml
new file mode 100644
index 000000000..9b7d6a0a5
--- /dev/null
+++ b/core/designresource/src/main/res/drawable/ic_forward.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/core/designresource/src/main/res/drawable/ic_forward_yellow.xml b/core/designresource/src/main/res/drawable/ic_forward_yellow.xml
new file mode 100644
index 000000000..204021024
--- /dev/null
+++ b/core/designresource/src/main/res/drawable/ic_forward_yellow.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/core/designresource/src/main/res/drawable/ic_github.xml b/core/designresource/src/main/res/drawable/ic_github.xml
new file mode 100644
index 000000000..95615b46a
--- /dev/null
+++ b/core/designresource/src/main/res/drawable/ic_github.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/core/designresource/src/main/res/drawable/ic_green_circle.xml b/core/designresource/src/main/res/drawable/ic_green_circle.xml
new file mode 100644
index 000000000..edb8d2259
--- /dev/null
+++ b/core/designresource/src/main/res/drawable/ic_green_circle.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/core/designresource/src/main/res/drawable/ic_guest_cat.xml b/core/designresource/src/main/res/drawable/ic_guest_cat.xml
new file mode 100644
index 000000000..6af02ac75
--- /dev/null
+++ b/core/designresource/src/main/res/drawable/ic_guest_cat.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/core/designresource/src/main/res/drawable/ic_guest_github.xml b/core/designresource/src/main/res/drawable/ic_guest_github.xml
new file mode 100644
index 000000000..bb72150db
--- /dev/null
+++ b/core/designresource/src/main/res/drawable/ic_guest_github.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/core/designresource/src/main/res/drawable/ic_magnifier.xml b/core/designresource/src/main/res/drawable/ic_magnifier.xml
new file mode 100644
index 000000000..70296bddf
--- /dev/null
+++ b/core/designresource/src/main/res/drawable/ic_magnifier.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/core/designresource/src/main/res/drawable/ic_manager_cat.xml b/core/designresource/src/main/res/drawable/ic_manager_cat.xml
new file mode 100644
index 000000000..34ec0ac8a
--- /dev/null
+++ b/core/designresource/src/main/res/drawable/ic_manager_cat.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/core/designresource/src/main/res/drawable/ic_manager_github.xml b/core/designresource/src/main/res/drawable/ic_manager_github.xml
new file mode 100644
index 000000000..24dba7830
--- /dev/null
+++ b/core/designresource/src/main/res/drawable/ic_manager_github.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/core/designresource/src/main/res/drawable/ic_normal_cat.xml b/core/designresource/src/main/res/drawable/ic_normal_cat.xml
new file mode 100644
index 000000000..48f17bc98
--- /dev/null
+++ b/core/designresource/src/main/res/drawable/ic_normal_cat.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/core/designresource/src/main/res/drawable/ic_normal_github.xml b/core/designresource/src/main/res/drawable/ic_normal_github.xml
new file mode 100644
index 000000000..3a4a3309f
--- /dev/null
+++ b/core/designresource/src/main/res/drawable/ic_normal_github.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/core/designresource/src/main/res/drawable/ic_profile.xml b/core/designresource/src/main/res/drawable/ic_profile.xml
new file mode 100644
index 000000000..8394a2082
--- /dev/null
+++ b/core/designresource/src/main/res/drawable/ic_profile.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/core/designresource/src/main/res/drawable/ic_profile_more.xml b/core/designresource/src/main/res/drawable/ic_profile_more.xml
new file mode 100644
index 000000000..81a49075d
--- /dev/null
+++ b/core/designresource/src/main/res/drawable/ic_profile_more.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
diff --git a/core/designresource/src/main/res/drawable/ic_red_circle.xml b/core/designresource/src/main/res/drawable/ic_red_circle.xml
new file mode 100644
index 000000000..697979309
--- /dev/null
+++ b/core/designresource/src/main/res/drawable/ic_red_circle.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/core/designresource/src/main/res/drawable/ic_return.xml b/core/designresource/src/main/res/drawable/ic_return.xml
new file mode 100644
index 000000000..6ff659c14
--- /dev/null
+++ b/core/designresource/src/main/res/drawable/ic_return.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/core/designresource/src/main/res/drawable/ic_small_right_arrow.xml b/core/designresource/src/main/res/drawable/ic_small_right_arrow.xml
new file mode 100644
index 000000000..fd6714f57
--- /dev/null
+++ b/core/designresource/src/main/res/drawable/ic_small_right_arrow.xml
@@ -0,0 +1,13 @@
+
+
+
diff --git a/core/designresource/src/main/res/drawable/ic_yellow_check.xml b/core/designresource/src/main/res/drawable/ic_yellow_check.xml
new file mode 100644
index 000000000..db3bc57a6
--- /dev/null
+++ b/core/designresource/src/main/res/drawable/ic_yellow_check.xml
@@ -0,0 +1,13 @@
+
+
+
diff --git a/core/designresource/src/main/res/drawable/img_white_cat.png b/core/designresource/src/main/res/drawable/img_white_cat.png
new file mode 100644
index 000000000..139c7a3ef
Binary files /dev/null and b/core/designresource/src/main/res/drawable/img_white_cat.png differ
diff --git a/core/designresource/src/main/res/font/notosanskr_bold.ttf b/core/designresource/src/main/res/font/notosanskr_bold.ttf
new file mode 100644
index 000000000..6cf639eb7
Binary files /dev/null and b/core/designresource/src/main/res/font/notosanskr_bold.ttf differ
diff --git a/core/designresource/src/main/res/font/notosanskr_medium.ttf b/core/designresource/src/main/res/font/notosanskr_medium.ttf
new file mode 100644
index 000000000..5311c8a31
Binary files /dev/null and b/core/designresource/src/main/res/font/notosanskr_medium.ttf differ
diff --git a/core/designresource/src/main/res/font/notosanskr_regular.ttf b/core/designresource/src/main/res/font/notosanskr_regular.ttf
new file mode 100644
index 000000000..1b14d3247
Binary files /dev/null and b/core/designresource/src/main/res/font/notosanskr_regular.ttf differ
diff --git a/core/designresource/src/main/res/values/colors.xml b/core/designresource/src/main/res/values/colors.xml
new file mode 100644
index 000000000..268270d15
--- /dev/null
+++ b/core/designresource/src/main/res/values/colors.xml
@@ -0,0 +1,17 @@
+
+
+ #131313
+
+ #252424
+ #424242
+ #828282
+
+ #A2A2A2
+ #F4F4F4
+
+ #FBCF34
+
+ #FFFFFF
+
diff --git a/core/designresource/src/main/res/values/strings.xml b/core/designresource/src/main/res/values/strings.xml
new file mode 100644
index 000000000..0815e2586
--- /dev/null
+++ b/core/designresource/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+
+
+ ๊ณต์ง์ฌํญ
+ ์ค๋ฌธ
+ ํ๋กํ
+ ๊ด๋ฆฌ
+
diff --git a/core/designresource/src/main/res/values/styles.xml b/core/designresource/src/main/res/values/styles.xml
new file mode 100644
index 000000000..a6b3daec9
--- /dev/null
+++ b/core/designresource/src/main/res/values/styles.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/core/designresource/src/main/res/values/typography.xml b/core/designresource/src/main/res/values/typography.xml
new file mode 100644
index 000000000..7be008269
--- /dev/null
+++ b/core/designresource/src/main/res/values/typography.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core/designsystem/.gitignore b/core/designsystem/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/core/designsystem/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core/designsystem/build.gradle.kts b/core/designsystem/build.gradle.kts
new file mode 100644
index 000000000..11cb14aae
--- /dev/null
+++ b/core/designsystem/build.gradle.kts
@@ -0,0 +1,33 @@
+plugins {
+ id("com.wap.wapp.library")
+ id("com.wap.wapp.compose")
+}
+
+android {
+ namespace = "com.wap.wapp.core.designsystem"
+
+ defaultConfig {
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro",
+ )
+ }
+ }
+}
+
+dependencies {
+ implementation(libs.androidx.core)
+ implementation(libs.material)
+ implementation(libs.lottie.compose)
+
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.test.junit)
+ androidTestImplementation(libs.androidx.test.espresso)
+}
diff --git a/core/designsystem/consumer-rules.pro b/core/designsystem/consumer-rules.pro
new file mode 100644
index 000000000..e69de29bb
diff --git a/core/designsystem/proguard-rules.pro b/core/designsystem/proguard-rules.pro
new file mode 100644
index 000000000..481bb4348
--- /dev/null
+++ b/core/designsystem/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/core/designsystem/src/androidTest/java/com/wap/designsystem/ExampleInstrumentedTest.kt b/core/designsystem/src/androidTest/java/com/wap/designsystem/ExampleInstrumentedTest.kt
new file mode 100644
index 000000000..93a5c5b34
--- /dev/null
+++ b/core/designsystem/src/androidTest/java/com/wap/designsystem/ExampleInstrumentedTest.kt
@@ -0,0 +1,22 @@
+package com.wap.designsystem
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.wap.designsystem.test", appContext.packageName)
+ }
+}
diff --git a/core/designsystem/src/main/java/com/wap/designsystem/Color.kt b/core/designsystem/src/main/java/com/wap/designsystem/Color.kt
new file mode 100644
index 000000000..2caf011d9
--- /dev/null
+++ b/core/designsystem/src/main/java/com/wap/designsystem/Color.kt
@@ -0,0 +1,107 @@
+package com.wap.designsystem
+
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.graphics.Color
+
+val White = Color(0xFFFFFFFF)
+val GrayF4 = Color(0xFFF4F4F4)
+val GrayBD = Color(0xFFBDBDBD)
+val Gray95 = Color(0xFF959595)
+val Gray82 = Color(0xFF828282)
+val Gray7C = Color(0xFF7C7C7C)
+val Gray4A = Color(0xFF49494A)
+val Black20 = Color(0xFF202022)
+val Black25 = Color(0xFF252424)
+val Black42 = Color(0xFF424242)
+val Black = Color(0xFF000000)
+val BackgroundBlack = Color(0xFF131313)
+
+val YellowA4 = Color(0xFFFEEBA4)
+val Yellow3C = Color(0xFFFFBD3C)
+val Yellow34 = Color(0xFFFBCF34)
+
+val GreenAB = Color(0xFF83F8AB)
+
+val Red = Color(0xFFE7475D)
+
+val Blue1FF = Color(0xFFEEF1FF)
+val Blue4FF = Color(0xFFAAC4FF)
+val Blue2FF = Color(0xFFB1B2FF)
+val BlueA3 = Color(0xFF2F3EA3)
+
+@Stable
+class WappColor(
+ white: Color = Color(0xFFFFFFFF),
+ black: Color = Color(0xFF000000),
+ backgroundBlack: Color = Color(0xFF131313),
+ black20: Color = Color(0xFF202022),
+ black25: Color = Color(0xFF252424),
+ black42: Color = Color(0xFF424242),
+ gray95: Color = Color(0xFF959595),
+ black82: Color = Color(0xFF828282),
+ grayA2: Color = Color(0xFFA2A2A2),
+ grayF4: Color = Color(0xFFF4F4F4),
+ grayBD: Color = Color(0xFFBDBDBD),
+ gray82: Color = Color(0xFF828282),
+ gray7C: Color = Color(0xFF7C7C7C),
+ gray4A: Color = Color(0xFF49494A),
+ yellow34: Color = Color(0xFFFBCF34),
+ yellow3C: Color = Color(0xFFFFBD3C),
+ yellowA4: Color = Color(0xFFFEEBA4),
+ greenAB: Color = Color(0xFF83F8AB),
+ red: Color = Color(0xFFE7475D),
+ blueA3: Color = Color(0xFF2F3EA3),
+ blue2FF: Color = Color(0xFFB1B2FF),
+ blue4FF: Color = Color(0xFFAAC4FF),
+ blue1FF: Color = Color(0xFFEEF1FF),
+) {
+ var white by mutableStateOf(white)
+ private set
+ var black by mutableStateOf(black)
+ private set
+ var backgroundBlack by mutableStateOf(backgroundBlack)
+ private set
+ var black20 by mutableStateOf(black20)
+ private set
+ var black25 by mutableStateOf(black25)
+ private set
+ var black42 by mutableStateOf(black42)
+ private set
+ var gray95 by mutableStateOf(gray95)
+ private set
+ var black82 by mutableStateOf(black82)
+ private set
+ var grayA2 by mutableStateOf(grayA2)
+ private set
+ var grayF4 by mutableStateOf(grayF4)
+ private set
+ var gray4A by mutableStateOf(gray4A)
+ private set
+ var grayBD by mutableStateOf(grayBD)
+ private set
+ var gray82 by mutableStateOf(gray82)
+ private set
+ var gray7C by mutableStateOf(gray7C)
+ private set
+ var yellow34 by mutableStateOf(yellow34)
+ private set
+ var yellow3C by mutableStateOf(yellow3C)
+ private set
+ var yellowA4 by mutableStateOf(yellowA4)
+ private set
+ var greenAB by mutableStateOf(greenAB)
+ private set
+ var red by mutableStateOf(red)
+ private set
+ var blueA3 by mutableStateOf(blueA3)
+ private set
+ var blue2FF by mutableStateOf(blue2FF)
+ private set
+ var blue4FF by mutableStateOf(blue4FF)
+ private set
+ var blue1FF by mutableStateOf(blue1FF)
+ private set
+}
diff --git a/core/designsystem/src/main/java/com/wap/designsystem/Theme.kt b/core/designsystem/src/main/java/com/wap/designsystem/Theme.kt
new file mode 100644
index 000000000..e8dc2ab6b
--- /dev/null
+++ b/core/designsystem/src/main/java/com/wap/designsystem/Theme.kt
@@ -0,0 +1,33 @@
+package com.wap.designsystem
+
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.staticCompositionLocalOf
+
+@Composable
+fun WappTheme(
+ content: @Composable () -> Unit,
+) {
+ val colors = WappColor()
+ val typography = WappTypography()
+ CompositionLocalProvider(
+ LocalWappColors provides colors,
+ LocalWappTypography provides typography,
+ ) {
+ MaterialTheme(content = content)
+ }
+}
+
+object WappTheme {
+ val colors: WappColor @Composable get() = LocalWappColors.current
+ val typography: WappTypography @Composable get() = LocalWappTypography.current
+}
+
+private val LocalWappColors = staticCompositionLocalOf {
+ error("Any WappColors Did Not Provided")
+}
+
+private val LocalWappTypography = staticCompositionLocalOf {
+ error("Any WappTypography Did Not Provided")
+}
diff --git a/core/designsystem/src/main/java/com/wap/designsystem/Type.kt b/core/designsystem/src/main/java/com/wap/designsystem/Type.kt
new file mode 100644
index 000000000..03c67b8ec
--- /dev/null
+++ b/core/designsystem/src/main/java/com/wap/designsystem/Type.kt
@@ -0,0 +1,173 @@
+package com.wap.designsystem
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.text.PlatformTextStyle
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.Font
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.sp
+import com.wap.wapp.core.designsystem.R
+
+val NotoSansRegular: FontFamily =
+ FontFamily(
+ Font(
+ resId = R.font.notosanskr_regular,
+ weight = FontWeight.Normal,
+ style = FontStyle.Normal,
+ ),
+ )
+
+val NotoSansMedium: FontFamily =
+ FontFamily(
+ Font(
+ resId = R.font.notosanskr_medium,
+ weight = FontWeight.Medium,
+ style = FontStyle.Normal,
+ ),
+ )
+
+val NotoSansBold: FontFamily =
+ FontFamily(
+ Font(
+ resId = R.font.notosanskr_bold,
+ weight = FontWeight.Bold,
+ style = FontStyle.Normal,
+ ),
+ )
+
+@Stable
+class WappTypography internal constructor(
+ titleBold: TextStyle,
+ titleMedium: TextStyle,
+ titleRegular: TextStyle,
+ contentBold: TextStyle,
+ contentMedium: TextStyle,
+ contentRegular: TextStyle,
+ labelBold: TextStyle,
+ labelMedium: TextStyle,
+ labelRegular: TextStyle,
+ captionBold: TextStyle,
+ captionMedium: TextStyle,
+ captionRegular: TextStyle,
+) {
+ var titleBold: TextStyle by mutableStateOf(titleBold)
+ private set
+ var titleMedium: TextStyle by mutableStateOf(titleMedium)
+ private set
+ var titleRegular: TextStyle by mutableStateOf(titleRegular)
+ private set
+ var contentBold: TextStyle by mutableStateOf(contentBold)
+ private set
+ var contentMedium: TextStyle by mutableStateOf(contentMedium)
+ private set
+ var contentRegular: TextStyle by mutableStateOf(contentRegular)
+ private set
+ var labelBold: TextStyle by mutableStateOf(labelBold)
+ private set
+ var labelMedium: TextStyle by mutableStateOf(labelMedium)
+ private set
+ var labelRegular: TextStyle by mutableStateOf(labelRegular)
+ private set
+ var captionBold: TextStyle by mutableStateOf(captionBold)
+ private set
+ var captionMedium: TextStyle by mutableStateOf(captionMedium)
+ private set
+ var captionRegular: TextStyle by mutableStateOf(captionRegular)
+ private set
+}
+
+@Composable
+fun WappTypography(): WappTypography {
+ return WappTypography(
+ titleBold = TextStyle(
+ fontSize = 18.sp,
+ fontFamily = NotoSansBold,
+ platformStyle = PlatformTextStyle(
+ includeFontPadding = false,
+ ),
+ ),
+ titleMedium = TextStyle(
+ fontSize = 18.sp,
+ fontFamily = NotoSansMedium,
+ platformStyle = PlatformTextStyle(
+ includeFontPadding = false,
+ ),
+ ),
+ titleRegular = TextStyle(
+ fontSize = 18.sp,
+ fontFamily = NotoSansRegular,
+ platformStyle = PlatformTextStyle(
+ includeFontPadding = false,
+ ),
+ ),
+ contentBold = TextStyle(
+ fontSize = 16.sp,
+ fontFamily = NotoSansBold,
+ platformStyle = PlatformTextStyle(
+ includeFontPadding = false,
+ ),
+ ),
+ contentMedium = TextStyle(
+ fontSize = 16.sp,
+ fontFamily = NotoSansMedium,
+ platformStyle = PlatformTextStyle(
+ includeFontPadding = false,
+ ),
+ ),
+ contentRegular = TextStyle(
+ fontSize = 16.sp,
+ fontFamily = NotoSansRegular,
+ platformStyle = PlatformTextStyle(
+ includeFontPadding = false,
+ ),
+ ),
+ labelBold = TextStyle(
+ fontSize = 14.sp,
+ fontFamily = NotoSansBold,
+ platformStyle = PlatformTextStyle(
+ includeFontPadding = false,
+ ),
+ ),
+ labelMedium = TextStyle(
+ fontSize = 14.sp,
+ fontFamily = NotoSansMedium,
+ platformStyle = PlatformTextStyle(
+ includeFontPadding = false,
+ ),
+ ),
+ labelRegular = TextStyle(
+ fontSize = 14.sp,
+ fontFamily = NotoSansRegular,
+ platformStyle = PlatformTextStyle(
+ includeFontPadding = false,
+ ),
+ ),
+ captionBold = TextStyle(
+ fontSize = 12.sp,
+ fontFamily = NotoSansBold,
+ platformStyle = PlatformTextStyle(
+ includeFontPadding = false,
+ ),
+ ),
+ captionMedium = TextStyle(
+ fontSize = 12.sp,
+ fontFamily = NotoSansMedium,
+ platformStyle = PlatformTextStyle(
+ includeFontPadding = false,
+ ),
+ ),
+ captionRegular = TextStyle(
+ fontSize = 12.sp,
+ fontFamily = NotoSansRegular,
+ platformStyle = PlatformTextStyle(
+ includeFontPadding = false,
+ ),
+ ),
+ )
+}
diff --git a/core/designsystem/src/main/java/com/wap/designsystem/component/Button.kt b/core/designsystem/src/main/java/com/wap/designsystem/component/Button.kt
new file mode 100644
index 000000000..367bb75c3
--- /dev/null
+++ b/core/designsystem/src/main/java/com/wap/designsystem/component/Button.kt
@@ -0,0 +1,44 @@
+package com.wap.designsystem.component
+
+import androidx.annotation.StringRes
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import com.wap.designsystem.WappTheme
+import com.wap.wapp.core.designsystem.R
+
+@Composable
+fun WappButton(
+ modifier: Modifier = Modifier,
+ @StringRes textRes: Int = R.string.done,
+ onClick: () -> Unit,
+ isEnabled: Boolean = true,
+) {
+ Button(
+ modifier = modifier
+ .fillMaxWidth()
+ .height(48.dp),
+ onClick = { onClick() },
+ enabled = isEnabled,
+ colors = ButtonDefaults.buttonColors(
+ contentColor = WappTheme.colors.white,
+ containerColor = WappTheme.colors.yellow34,
+ disabledContentColor = WappTheme.colors.white,
+ disabledContainerColor = WappTheme.colors.grayA2,
+ ),
+ shape = RoundedCornerShape(10.dp),
+ content = {
+ Text(
+ text = stringResource(textRes),
+ style = WappTheme.typography.contentRegular,
+ )
+ },
+ )
+}
diff --git a/core/designsystem/src/main/java/com/wap/designsystem/component/Card.kt b/core/designsystem/src/main/java/com/wap/designsystem/component/Card.kt
new file mode 100644
index 000000000..05847bae6
--- /dev/null
+++ b/core/designsystem/src/main/java/com/wap/designsystem/component/Card.kt
@@ -0,0 +1,21 @@
+package com.wap.designsystem.component
+
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Card
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.wap.designsystem.WappTheme
+
+@Composable
+fun WappCard(
+ modifier: Modifier = Modifier,
+ content: @Composable () -> Unit,
+) {
+ Card(
+ shape = RoundedCornerShape(10.dp),
+ modifier = modifier,
+ backgroundColor = WappTheme.colors.black25,
+ content = content,
+ )
+}
diff --git a/core/designsystem/src/main/java/com/wap/designsystem/component/CircleLoader.kt b/core/designsystem/src/main/java/com/wap/designsystem/component/CircleLoader.kt
new file mode 100644
index 000000000..418dcbe27
--- /dev/null
+++ b/core/designsystem/src/main/java/com/wap/designsystem/component/CircleLoader.kt
@@ -0,0 +1,29 @@
+package com.wap.designsystem.component
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import com.airbnb.lottie.compose.LottieAnimation
+import com.airbnb.lottie.compose.LottieCompositionSpec
+import com.airbnb.lottie.compose.LottieConstants
+import com.airbnb.lottie.compose.rememberLottieComposition
+import com.wap.wapp.core.designsystem.R
+
+@Composable
+fun CircleLoader(
+ modifier: Modifier = Modifier,
+ contentAlignment: Alignment = Alignment.Center,
+) {
+ val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.raw_loading))
+ Box(
+ modifier = modifier,
+ contentAlignment = contentAlignment,
+ ) {
+ LottieAnimation(
+ composition = composition,
+ iterations = LottieConstants.IterateForever,
+ )
+ }
+}
diff --git a/core/designsystem/src/main/java/com/wap/designsystem/component/MainTopBar.kt b/core/designsystem/src/main/java/com/wap/designsystem/component/MainTopBar.kt
new file mode 100644
index 000000000..b8e6904b9
--- /dev/null
+++ b/core/designsystem/src/main/java/com/wap/designsystem/component/MainTopBar.kt
@@ -0,0 +1,173 @@
+package com.wap.designsystem.component
+
+import androidx.annotation.StringRes
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.wap.designsystem.WappTheme
+import com.wap.wapp.core.designsystem.R
+
+@Composable
+fun WappLeftMainTopBar(
+ @StringRes titleRes: Int,
+ @StringRes contentRes: Int,
+ showSettingButton: Boolean = false,
+ onClickSettingButton: () -> Unit = {},
+ @StringRes settingButtonDescriptionRes: Int = R.string.setting_button,
+ modifier: Modifier = Modifier,
+) {
+ Column(
+ verticalArrangement = Arrangement.spacedBy(16.dp),
+ horizontalAlignment = Alignment.Start,
+ modifier = modifier
+ .padding(top = 40.dp, start = 24.dp, end = 24.dp, bottom = 40.dp)
+ .fillMaxWidth()
+ .wrapContentHeight(),
+ ) {
+ Box(modifier = Modifier.fillMaxWidth()) {
+ Text(
+ text = stringResource(id = titleRes),
+ color = WappTheme.colors.white,
+ style = WappTheme.typography.titleBold.copy(fontSize = 24.sp),
+ modifier = Modifier.align(Alignment.CenterStart),
+ )
+
+ if (showSettingButton) {
+ Image(
+ painter =
+ painterResource(id = R.drawable.ic_subtract),
+ contentDescription = stringResource(id = settingButtonDescriptionRes),
+ modifier = Modifier
+ .align(Alignment.CenterEnd)
+ .clickable { onClickSettingButton() },
+ )
+ }
+ }
+ Text(
+ text = stringResource(id = contentRes),
+ color = WappTheme.colors.white,
+ style = WappTheme.typography.contentRegular,
+ )
+ }
+}
+
+@Composable
+fun WappRightMainTopBar(
+ @StringRes titleRes: Int,
+ @StringRes contentRes: Int,
+ showBackButton: Boolean = false,
+ onClickBackButton: () -> Unit = {},
+ @StringRes settingButtonDescriptionRes: Int = R.string.back_button,
+ modifier: Modifier = Modifier,
+) {
+ Column(
+ verticalArrangement = Arrangement.spacedBy(16.dp),
+ horizontalAlignment = Alignment.End,
+ modifier = modifier
+ .padding(top = 40.dp, start = 24.dp, end = 24.dp, bottom = 40.dp)
+ .fillMaxWidth()
+ .wrapContentHeight(),
+ ) {
+ Box(modifier = Modifier.fillMaxWidth()) {
+ Text(
+ text = stringResource(id = titleRes),
+ color = WappTheme.colors.white,
+ style = WappTheme.typography.titleBold.copy(fontSize = 24.sp),
+ modifier = Modifier.align(Alignment.CenterEnd),
+ )
+
+ if (showBackButton) {
+ Image(
+ painter =
+ painterResource(id = R.drawable.ic_back),
+ contentDescription = stringResource(id = settingButtonDescriptionRes),
+ modifier = Modifier
+ .align(Alignment.CenterStart)
+ .clickable { onClickBackButton() },
+ )
+ }
+ }
+ Text(
+ text = stringResource(id = contentRes),
+ color = WappTheme.colors.white,
+ style = WappTheme.typography.contentRegular,
+ )
+ }
+}
+
+@Preview("without Button Left TopBar")
+@Composable
+fun WappLeftMainTopBarWithoutButton() {
+ WappTheme {
+ Surface(
+ color = WappTheme.colors.backgroundBlack,
+ ) {
+ WappLeftMainTopBar(
+ titleRes = R.string.notice,
+ contentRes = R.string.notice,
+ )
+ }
+ }
+}
+
+@Preview("with Button Left TopBar")
+@Composable
+fun WappLeftMainTopBarWithButton() {
+ WappTheme {
+ Surface(
+ color = WappTheme.colors.backgroundBlack,
+ ) {
+ WappLeftMainTopBar(
+ titleRes = R.string.notice,
+ contentRes = R.string.notice,
+ showSettingButton = true,
+ )
+ }
+ }
+}
+
+@Preview("without Button Right TopBar")
+@Composable
+fun WappRightMainTopBarWithoutButton() {
+ WappTheme {
+ Surface(
+ color = WappTheme.colors.backgroundBlack,
+ ) {
+ WappRightMainTopBar(
+ titleRes = R.string.notice,
+ contentRes = R.string.notice,
+ )
+ }
+ }
+}
+
+@Preview("with Button Right TopBar")
+@Composable
+fun WappRightMainTopBarWithButton() {
+ WappTheme {
+ Surface(
+ color = WappTheme.colors.backgroundBlack,
+ ) {
+ WappRightMainTopBar(
+ titleRes = R.string.notice,
+ contentRes = R.string.notice,
+ showBackButton = true,
+ )
+ }
+ }
+}
diff --git a/core/designsystem/src/main/java/com/wap/designsystem/component/NothingToShow.kt b/core/designsystem/src/main/java/com/wap/designsystem/component/NothingToShow.kt
new file mode 100644
index 000000000..a31981dd8
--- /dev/null
+++ b/core/designsystem/src/main/java/com/wap/designsystem/component/NothingToShow.kt
@@ -0,0 +1,38 @@
+package com.wap.designsystem.component
+
+import androidx.annotation.StringRes
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.wap.designsystem.WappTheme
+
+@Composable
+fun NothingToShow(@StringRes title: Int) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(10.dp),
+ ) {
+ Spacer(modifier = Modifier.weight(1f))
+
+ Text(
+ text = stringResource(id = title),
+ style = WappTheme.typography.contentRegular.copy(fontSize = 20.sp),
+ color = WappTheme.colors.white,
+ textAlign = TextAlign.Center,
+ modifier = Modifier.weight(1f),
+ )
+
+ Spacer(modifier = Modifier.weight(1f))
+ }
+}
diff --git a/core/designsystem/src/main/java/com/wap/designsystem/component/RowBar.kt b/core/designsystem/src/main/java/com/wap/designsystem/component/RowBar.kt
new file mode 100644
index 000000000..736d97f67
--- /dev/null
+++ b/core/designsystem/src/main/java/com/wap/designsystem/component/RowBar.kt
@@ -0,0 +1,51 @@
+package com.wap.designsystem.component
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.wap.designsystem.WappTheme
+import com.wap.wapp.core.designsystem.R
+
+@Composable
+fun WappRowBar(
+ title: String,
+ onClicked: () -> Unit = {},
+ modifier: Modifier = Modifier,
+) {
+ Box(
+ modifier = modifier
+ .fillMaxWidth()
+ .clickable { onClicked() }
+ .padding(horizontal = 20.dp, vertical = 15.dp),
+ ) {
+ Text(
+ text = title,
+ style = WappTheme.typography.contentRegular,
+ color = WappTheme.colors.white,
+ modifier = Modifier
+ .align(Alignment.CenterStart),
+ )
+ Image(
+ painter = painterResource(id = R.drawable.ic_right_arrow_yellow),
+ contentDescription = stringResource(id = R.string.right_yellow_arrow_description),
+ modifier = Modifier
+ .align(Alignment.CenterEnd),
+ )
+ }
+}
+
+@Preview
+@Composable
+fun PreviewWappRowBar() {
+ WappRowBar(title = "์๋ฆผ ์ค์ ")
+}
diff --git a/core/designsystem/src/main/java/com/wap/designsystem/component/SubTopBar.kt b/core/designsystem/src/main/java/com/wap/designsystem/component/SubTopBar.kt
new file mode 100644
index 000000000..ade79e523
--- /dev/null
+++ b/core/designsystem/src/main/java/com/wap/designsystem/component/SubTopBar.kt
@@ -0,0 +1,143 @@
+package com.wap.designsystem.component
+
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.wap.designsystem.WappTheme
+import com.wap.wapp.core.designsystem.R
+
+@Composable
+fun WappSubTopBar(
+ @StringRes titleRes: Int,
+ modifier: Modifier = Modifier,
+ showLeftButton: Boolean = false,
+ showRightButton: Boolean = false,
+ onClickLeftButton: () -> Unit = {},
+ onClickRightButton: () -> Unit = {},
+ @StringRes leftButtonDescriptionRes: Int = R.string.back_button,
+ @DrawableRes leftButtonDrawableRes: Int = R.drawable.ic_back,
+) {
+ Box(
+ modifier = modifier.fillMaxWidth(),
+ ) {
+ if (showLeftButton) {
+ Icon(
+ painter = painterResource(leftButtonDrawableRes),
+ contentDescription = stringResource(leftButtonDescriptionRes),
+ tint = WappTheme.colors.white,
+ modifier = Modifier
+ .padding(start = 20.dp)
+ .size(20.dp)
+ .clickable { onClickLeftButton() }
+ .align(Alignment.CenterStart),
+ )
+ }
+
+ Text(
+ text = stringResource(titleRes),
+ textAlign = TextAlign.Center,
+ style = WappTheme.typography.titleBold,
+ color = WappTheme.colors.white,
+ modifier = Modifier.align(Alignment.Center),
+ )
+
+ if (showRightButton) {
+ Text(
+ text = stringResource(id = R.string.delete),
+ style = WappTheme.typography.titleBold,
+ color = WappTheme.colors.yellow34,
+ modifier = Modifier
+ .align(Alignment.CenterEnd)
+ .padding(end = 20.dp)
+ .clickable { onClickRightButton() },
+ )
+ }
+ }
+}
+
+@Preview("without Button TopBar")
+@Composable
+fun WappSubTopBarWithoutButton() {
+ WappTheme {
+ Surface(
+ color = WappTheme.colors.backgroundBlack,
+ ) {
+ WappSubTopBar(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp),
+ titleRes = R.string.notice,
+ )
+ }
+ }
+}
+
+@Preview("with Right Button TopBar")
+@Composable
+fun WappSubTopBarWithRightButton() {
+ WappTheme {
+ Surface(
+ color = WappTheme.colors.backgroundBlack,
+ ) {
+ WappSubTopBar(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 16.dp),
+ titleRes = R.string.notice,
+ showRightButton = true,
+ )
+ }
+ }
+}
+
+@Preview("with Left Button TopBar")
+@Composable
+fun WappSubTopBarWithLeftButton() {
+ WappTheme {
+ Surface(
+ color = WappTheme.colors.backgroundBlack,
+ ) {
+ WappSubTopBar(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 16.dp),
+ titleRes = R.string.notice,
+ showLeftButton = true,
+ )
+ }
+ }
+}
+
+@Preview("with Both Button TopBar")
+@Composable
+fun WappSubTopBarWithBothButton() {
+ WappTheme {
+ Surface(
+ color = WappTheme.colors.backgroundBlack,
+ ) {
+ WappSubTopBar(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 16.dp),
+ titleRes = R.string.notice,
+ showRightButton = true,
+ showLeftButton = true,
+ )
+ }
+ }
+}
diff --git a/core/designsystem/src/main/java/com/wap/designsystem/component/TextField.kt b/core/designsystem/src/main/java/com/wap/designsystem/component/TextField.kt
new file mode 100644
index 000000000..aa117d198
--- /dev/null
+++ b/core/designsystem/src/main/java/com/wap/designsystem/component/TextField.kt
@@ -0,0 +1,93 @@
+package com.wap.designsystem.component
+
+import androidx.annotation.StringRes
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.KeyboardActions
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextField
+import androidx.compose.material3.TextFieldDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalSoftwareKeyboardController
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.unit.dp
+import com.wap.designsystem.WappTheme
+
+@OptIn(ExperimentalComposeUiApi::class)
+@Composable
+fun WappTextField(
+ value: String,
+ onValueChanged: (String) -> Unit,
+ @StringRes label: Int,
+ isError: Boolean = false,
+ supportingText: String = "",
+) {
+ val keyboardController = LocalSoftwareKeyboardController.current
+
+ TextField(
+ value = value,
+ onValueChange = { newValue -> onValueChanged(newValue) },
+ colors = TextFieldDefaults.colors(
+ unfocusedContainerColor = WappTheme.colors.black25,
+ focusedContainerColor = WappTheme.colors.black25,
+ errorContainerColor = WappTheme.colors.black25,
+ unfocusedLabelColor = WappTheme.colors.white,
+ focusedLabelColor = WappTheme.colors.white,
+ unfocusedTextColor = WappTheme.colors.white,
+ focusedTextColor = WappTheme.colors.white,
+ errorTextColor = WappTheme.colors.white,
+ unfocusedSupportingTextColor = WappTheme.colors.white,
+ focusedSupportingTextColor = WappTheme.colors.white,
+ focusedIndicatorColor = WappTheme.colors.yellow34,
+ cursorColor = WappTheme.colors.yellow34,
+ ),
+ label = {
+ Text(text = stringResource(label))
+ },
+ singleLine = true,
+ keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
+ keyboardActions = KeyboardActions(onDone = { keyboardController?.hide() }),
+ isError = isError,
+ supportingText = {
+ Text(text = supportingText)
+ },
+ )
+}
+
+@OptIn(ExperimentalComposeUiApi::class)
+@Composable
+fun WappRoundedTextField(
+ value: String,
+ onValueChange: (String) -> Unit,
+ modifier: Modifier = Modifier,
+ @StringRes placeholder: Int,
+) {
+ val keyboardController = LocalSoftwareKeyboardController.current
+
+ TextField(
+ value = value,
+ onValueChange = onValueChange,
+ modifier = modifier,
+ colors = TextFieldDefaults.colors(
+ focusedTextColor = WappTheme.colors.white,
+ unfocusedTextColor = WappTheme.colors.white,
+ focusedContainerColor = WappTheme.colors.black25,
+ unfocusedContainerColor = WappTheme.colors.black25,
+ focusedIndicatorColor = WappTheme.colors.black25,
+ unfocusedIndicatorColor = WappTheme.colors.black25,
+ cursorColor = WappTheme.colors.yellow34,
+ ),
+ placeholder = {
+ androidx.compose.material.Text(
+ text = stringResource(id = placeholder),
+ color = WappTheme.colors.gray82,
+ )
+ },
+ keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
+ keyboardActions = KeyboardActions(onDone = { keyboardController?.hide() }),
+ shape = RoundedCornerShape(10.dp),
+ )
+}
diff --git a/core/designsystem/src/main/java/com/wap/designsystem/component/Title.kt b/core/designsystem/src/main/java/com/wap/designsystem/component/Title.kt
new file mode 100644
index 000000000..4b994da0b
--- /dev/null
+++ b/core/designsystem/src/main/java/com/wap/designsystem/component/Title.kt
@@ -0,0 +1,39 @@
+package com.wap.designsystem.component
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.wap.designsystem.WappTheme
+
+@Composable
+fun WappTitle(
+ title: String,
+ content: String,
+ modifier: Modifier = Modifier,
+) {
+ Column(
+ verticalArrangement = Arrangement.spacedBy(16.dp),
+ modifier = modifier.fillMaxWidth(),
+ ) {
+ Text(
+ text = title,
+ style = WappTheme.typography.titleBold,
+ fontSize = 22.sp,
+ color = WappTheme.colors.white,
+ textAlign = TextAlign.Start,
+ )
+
+ Text(
+ text = content,
+ style = WappTheme.typography.contentRegular,
+ color = WappTheme.colors.white,
+ textAlign = TextAlign.Start,
+ )
+ }
+}
diff --git a/core/designsystem/src/main/java/com/wap/designsystem/modifier/ModifierUtil.kt b/core/designsystem/src/main/java/com/wap/designsystem/modifier/ModifierUtil.kt
new file mode 100644
index 000000000..2152086a1
--- /dev/null
+++ b/core/designsystem/src/main/java/com/wap/designsystem/modifier/ModifierUtil.kt
@@ -0,0 +1,17 @@
+package com.wap.designsystem.modifier
+
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusManager
+import androidx.compose.ui.input.pointer.pointerInput
+
+fun Modifier.addFocusCleaner(focusManager: FocusManager, doOnClear: () -> Unit = {}): Modifier {
+ return this.pointerInput(Unit) {
+ detectTapGestures(
+ onTap = {
+ doOnClear()
+ focusManager.clearFocus()
+ },
+ )
+ }
+}
diff --git a/core/designsystem/src/main/res/drawable/ic_back.xml b/core/designsystem/src/main/res/drawable/ic_back.xml
new file mode 100644
index 000000000..0aaf65619
--- /dev/null
+++ b/core/designsystem/src/main/res/drawable/ic_back.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/core/designsystem/src/main/res/drawable/ic_close.xml b/core/designsystem/src/main/res/drawable/ic_close.xml
new file mode 100644
index 000000000..844b6b62e
--- /dev/null
+++ b/core/designsystem/src/main/res/drawable/ic_close.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/core/designsystem/src/main/res/drawable/ic_right_arrow_yellow.xml b/core/designsystem/src/main/res/drawable/ic_right_arrow_yellow.xml
new file mode 100644
index 000000000..8a52c1dc6
--- /dev/null
+++ b/core/designsystem/src/main/res/drawable/ic_right_arrow_yellow.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/core/designsystem/src/main/res/drawable/ic_subtract.xml b/core/designsystem/src/main/res/drawable/ic_subtract.xml
new file mode 100644
index 000000000..8b9e47088
--- /dev/null
+++ b/core/designsystem/src/main/res/drawable/ic_subtract.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/core/designsystem/src/main/res/font/notosanskr_bold.ttf b/core/designsystem/src/main/res/font/notosanskr_bold.ttf
new file mode 100644
index 000000000..6cf639eb7
Binary files /dev/null and b/core/designsystem/src/main/res/font/notosanskr_bold.ttf differ
diff --git a/core/designsystem/src/main/res/font/notosanskr_medium.ttf b/core/designsystem/src/main/res/font/notosanskr_medium.ttf
new file mode 100644
index 000000000..5311c8a31
Binary files /dev/null and b/core/designsystem/src/main/res/font/notosanskr_medium.ttf differ
diff --git a/core/designsystem/src/main/res/font/notosanskr_regular.ttf b/core/designsystem/src/main/res/font/notosanskr_regular.ttf
new file mode 100644
index 000000000..1b14d3247
Binary files /dev/null and b/core/designsystem/src/main/res/font/notosanskr_regular.ttf differ
diff --git a/core/designsystem/src/main/res/raw/raw_loading.json b/core/designsystem/src/main/res/raw/raw_loading.json
new file mode 100644
index 000000000..1ed540c44
--- /dev/null
+++ b/core/designsystem/src/main/res/raw/raw_loading.json
@@ -0,0 +1 @@
+{"nm":"loading_6","ddd":0,"h":300,"w":300,"meta":{"g":"@lottiefiles/toolkit-js 0.33.2"},"layers":[{"ty":4,"nm":"Shape Layer 2","sr":1,"st":0,"op":300,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[30.000000000000004,30.000000000000004,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[150.00000000000003,150.00000000000003,0],"ix":2},"r":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":0},{"s":[360],"t":60}],"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 1","ix":1,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[300,300],"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":2,"lj":1,"ml":4,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":50,"ix":5},"c":{"a":0,"k":[0.9843,0.8118,0.2039,1],"ix":3}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"tm","bm":0,"hd":false,"mn":"ADBE Vector Filter - Trim","nm":"Trim Paths 1","ix":2,"e":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[1],"t":0},{"s":[100],"t":50}],"ix":2},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":0},{"s":[3],"t":60}],"ix":3},"s":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[0],"t":10},{"s":[99],"t":60}],"ix":1},"m":1}],"ind":1},{"ty":4,"nm":"Shape Layer 1","sr":1,"st":0,"op":300,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[30.000000000000004,30.000000000000004,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[150.00000000000003,150.00000000000003,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":30,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 1","ix":1,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[300,300],"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":50,"ix":5},"c":{"a":0,"k":[1,1,1,1],"ix":3}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":2}],"v":"5.8.1","fr":30,"op":60,"ip":0,"assets":[]}
diff --git a/core/designsystem/src/main/res/values/strings.xml b/core/designsystem/src/main/res/values/strings.xml
new file mode 100644
index 000000000..59b6bf47e
--- /dev/null
+++ b/core/designsystem/src/main/res/values/strings.xml
@@ -0,0 +1,10 @@
+
+
+ ๋ค๋ก๊ฐ๊ธฐ ๋ฒํผ
+ ์ค์ ๋ฒํผ
+ ๋ซ๊ธฐ ๋ฒํผ
+ ๊ณต์ง์ฌํญ
+ ์๋ฃ
+ ์ญ์
+ ํด๋น ๊ธฐ๋ฅ์ผ๋ก ์ด๋ํ๋ ์ค๋ฅธ์ชฝ์ผ๋ก ๋ฝ๋กํ ๋
ธ๋์ ํ์ดํ ์
๋๋ค.
+
diff --git a/core/designsystem/src/test/java/com/wap/designsystem/ExampleUnitTest.kt b/core/designsystem/src/test/java/com/wap/designsystem/ExampleUnitTest.kt
new file mode 100644
index 000000000..dd0419f5b
--- /dev/null
+++ b/core/designsystem/src/test/java/com/wap/designsystem/ExampleUnitTest.kt
@@ -0,0 +1,16 @@
+package com.wap.designsystem
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/core/domain/.gitignore b/core/domain/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/core/domain/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core/domain/build.gradle.kts b/core/domain/build.gradle.kts
new file mode 100644
index 000000000..effdcc047
--- /dev/null
+++ b/core/domain/build.gradle.kts
@@ -0,0 +1,31 @@
+plugins {
+ id("com.wap.wapp.library")
+ id("com.wap.wapp.hilt")
+}
+
+android {
+ namespace = "com.wap.wapp.core.domain"
+
+ defaultConfig {
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro",
+ )
+ }
+ }
+}
+
+dependencies {
+ implementation(project(":core:data"))
+ implementation(project(":core:model"))
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.test.junit)
+ androidTestImplementation(libs.androidx.test.espresso)
+}
diff --git a/core/domain/consumer-rules.pro b/core/domain/consumer-rules.pro
new file mode 100644
index 000000000..e69de29bb
diff --git a/core/domain/proguard-rules.pro b/core/domain/proguard-rules.pro
new file mode 100644
index 000000000..481bb4348
--- /dev/null
+++ b/core/domain/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/core/domain/src/androidTest/java/com/wap/wapp/core/domain/ExampleInstrumentedTest.kt b/core/domain/src/androidTest/java/com/wap/wapp/core/domain/ExampleInstrumentedTest.kt
new file mode 100644
index 000000000..406dfe371
--- /dev/null
+++ b/core/domain/src/androidTest/java/com/wap/wapp/core/domain/ExampleInstrumentedTest.kt
@@ -0,0 +1,22 @@
+package com.wap.wapp.core.domain
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.wap.wapp.core.domain.test", appContext.packageName)
+ }
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/model/AuthState.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/model/AuthState.kt
new file mode 100644
index 000000000..e603e17dd
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/model/AuthState.kt
@@ -0,0 +1,5 @@
+package com.wap.wapp.core.domain.model
+
+enum class AuthState {
+ SIGN_IN, SIGN_UP
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/model/CodeValidation.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/model/CodeValidation.kt
new file mode 100644
index 000000000..c8693f8aa
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/model/CodeValidation.kt
@@ -0,0 +1,5 @@
+package com.wap.wapp.core.domain.model
+
+enum class CodeValidation {
+ INVALID, VALID
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/attendance/GetAttendanceUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/attendance/GetAttendanceUseCase.kt
new file mode 100644
index 000000000..0edbbff8b
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/attendance/GetAttendanceUseCase.kt
@@ -0,0 +1,10 @@
+package com.wap.wapp.core.domain.usecase.attendance
+
+import com.wap.wapp.core.data.repository.attendance.AttendanceRepository
+import javax.inject.Inject
+
+class GetAttendanceUseCase @Inject constructor(
+ private val attendanceRepository: AttendanceRepository,
+) {
+ suspend operator fun invoke(eventId: String) = attendanceRepository.getAttendance(eventId)
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/attendance/GetEventListAttendanceUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/attendance/GetEventListAttendanceUseCase.kt
new file mode 100644
index 000000000..abc915475
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/attendance/GetEventListAttendanceUseCase.kt
@@ -0,0 +1,27 @@
+package com.wap.wapp.core.domain.usecase.attendance
+
+import com.wap.wapp.core.data.repository.attendance.AttendanceRepository
+import com.wap.wapp.core.model.attendance.Attendance
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.coroutineScope
+import javax.inject.Inject
+
+class GetEventListAttendanceUseCase @Inject constructor(
+ private val attendanceRepository: AttendanceRepository,
+) {
+ // ์ค๋ ์ผ์ ์ค, ์ถ์์ด ์์๋ ์ผ์ ๋ค์ ๊ฐ์ ธ์ต๋๋ค.
+ suspend operator fun invoke(eventIdList: List): Result> =
+ runCatching {
+ // eventIdList๋ฅผ ๋ฐํ์ผ๋ก ์ถ์์ด ์ด๋ ธ๋์ง ๋ณ๋ ฌ์ ์ผ๋ก ๋ชจ๋ ํ์ธํฉ๋๋ค.
+ coroutineScope {
+ val deferredList = eventIdList.map { eventId ->
+ async { attendanceRepository.getAttendance(eventId = eventId) }
+ }
+
+ deferredList.awaitAll()
+ .map { it.getOrThrow() }
+ .filter { it.isBeforeEndTime() }
+ }
+ }
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/attendance/PostAttendanceUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/attendance/PostAttendanceUseCase.kt
new file mode 100644
index 000000000..966c0596c
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/attendance/PostAttendanceUseCase.kt
@@ -0,0 +1,16 @@
+package com.wap.wapp.core.domain.usecase.attendance
+
+import com.wap.wapp.core.data.repository.attendance.AttendanceRepository
+import java.time.LocalDateTime
+import javax.inject.Inject
+
+class PostAttendanceUseCase @Inject constructor(
+ private val attendanceRepository: AttendanceRepository,
+) {
+ suspend operator fun invoke(
+ eventId: String,
+ code: String,
+ deadline: LocalDateTime,
+ ): Result =
+ attendanceRepository.postAttendance(eventId = eventId, code = code, deadline = deadline)
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/attendance/ValidationAttendanceCodeUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/attendance/ValidationAttendanceCodeUseCase.kt
new file mode 100644
index 000000000..05925e8dd
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/attendance/ValidationAttendanceCodeUseCase.kt
@@ -0,0 +1,15 @@
+package com.wap.wapp.core.domain.usecase.attendance
+
+import com.wap.wapp.core.data.repository.attendance.AttendanceRepository
+import javax.inject.Inject
+
+class ValidationAttendanceCodeUseCase @Inject constructor(
+ private val attendanceRepository: AttendanceRepository,
+) {
+ suspend operator fun invoke(eventId: String, attendanceCode: String): Result =
+ runCatching {
+ attendanceRepository.getAttendance(eventId).map {
+ it.code == attendanceCode
+ }.getOrThrow()
+ }
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/attendancestatus/GetEventAttendanceStatusUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/attendancestatus/GetEventAttendanceStatusUseCase.kt
new file mode 100644
index 000000000..5f1a14b15
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/attendancestatus/GetEventAttendanceStatusUseCase.kt
@@ -0,0 +1,11 @@
+package com.wap.wapp.core.domain.usecase.attendancestatus
+
+import com.wap.wapp.core.data.repository.attendancestatus.AttendanceStatusRepository
+import javax.inject.Inject
+
+class GetEventAttendanceStatusUseCase @Inject constructor(
+ private val attendanceStatusRepository: AttendanceStatusRepository,
+) {
+ suspend operator fun invoke(eventId: String, userId: String) =
+ attendanceStatusRepository.getAttendanceStatus(eventId = eventId, userId = userId)
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/attendancestatus/GetEventListAttendanceStatusUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/attendancestatus/GetEventListAttendanceStatusUseCase.kt
new file mode 100644
index 000000000..923013b66
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/attendancestatus/GetEventListAttendanceStatusUseCase.kt
@@ -0,0 +1,33 @@
+package com.wap.wapp.core.domain.usecase.attendancestatus
+
+import com.wap.wapp.core.data.repository.attendancestatus.AttendanceStatusRepository
+import com.wap.wapp.core.model.attendancestatus.AttendanceStatus
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.coroutineScope
+import javax.inject.Inject
+
+class GetEventListAttendanceStatusUseCase @Inject constructor(
+ private val attendanceStatusRepository: AttendanceStatusRepository,
+) {
+ // eventIdList๋ฅผ ๋ฐ์์, ํด๋น ์ผ์ ์ ์ฌ์ฉ์๊ฐ ์ถ์์ ํ๋์ง, ํ์ง ์์๋ ์ง ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
+ suspend operator fun invoke(
+ eventIdList: List,
+ userId: String,
+ ): Result> = runCatching {
+ coroutineScope {
+ val deferredList = eventIdList.map { eventId ->
+ async {
+ attendanceStatusRepository.getAttendanceStatus(
+ eventId = eventId,
+ userId = userId,
+ )
+ }
+ }
+
+ deferredList.awaitAll().map {
+ it.getOrThrow()
+ }
+ }
+ }
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/attendancestatus/PostAttendanceStatusUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/attendancestatus/PostAttendanceStatusUseCase.kt
new file mode 100644
index 000000000..75de717d9
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/attendancestatus/PostAttendanceStatusUseCase.kt
@@ -0,0 +1,11 @@
+package com.wap.wapp.core.domain.usecase.attendancestatus
+
+import com.wap.wapp.core.data.repository.attendancestatus.AttendanceStatusRepository
+import javax.inject.Inject
+
+class PostAttendanceStatusUseCase @Inject constructor(
+ private val attendanceStatusRepository: AttendanceStatusRepository,
+) {
+ suspend operator fun invoke(eventId: String, userId: String): Result =
+ attendanceStatusRepository.postAttendance(eventId = eventId, userId = userId)
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/auth/CheckMemberCodeUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/auth/CheckMemberCodeUseCase.kt
new file mode 100644
index 000000000..0dcf0d647
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/auth/CheckMemberCodeUseCase.kt
@@ -0,0 +1,21 @@
+package com.wap.wapp.core.domain.usecase.auth
+
+import com.wap.wapp.core.data.repository.auth.AuthRepository
+import com.wap.wapp.core.domain.model.CodeValidation
+import javax.inject.Inject
+
+class CheckMemberCodeUseCase @Inject constructor(
+ private val authRepository: AuthRepository,
+) {
+ suspend operator fun invoke(code: String): Result = runCatching {
+ authRepository.checkMemberCode(code).fold(
+ onSuccess = { isValid ->
+ if (isValid) {
+ return@fold CodeValidation.VALID
+ }
+ CodeValidation.INVALID
+ },
+ onFailure = { CodeValidation.INVALID },
+ )
+ }
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/auth/DeleteUserUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/auth/DeleteUserUseCase.kt
new file mode 100644
index 000000000..90219486e
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/auth/DeleteUserUseCase.kt
@@ -0,0 +1,35 @@
+package com.wap.wapp.core.domain.usecase.auth
+
+import com.wap.wapp.core.data.repository.auth.AuthRepository
+import com.wap.wapp.core.data.repository.management.ManagementRepository
+import com.wap.wapp.core.data.repository.user.UserRepository
+import com.wap.wapp.core.domain.usecase.user.GetUserRoleUseCase
+import com.wap.wapp.core.model.user.UserRole
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class DeleteUserUseCase @Inject constructor(
+ private val authRepository: AuthRepository,
+ private val userRepository: UserRepository,
+ private val managementRepository: ManagementRepository,
+ private val getUserRoleUseCase: GetUserRoleUseCase,
+) {
+ suspend operator fun invoke(userId: String): Result = runCatching {
+ val userRole = getUserRoleUseCase().getOrThrow()
+ when (userRole) {
+ UserRole.GUEST -> { return@runCatching }
+
+ UserRole.MEMBER -> {
+ userRepository.deleteUserProfile(userId)
+ }
+
+ UserRole.MANAGER -> {
+ userRepository.deleteUserProfile(userId)
+ managementRepository.deleteManager(userId)
+ }
+ }
+
+ authRepository.deleteUser().getOrThrow()
+ }
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/auth/IsUserSignInUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/auth/IsUserSignInUseCase.kt
new file mode 100644
index 000000000..04f910257
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/auth/IsUserSignInUseCase.kt
@@ -0,0 +1,10 @@
+package com.wap.wapp.core.domain.usecase.auth
+
+import com.wap.wapp.core.data.repository.auth.AuthRepository
+import javax.inject.Inject
+
+class IsUserSignInUseCase @Inject constructor(
+ private val authRepository: AuthRepository,
+) {
+ suspend operator fun invoke(): Result = authRepository.isUserSignIn()
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/auth/SignInUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/auth/SignInUseCase.kt
new file mode 100644
index 000000000..511c159cd
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/auth/SignInUseCase.kt
@@ -0,0 +1,33 @@
+package com.wap.wapp.core.domain.usecase.auth
+
+import com.wap.wapp.core.data.repository.auth.SignInRepository
+import com.wap.wapp.core.data.repository.user.UserRepository
+import com.wap.wapp.core.domain.model.AuthState
+import com.wap.wapp.core.domain.model.AuthState.SIGN_IN
+import com.wap.wapp.core.domain.model.AuthState.SIGN_UP
+import dagger.hilt.android.scopes.ActivityScoped
+import javax.inject.Inject
+
+@ActivityScoped
+class SignInUseCase @Inject constructor(
+ private val signInRepository: SignInRepository,
+ private val userRepository: UserRepository,
+) {
+ suspend operator fun invoke(email: String): Result = runCatching {
+ val userId = signInRepository.signIn(email)
+ .getOrThrow()
+
+ userRepository.getUserProfile(userId)
+ .fold(
+ onFailure = { exception ->
+ // ๋ฑ๋ก๋์ง ์์ ์ฌ์ฉ์์ธ ๊ฒฝ์ฐ
+ if (exception is IllegalStateException) {
+ return@fold SIGN_UP
+ }
+ // ๊ทธ ์ธ์ ์์ธ์ธ ๊ฒฝ์ฐ
+ throw (exception)
+ },
+ onSuccess = { SIGN_IN },
+ )
+ }
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/auth/SignOutUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/auth/SignOutUseCase.kt
new file mode 100644
index 000000000..eae7758be
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/auth/SignOutUseCase.kt
@@ -0,0 +1,13 @@
+package com.wap.wapp.core.domain.usecase.auth
+
+import com.wap.wapp.core.data.repository.auth.AuthRepository
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class SignOutUseCase @Inject constructor(
+ private val authRepository: AuthRepository,
+) {
+ suspend operator fun invoke(): Result =
+ authRepository.signOut()
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/event/DeleteEventAndRelatedSurveysUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/event/DeleteEventAndRelatedSurveysUseCase.kt
new file mode 100644
index 000000000..fc4970cdb
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/event/DeleteEventAndRelatedSurveysUseCase.kt
@@ -0,0 +1,29 @@
+package com.wap.wapp.core.domain.usecase.event
+
+import com.wap.wapp.core.data.repository.event.EventRepository
+import com.wap.wapp.core.data.repository.survey.SurveyFormRepository
+import com.wap.wapp.core.data.repository.survey.SurveyRepository
+import javax.inject.Inject
+
+class DeleteEventAndRelatedSurveysUseCase @Inject constructor(
+ private val eventRepository: EventRepository,
+ private val surveyFormRepository: SurveyFormRepository,
+ private val surveyRepository: SurveyRepository,
+) {
+ // ์ผ์ ์ ์ญ์ ํฉ๋๋ค. ์ถ๊ฐ๋ก ์ด์ ๊ด๋ จ๋ ์ค๋ฌธ ํผ, ์์ฑ๋ ์ค๋ฌธ๋ค์ ์ ๊ฑฐํฉ๋๋ค.
+ suspend operator fun invoke(eventId: String): Result = runCatching {
+ eventRepository.deleteEvent(eventId).getOrThrow()
+
+ surveyFormRepository.getSurveyFormListByEventId(eventId).map { surveyFormList ->
+ surveyFormList.map { surveyForm ->
+ surveyFormRepository.deleteSurveyForm(surveyForm.surveyFormId)
+ }
+ }.getOrThrow()
+
+ surveyRepository.getSurveyListByEventId(eventId).map { surveyList ->
+ surveyList.map { survey ->
+ surveyRepository.deleteSurvey(survey.surveyId)
+ }
+ }.getOrThrow()
+ }
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/event/GetDateEventListUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/event/GetDateEventListUseCase.kt
new file mode 100644
index 000000000..a37684209
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/event/GetDateEventListUseCase.kt
@@ -0,0 +1,13 @@
+package com.wap.wapp.core.domain.usecase.event
+
+import com.wap.wapp.core.data.repository.event.EventRepository
+import com.wap.wapp.core.model.event.Event
+import java.time.LocalDate
+import javax.inject.Inject
+
+class GetDateEventListUseCase @Inject constructor(
+ private val eventRepository: EventRepository,
+) {
+ suspend operator fun invoke(date: LocalDate): Result> =
+ eventRepository.getDateEventList(date)
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/event/GetEventListUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/event/GetEventListUseCase.kt
new file mode 100644
index 000000000..8521d36a5
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/event/GetEventListUseCase.kt
@@ -0,0 +1,11 @@
+package com.wap.wapp.core.domain.usecase.event
+
+import com.wap.wapp.core.data.repository.event.EventRepository
+import com.wap.wapp.core.model.event.Event
+import javax.inject.Inject
+
+class GetEventListUseCase @Inject constructor(
+ private val eventRepository: EventRepository,
+) {
+ suspend operator fun invoke(): Result> = eventRepository.getEventList()
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/event/GetEventUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/event/GetEventUseCase.kt
new file mode 100644
index 000000000..adbea66df
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/event/GetEventUseCase.kt
@@ -0,0 +1,10 @@
+package com.wap.wapp.core.domain.usecase.event
+
+import com.wap.wapp.core.data.repository.event.EventRepository
+import javax.inject.Inject
+
+class GetEventUseCase @Inject constructor(
+ private val eventRepository: EventRepository,
+) {
+ suspend operator fun invoke(eventId: String) = eventRepository.getEvent(eventId)
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/event/GetMonthEventListUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/event/GetMonthEventListUseCase.kt
new file mode 100644
index 000000000..e7fb265d2
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/event/GetMonthEventListUseCase.kt
@@ -0,0 +1,13 @@
+package com.wap.wapp.core.domain.usecase.event
+
+import com.wap.wapp.core.data.repository.event.EventRepository
+import com.wap.wapp.core.model.event.Event
+import java.time.LocalDate
+import javax.inject.Inject
+
+class GetMonthEventListUseCase @Inject constructor(
+ private val eventRepository: EventRepository,
+) {
+ suspend operator fun invoke(date: LocalDate): Result> =
+ eventRepository.getMonthEventList(date)
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/event/GetRecentEventListUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/event/GetRecentEventListUseCase.kt
new file mode 100644
index 000000000..7cd5445d3
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/event/GetRecentEventListUseCase.kt
@@ -0,0 +1,19 @@
+package com.wap.wapp.core.domain.usecase.event
+
+import com.wap.wapp.core.data.repository.event.EventRepository
+import com.wap.wapp.core.model.event.Event
+import java.time.LocalDate
+import java.time.ZoneId
+import java.time.temporal.ChronoUnit
+import javax.inject.Inject
+
+class GetRecentEventListUseCase @Inject constructor(
+ private val eventRepository: EventRepository,
+) {
+ suspend operator fun invoke(registrationDate: LocalDate): Result> {
+ val currentDate = LocalDate.now(ZoneId.of("Asia/Seoul"))
+ val minimumDate = currentDate.minus(3, ChronoUnit.MONTHS)
+ val selectedDate = maxOf(registrationDate, minimumDate)
+ return eventRepository.getEventListFromDate(selectedDate)
+ }
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/event/PostEventUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/event/PostEventUseCase.kt
new file mode 100644
index 000000000..b0a286acc
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/event/PostEventUseCase.kt
@@ -0,0 +1,29 @@
+package com.wap.wapp.core.domain.usecase.event
+
+import com.wap.wapp.core.data.repository.event.EventRepository
+import java.time.LocalDate
+import java.time.LocalDateTime
+import java.time.LocalTime
+import javax.inject.Inject
+
+class PostEventUseCase @Inject constructor(
+ private val eventRepository: EventRepository,
+) {
+ suspend operator fun invoke(
+ eventTitle: String,
+ eventContent: String,
+ eventLocation: String,
+ eventStartDate: LocalDate,
+ eventStartTime: LocalTime,
+ eventEndDate: LocalDate,
+ eventEndTime: LocalTime,
+ ): Result = runCatching {
+ eventRepository.postEvent(
+ title = eventTitle,
+ content = eventContent,
+ location = eventLocation,
+ startDateTime = LocalDateTime.of(eventStartDate, eventStartTime),
+ endDateTime = LocalDateTime.of(eventEndDate, eventEndTime),
+ )
+ }
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/event/UpdateEventUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/event/UpdateEventUseCase.kt
new file mode 100644
index 000000000..9c209253a
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/event/UpdateEventUseCase.kt
@@ -0,0 +1,31 @@
+package com.wap.wapp.core.domain.usecase.event
+
+import com.wap.wapp.core.data.repository.event.EventRepository
+import java.time.LocalDate
+import java.time.LocalDateTime
+import java.time.LocalTime
+import javax.inject.Inject
+
+class UpdateEventUseCase @Inject constructor(
+ private val eventRepository: EventRepository,
+) {
+ suspend operator fun invoke(
+ eventId: String,
+ eventTitle: String,
+ eventContent: String,
+ eventLocation: String,
+ eventStartDate: LocalDate,
+ eventStartTime: LocalTime,
+ eventEndDate: LocalDate,
+ eventEndTime: LocalTime,
+ ): Result = runCatching {
+ eventRepository.updateEvent(
+ eventId = eventId,
+ title = eventTitle,
+ content = eventContent,
+ location = eventLocation,
+ startDateTime = LocalDateTime.of(eventStartDate, eventStartTime),
+ endDateTime = LocalDateTime.of(eventEndDate, eventEndTime),
+ )
+ }
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/management/CheckManagementCodeUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/management/CheckManagementCodeUseCase.kt
new file mode 100644
index 000000000..999cdd644
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/management/CheckManagementCodeUseCase.kt
@@ -0,0 +1,29 @@
+package com.wap.wapp.core.domain.usecase.management
+
+import com.wap.wapp.core.data.repository.management.ManagementRepository
+import com.wap.wapp.core.data.repository.user.UserRepository
+import com.wap.wapp.core.domain.model.CodeValidation
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class CheckManagementCodeUseCase @Inject constructor(
+ private val managementRepository: ManagementRepository,
+ private val userRepository: UserRepository,
+) {
+ suspend operator fun invoke(code: String): Result = runCatching {
+ managementRepository.checkManagementCode(code)
+ .onSuccess { isValid ->
+ if (isValid.not()) { // ์ฝ๋๊ฐ ํ๋ ธ์ ๊ฒฝ์ฐ
+ return@runCatching CodeValidation.INVALID
+ }
+ }
+
+ // ์ด์์ง ๋ฑ๋ก
+ val userId = userRepository.getUserId().getOrThrow()
+ managementRepository.postManager(userId).fold(
+ onSuccess = { CodeValidation.VALID },
+ onFailure = { exception -> throw (exception) },
+ )
+ }
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/DeleteSurveyFormAndRelatedSurveysUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/DeleteSurveyFormAndRelatedSurveysUseCase.kt
new file mode 100644
index 000000000..4547e5980
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/DeleteSurveyFormAndRelatedSurveysUseCase.kt
@@ -0,0 +1,20 @@
+package com.wap.wapp.core.domain.usecase.survey
+
+import com.wap.wapp.core.data.repository.survey.SurveyFormRepository
+import com.wap.wapp.core.data.repository.survey.SurveyRepository
+import javax.inject.Inject
+
+class DeleteSurveyFormAndRelatedSurveysUseCase @Inject constructor(
+ private val surveyFormRepository: SurveyFormRepository,
+ private val surveyRepository: SurveyRepository,
+) {
+ // ์ค๋ฌธ ํผ์ ์ญ์ ํฉ๋๋ค. ์ถ๊ฐ๋ก ์ด์ ๊ด๋ จ๋ ์์ฑ๋ ์ค๋ฌธ๋ค์ ์ ๊ฑฐํฉ๋๋ค.
+ suspend operator fun invoke(surveyFormId: String): Result = runCatching {
+ surveyFormRepository.deleteSurveyForm(surveyFormId).getOrThrow()
+ surveyRepository.getSurveyListBySurveyFormId(surveyFormId).map { surveyList ->
+ surveyList.map { survey ->
+ surveyRepository.deleteSurvey(survey.surveyId)
+ }
+ }.getOrThrow()
+ }
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/DeleteSurveyUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/DeleteSurveyUseCase.kt
new file mode 100644
index 000000000..57190bc89
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/DeleteSurveyUseCase.kt
@@ -0,0 +1,11 @@
+package com.wap.wapp.core.domain.usecase.survey
+
+import com.wap.wapp.core.data.repository.survey.SurveyRepository
+import javax.inject.Inject
+
+class DeleteSurveyUseCase @Inject constructor(
+ private val surveyRepository: SurveyRepository,
+) {
+ suspend operator fun invoke(surveyId: String): Result =
+ surveyRepository.deleteSurvey(surveyId)
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/GetSurveyFormListUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/GetSurveyFormListUseCase.kt
new file mode 100644
index 000000000..898087130
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/GetSurveyFormListUseCase.kt
@@ -0,0 +1,13 @@
+package com.wap.wapp.core.domain.usecase.survey
+
+import com.wap.wapp.core.data.repository.survey.SurveyFormRepository
+import javax.inject.Inject
+
+class GetSurveyFormListUseCase @Inject constructor(
+ private val surveyFormRepository: SurveyFormRepository,
+) {
+ suspend operator fun invoke() = surveyFormRepository.getSurveyFormList()
+
+ suspend operator fun invoke(eventId: String) =
+ surveyFormRepository.getSurveyFormListByEventId(eventId)
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/GetSurveyFormUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/GetSurveyFormUseCase.kt
new file mode 100644
index 000000000..424be5deb
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/GetSurveyFormUseCase.kt
@@ -0,0 +1,11 @@
+package com.wap.wapp.core.domain.usecase.survey
+
+import com.wap.wapp.core.data.repository.survey.SurveyFormRepository
+import javax.inject.Inject
+
+class GetSurveyFormUseCase @Inject constructor(
+ private val surveyFormRepository: SurveyFormRepository,
+) {
+ suspend operator fun invoke(surveyFormId: String) =
+ surveyFormRepository.getSurveyForm(surveyFormId)
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/GetSurveyListUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/GetSurveyListUseCase.kt
new file mode 100644
index 000000000..be3cbf8dc
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/GetSurveyListUseCase.kt
@@ -0,0 +1,12 @@
+package com.wap.wapp.core.domain.usecase.survey
+
+import com.wap.wapp.core.data.repository.survey.SurveyRepository
+import com.wap.wapp.core.model.survey.Survey
+import javax.inject.Inject
+
+class GetSurveyListUseCase @Inject constructor(
+ private val surveyRepository: SurveyRepository,
+) {
+ suspend operator fun invoke(): Result> =
+ surveyRepository.getSurveyList()
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/GetSurveyUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/GetSurveyUseCase.kt
new file mode 100644
index 000000000..6400d782f
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/GetSurveyUseCase.kt
@@ -0,0 +1,12 @@
+package com.wap.wapp.core.domain.usecase.survey
+
+import com.wap.wapp.core.data.repository.survey.SurveyRepository
+import com.wap.wapp.core.model.survey.Survey
+import javax.inject.Inject
+
+class GetSurveyUseCase @Inject constructor(
+ private val surveyRepository: SurveyRepository,
+) {
+ suspend operator fun invoke(surveyId: String): Result =
+ surveyRepository.getSurvey(surveyId)
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/GetUserRespondedSurveyListUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/GetUserRespondedSurveyListUseCase.kt
new file mode 100644
index 000000000..d4697af5f
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/GetUserRespondedSurveyListUseCase.kt
@@ -0,0 +1,12 @@
+package com.wap.wapp.core.domain.usecase.survey
+
+import com.wap.wapp.core.data.repository.survey.SurveyRepository
+import com.wap.wapp.core.model.survey.Survey
+import javax.inject.Inject
+
+class GetUserRespondedSurveyListUseCase @Inject constructor(
+ private val surveyRepository: SurveyRepository,
+) {
+ suspend operator fun invoke(userId: String): Result> =
+ surveyRepository.getUserRespondedSurveyList(userId)
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/IsSubmittedSurveyUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/IsSubmittedSurveyUseCase.kt
new file mode 100644
index 000000000..f2fca1b67
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/IsSubmittedSurveyUseCase.kt
@@ -0,0 +1,19 @@
+package com.wap.wapp.core.domain.usecase.survey
+
+import com.wap.wapp.core.data.repository.survey.SurveyRepository
+import com.wap.wapp.core.data.repository.user.UserRepository
+import javax.inject.Inject
+
+class IsSubmittedSurveyUseCase @Inject constructor(
+ private val userRepository: UserRepository,
+ private val surveyRepository: SurveyRepository,
+) {
+ suspend operator fun invoke(surveyFormId: String): Result = runCatching {
+ val userId = userRepository.getUserId().getOrThrow()
+
+ surveyRepository.isSubmittedSurvey(
+ userId = userId,
+ surveyFormId = surveyFormId,
+ ).getOrThrow()
+ }
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/PostSurveyFormUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/PostSurveyFormUseCase.kt
new file mode 100644
index 000000000..6fcfca3d8
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/PostSurveyFormUseCase.kt
@@ -0,0 +1,30 @@
+package com.wap.wapp.core.domain.usecase.survey
+
+import com.wap.wapp.core.data.repository.survey.SurveyFormRepository
+import com.wap.wapp.core.model.event.Event
+import com.wap.wapp.core.model.survey.SurveyQuestion
+import java.time.LocalDate
+import java.time.LocalDateTime
+import java.time.LocalTime
+import javax.inject.Inject
+
+class PostSurveyFormUseCase @Inject constructor(
+ private val surveyFormRepository: SurveyFormRepository,
+) {
+ suspend operator fun invoke(
+ event: Event,
+ title: String,
+ content: String,
+ surveyQuestionList: List,
+ deadlineDate: LocalDate,
+ deadlineTime: LocalTime,
+ ): Result = runCatching {
+ surveyFormRepository.postSurveyForm(
+ eventId = event.eventId,
+ title = title,
+ content = content,
+ surveyQuestionList = surveyQuestionList,
+ deadline = LocalDateTime.of(deadlineDate, deadlineTime),
+ )
+ }
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/PostSurveyUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/PostSurveyUseCase.kt
new file mode 100644
index 000000000..981ca5682
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/PostSurveyUseCase.kt
@@ -0,0 +1,32 @@
+package com.wap.wapp.core.domain.usecase.survey
+
+import com.wap.wapp.core.data.repository.survey.SurveyRepository
+import com.wap.wapp.core.data.repository.user.UserRepository
+import com.wap.wapp.core.model.survey.SurveyAnswer
+import java.time.LocalDateTime
+import javax.inject.Inject
+
+class PostSurveyUseCase @Inject constructor(
+ private val userRepository: UserRepository,
+ private val surveyRepository: SurveyRepository,
+) {
+ suspend operator fun invoke(
+ surveyFormId: String,
+ eventId: String,
+ title: String,
+ content: String,
+ surveyAnswerList: List,
+ ): Result = runCatching {
+ val userId = userRepository.getUserId().getOrThrow()
+
+ surveyRepository.postSurvey(
+ surveyFormId = surveyFormId,
+ userId = userId,
+ eventId = eventId,
+ title = title,
+ content = content,
+ surveyAnswerList = surveyAnswerList,
+ surveyedAt = LocalDateTime.now(),
+ )
+ }
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/UpdateSurveyFormUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/UpdateSurveyFormUseCase.kt
new file mode 100644
index 000000000..c88af0b1b
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/UpdateSurveyFormUseCase.kt
@@ -0,0 +1,13 @@
+package com.wap.wapp.core.domain.usecase.survey
+
+import com.wap.wapp.core.data.repository.survey.SurveyFormRepository
+import com.wap.wapp.core.model.survey.SurveyForm
+import javax.inject.Inject
+
+class UpdateSurveyFormUseCase @Inject constructor(
+ private val surveyFormRepository: SurveyFormRepository,
+) {
+ suspend operator fun invoke(surveyForm: SurveyForm): Result = runCatching {
+ surveyFormRepository.updateSurveyForm(surveyForm)
+ }
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/user/GetUserProfileUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/user/GetUserProfileUseCase.kt
new file mode 100644
index 000000000..3df73744c
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/user/GetUserProfileUseCase.kt
@@ -0,0 +1,16 @@
+package com.wap.wapp.core.domain.usecase.user
+
+import com.wap.wapp.core.data.repository.user.UserRepository
+import com.wap.wapp.core.model.user.UserProfile
+import javax.inject.Inject
+
+class GetUserProfileUseCase @Inject constructor(private val userRepository: UserRepository) {
+ suspend operator fun invoke(): Result = runCatching {
+ val userId = userRepository.getUserId().getOrThrow()
+
+ userRepository.getUserProfile(userId).fold(
+ onSuccess = { userProfile -> userProfile },
+ onFailure = { throw it },
+ )
+ }
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/user/GetUserRoleUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/user/GetUserRoleUseCase.kt
new file mode 100644
index 000000000..878acfe52
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/user/GetUserRoleUseCase.kt
@@ -0,0 +1,35 @@
+package com.wap.wapp.core.domain.usecase.user
+
+import com.wap.wapp.core.data.repository.management.ManagementRepository
+import com.wap.wapp.core.data.repository.user.UserRepository
+import com.wap.wapp.core.model.user.UserRole
+import javax.inject.Inject
+
+class GetUserRoleUseCase @Inject constructor(
+ private val userRepository: UserRepository,
+ private val managementRepository: ManagementRepository,
+) {
+ suspend operator fun invoke(): Result =
+ runCatching {
+ val userId = userRepository.getUserId()
+ .getOrElse { exception ->
+ if (exception is IllegalStateException) { // ํ์์ด ์๋ ๊ฒฝ์ฐ
+ return@runCatching UserRole.GUEST
+ }
+ throw exception
+ }
+
+ managementRepository.isManager(userId)
+ .fold(
+ onSuccess = { isManager ->
+ if (isManager) { // ๋งค๋์ ์ธ ๊ฒฝ์ฐ
+ return@fold UserRole.MANAGER
+ }
+ UserRole.MEMBER
+ },
+ onFailure = { exception ->
+ throw exception
+ },
+ )
+ }
+}
diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/user/PostUserProfileUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/user/PostUserProfileUseCase.kt
new file mode 100644
index 000000000..d6c9b71a0
--- /dev/null
+++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/user/PostUserProfileUseCase.kt
@@ -0,0 +1,25 @@
+package com.wap.wapp.core.domain.usecase.user
+
+import com.wap.wapp.core.data.repository.user.UserRepository
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class PostUserProfileUseCase @Inject constructor(
+ private val userRepository: UserRepository,
+) {
+ suspend operator fun invoke(
+ userName: String,
+ studentId: String,
+ registeredAt: String,
+ ): Result = runCatching {
+ val userId = userRepository.getUserId().getOrThrow()
+
+ userRepository.postUserProfile(
+ userId = userId,
+ userName = userName,
+ studentId = studentId,
+ registeredAt = registeredAt,
+ )
+ }
+}
diff --git a/core/domain/src/test/java/com/wap/wapp/core/domain/ExampleUnitTest.kt b/core/domain/src/test/java/com/wap/wapp/core/domain/ExampleUnitTest.kt
new file mode 100644
index 000000000..a899a99bd
--- /dev/null
+++ b/core/domain/src/test/java/com/wap/wapp/core/domain/ExampleUnitTest.kt
@@ -0,0 +1,16 @@
+package com.wap.wapp.core.domain
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/core/model/.gitignore b/core/model/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/core/model/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core/model/build.gradle.kts b/core/model/build.gradle.kts
new file mode 100644
index 000000000..62383a1a4
--- /dev/null
+++ b/core/model/build.gradle.kts
@@ -0,0 +1,21 @@
+plugins {
+ id("com.wap.wapp.library")
+}
+
+android {
+ namespace = "com.wap.wapp.core.model"
+
+ defaultConfig {
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro",
+ )
+ }
+ }
+}
diff --git a/core/model/consumer-rules.pro b/core/model/consumer-rules.pro
new file mode 100644
index 000000000..e69de29bb
diff --git a/core/model/proguard-rules.pro b/core/model/proguard-rules.pro
new file mode 100644
index 000000000..481bb4348
--- /dev/null
+++ b/core/model/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/core/model/src/main/java/com/wap/wapp/core/model/attendance/Attendance.kt b/core/model/src/main/java/com/wap/wapp/core/model/attendance/Attendance.kt
new file mode 100644
index 000000000..ef0c5f5a3
--- /dev/null
+++ b/core/model/src/main/java/com/wap/wapp/core/model/attendance/Attendance.kt
@@ -0,0 +1,29 @@
+package com.wap.wapp.core.model.attendance
+
+import java.time.Duration
+import java.time.LocalDateTime
+import java.time.ZoneId
+import kotlin.time.toKotlinDuration
+
+data class Attendance(
+ val eventId: String,
+ val code: String,
+ val deadline: LocalDateTime,
+) {
+ fun calculateDeadline(): String {
+ val currentDateTime = LocalDateTime.now(TIME_ZONE_SEOUL)
+ val duration = Duration.between(currentDateTime, deadline)
+
+ val leftMinutes = duration.toKotlinDuration().inWholeMinutes.toString()
+ return "${leftMinutes}๋ถ ํ ๋ง๊ฐ"
+ }
+
+ fun isBeforeEndTime(): Boolean {
+ val currentDateTime = LocalDateTime.now(TIME_ZONE_SEOUL)
+ return currentDateTime.isBefore(deadline)
+ }
+
+ companion object {
+ val TIME_ZONE_SEOUL = ZoneId.of("Asia/Seoul")
+ }
+}
diff --git a/core/model/src/main/java/com/wap/wapp/core/model/attendancestatus/AttendanceStatus.kt b/core/model/src/main/java/com/wap/wapp/core/model/attendancestatus/AttendanceStatus.kt
new file mode 100644
index 000000000..a5f47b66e
--- /dev/null
+++ b/core/model/src/main/java/com/wap/wapp/core/model/attendancestatus/AttendanceStatus.kt
@@ -0,0 +1,7 @@
+package com.wap.wapp.core.model.attendancestatus
+
+data class AttendanceStatus(
+ val attendanceDateTime: String = "",
+) {
+ fun isAttendance() = attendanceDateTime.isNotEmpty()
+}
diff --git a/core/model/src/main/java/com/wap/wapp/core/model/event/Event.kt b/core/model/src/main/java/com/wap/wapp/core/model/event/Event.kt
new file mode 100644
index 000000000..b39957b6d
--- /dev/null
+++ b/core/model/src/main/java/com/wap/wapp/core/model/event/Event.kt
@@ -0,0 +1,72 @@
+package com.wap.wapp.core.model.event
+
+import java.time.Duration
+import java.time.LocalDateTime
+import java.time.ZoneId
+import java.time.format.DateTimeFormatter
+
+data class Event(
+ val content: String,
+ val eventId: String,
+ val location: String,
+ val title: String,
+ val startDateTime: LocalDateTime,
+ val endDateTime: LocalDateTime,
+) {
+ fun getCalculatedTime(): String {
+ val currentDateTime = LocalDateTime.now(TIME_ZONE_SEOUL)
+ if (startDateTime >= currentDateTime) {
+ return calculateStartTime()
+ }
+
+ if (endDateTime >= currentDateTime) {
+ return calculateDeadline()
+ }
+
+ return "๋ง๊ฐ"
+ }
+
+ private fun calculateStartTime(): String {
+ val currentDateTime = LocalDateTime.now(TIME_ZONE_SEOUL)
+ val duration = Duration.between(currentDateTime, startDateTime)
+
+ if (duration.toMinutes() < 60) {
+ val leftMinutes = duration.toMinutes().toString()
+ return leftMinutes + "๋ถ ํ ์์"
+ }
+
+ if (duration.toHours() < 24) {
+ val leftHours = duration.toHours().toString()
+ return leftHours + "์๊ฐ ํ ์์"
+ }
+
+ return endDateTime.format(yyyyMMddFormatter) + " ์์"
+ }
+
+ private fun calculateDeadline(): String {
+ val currentDateTime = LocalDateTime.now(TIME_ZONE_SEOUL)
+ val duration = Duration.between(currentDateTime, endDateTime)
+
+ if (duration.toMinutes() < 60) {
+ val leftMinutes = duration.toMinutes().toString()
+ return leftMinutes + "๋ถ ํ ์ข
๋ฃ"
+ }
+
+ if (duration.toHours() < 24) {
+ val leftHours = duration.toHours().toString()
+ return leftHours + "์๊ฐ ํ ์ข
๋ฃ"
+ }
+
+ return endDateTime.format(yyyyMMddFormatter) + " ๋ง๊ฐ"
+ }
+
+ fun isBeforeEndTime(): Boolean {
+ val currentDateTime = LocalDateTime.now(TIME_ZONE_SEOUL)
+ return currentDateTime.isBefore(endDateTime)
+ }
+
+ companion object {
+ val yyyyMMddFormatter = DateTimeFormatter.ofPattern("yyyy.MM.dd")
+ val TIME_ZONE_SEOUL = ZoneId.of("Asia/Seoul")
+ }
+}
diff --git a/core/model/src/main/java/com/wap/wapp/core/model/survey/Rating.kt b/core/model/src/main/java/com/wap/wapp/core/model/survey/Rating.kt
new file mode 100644
index 000000000..2a024b501
--- /dev/null
+++ b/core/model/src/main/java/com/wap/wapp/core/model/survey/Rating.kt
@@ -0,0 +1,22 @@
+package com.wap.wapp.core.model.survey
+
+enum class Rating {
+ GOOD, MEDIOCRE, BAD
+}
+
+data class RatingDescription(
+ val title: String,
+ val content: String,
+)
+
+fun Rating.toDescription(): RatingDescription = when (this) {
+ Rating.GOOD -> {
+ RatingDescription("์ข์", "ํ์ฌ ์งํ์ด ์๋ฒฝํ๊ณ , \nํ์ฌ ๋ด์ฉ์ด ๋ง์์.")
+ }
+ Rating.MEDIOCRE -> {
+ RatingDescription("๋ณดํต", "ํ์ฌ ์งํ์ด ์ํํ๊ณ , \nํ์ฌ ๋ด์ฉ์ด ์ ๋นํจ.")
+ }
+ Rating.BAD -> {
+ RatingDescription("๋์จ", "ํ์ฌ ์งํ์ด ์์ฌ์ ๊ณ , \nํ์ฌ ๋ด์ฉ์ด ๋ถ์กฑํจ.")
+ }
+}
diff --git a/core/model/src/main/java/com/wap/wapp/core/model/survey/Survey.kt b/core/model/src/main/java/com/wap/wapp/core/model/survey/Survey.kt
new file mode 100644
index 000000000..3f900b7a5
--- /dev/null
+++ b/core/model/src/main/java/com/wap/wapp/core/model/survey/Survey.kt
@@ -0,0 +1,50 @@
+package com.wap.wapp.core.model.survey
+
+import java.time.Duration
+import java.time.LocalDateTime
+import java.time.ZoneId
+import java.time.format.DateTimeFormatter
+
+/*
+ํ์์ด ์์ฑํ๋ ์ค๋ฌธ ๋ชจ๋ธ
+*/
+
+data class Survey(
+ val surveyId: String,
+ val surveyFormId: String,
+ val eventName: String,
+ val userName: String,
+ val title: String,
+ val content: String,
+ val surveyAnswerList: List,
+ val surveyedAt: LocalDateTime,
+) {
+ fun calculateSurveyedAt(): String {
+ val currentDateTime = LocalDateTime.now(TIME_ZONE_SEOUL)
+ val duration = Duration.between(surveyedAt, currentDateTime)
+
+ if (duration.toMinutes() == 0L) {
+ return "๋ฐฉ๊ธ"
+ } else if (duration.toMinutes() < 60) {
+ val leftMinutes = duration.toMinutes().toString()
+ return leftMinutes + "๋ถ ์ "
+ }
+
+ if (duration.toHours() < 24) {
+ val leftHours = duration.toHours().toString()
+ return leftHours + "์๊ฐ ์ "
+ }
+
+ if (duration.toDays() < 31) {
+ val leftDays = duration.toDays().toString()
+ return leftDays + "์ผ ์ "
+ }
+
+ return surveyedAt.format(yyyyMMddFormatter)
+ }
+
+ companion object {
+ val yyyyMMddFormatter = DateTimeFormatter.ofPattern("yyyy.MM.dd")
+ val TIME_ZONE_SEOUL = ZoneId.of("Asia/Seoul")
+ }
+}
diff --git a/core/model/src/main/java/com/wap/wapp/core/model/survey/SurveyAnswer.kt b/core/model/src/main/java/com/wap/wapp/core/model/survey/SurveyAnswer.kt
new file mode 100644
index 000000000..6ff6c5358
--- /dev/null
+++ b/core/model/src/main/java/com/wap/wapp/core/model/survey/SurveyAnswer.kt
@@ -0,0 +1,7 @@
+package com.wap.wapp.core.model.survey
+
+data class SurveyAnswer(
+ val questionTitle: String,
+ val questionAnswer: String,
+ val questionType: QuestionType,
+)
diff --git a/core/model/src/main/java/com/wap/wapp/core/model/survey/SurveyForm.kt b/core/model/src/main/java/com/wap/wapp/core/model/survey/SurveyForm.kt
new file mode 100644
index 000000000..b2f831847
--- /dev/null
+++ b/core/model/src/main/java/com/wap/wapp/core/model/survey/SurveyForm.kt
@@ -0,0 +1,57 @@
+package com.wap.wapp.core.model.survey
+
+import java.time.Duration
+import java.time.LocalDateTime
+import java.time.ZoneId
+import java.time.format.DateTimeFormatter
+
+// ์ด์์ง์ด ๋ฑ๋กํ๋ ์ค๋ฌธ ๋ชจ๋ธ
+data class SurveyForm(
+ val surveyFormId: String,
+ val eventId: String,
+ val title: String,
+ val content: String,
+ val surveyQuestionList: List,
+ val deadline: LocalDateTime,
+) {
+ constructor() : this(
+ "",
+ "",
+ "",
+ "",
+ emptyList(),
+ LocalDateTime.MIN,
+ )
+
+ fun calculateDeadline(): String {
+ val currentDateTime = LocalDateTime.now(TIME_ZONE_SEOUL)
+ val duration = Duration.between(currentDateTime, deadline)
+
+ if (duration.toMinutes() < 60) {
+ val leftMinutes = duration.toMinutes().toString()
+ return leftMinutes + "๋ถ ํ ๋ง๊ฐ"
+ }
+
+ if (duration.toHours() < 24) {
+ val leftHours = duration.toHours().toString()
+ return leftHours + "์๊ฐ ํ ๋ง๊ฐ"
+ }
+
+ if (duration.toDays() < 31) {
+ val leftDays = duration.toDays().toString()
+ return leftDays + "์ผ ํ ๋ง๊ฐ"
+ }
+
+ return deadline.format(yyyyMMddFormatter) + " ๋ง๊ฐ"
+ }
+
+ fun isBeforeDeadline(): Boolean {
+ val currentDateTime = LocalDateTime.now(TIME_ZONE_SEOUL)
+ return currentDateTime.isBefore(deadline)
+ }
+
+ companion object {
+ val yyyyMMddFormatter = DateTimeFormatter.ofPattern("yyyy.MM.dd")
+ val TIME_ZONE_SEOUL = ZoneId.of("Asia/Seoul")
+ }
+}
diff --git a/core/model/src/main/java/com/wap/wapp/core/model/survey/SurveyQuestion.kt b/core/model/src/main/java/com/wap/wapp/core/model/survey/SurveyQuestion.kt
new file mode 100644
index 000000000..318dc30e5
--- /dev/null
+++ b/core/model/src/main/java/com/wap/wapp/core/model/survey/SurveyQuestion.kt
@@ -0,0 +1,10 @@
+package com.wap.wapp.core.model.survey
+
+data class SurveyQuestion(
+ val questionTitle: String,
+ val questionType: QuestionType,
+)
+
+enum class QuestionType {
+ SUBJECTIVE, OBJECTIVE
+}
diff --git a/core/model/src/main/java/com/wap/wapp/core/model/user/UserProfile.kt b/core/model/src/main/java/com/wap/wapp/core/model/user/UserProfile.kt
new file mode 100644
index 000000000..dfa5cd616
--- /dev/null
+++ b/core/model/src/main/java/com/wap/wapp/core/model/user/UserProfile.kt
@@ -0,0 +1,8 @@
+package com.wap.wapp.core.model.user
+
+data class UserProfile(
+ val userId: String,
+ val userName: String,
+ val studentId: String,
+ val registeredAt: String,
+)
diff --git a/core/model/src/main/java/com/wap/wapp/core/model/user/UserRole.kt b/core/model/src/main/java/com/wap/wapp/core/model/user/UserRole.kt
new file mode 100644
index 000000000..a159c242d
--- /dev/null
+++ b/core/model/src/main/java/com/wap/wapp/core/model/user/UserRole.kt
@@ -0,0 +1,5 @@
+package com.wap.wapp.core.model.user
+
+enum class UserRole {
+ GUEST, MEMBER, MANAGER
+}
diff --git a/core/network/.gitignore b/core/network/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/core/network/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core/network/build.gradle.kts b/core/network/build.gradle.kts
new file mode 100644
index 000000000..072c7ddb5
--- /dev/null
+++ b/core/network/build.gradle.kts
@@ -0,0 +1,30 @@
+plugins {
+ id("com.wap.wapp.library")
+ id("com.wap.wapp.hilt")
+}
+
+android {
+ namespace = "com.wap.wapp.core.network"
+
+ defaultConfig {
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro",
+ )
+ }
+ }
+}
+
+dependencies {
+ implementation(project(":core:model"))
+ implementation(platform(libs.firebase.bom))
+ implementation(libs.firebase.auth)
+ implementation(libs.firebase.firestore)
+}
diff --git a/core/network/consumer-rules.pro b/core/network/consumer-rules.pro
new file mode 100644
index 000000000..e69de29bb
diff --git a/core/network/proguard-rules.pro b/core/network/proguard-rules.pro
new file mode 100644
index 000000000..481bb4348
--- /dev/null
+++ b/core/network/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/core/network/src/androidTest/java/com/wap/wapp/core/network/ExampleInstrumentedTest.kt b/core/network/src/androidTest/java/com/wap/wapp/core/network/ExampleInstrumentedTest.kt
new file mode 100644
index 000000000..5a0aedeae
--- /dev/null
+++ b/core/network/src/androidTest/java/com/wap/wapp/core/network/ExampleInstrumentedTest.kt
@@ -0,0 +1,22 @@
+package com.wap.wapp.core.network
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.wap.wapp.core.network.test", appContext.packageName)
+ }
+}
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/constant/FirebaseConstant.kt b/core/network/src/main/java/com/wap/wapp/core/network/constant/FirebaseConstant.kt
new file mode 100644
index 000000000..6b63686b7
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/constant/FirebaseConstant.kt
@@ -0,0 +1,13 @@
+package com.wap.wapp.core.network.constant
+
+/*
+Firestore Collection, Document Id
+*/
+const val USER_COLLECTION = "users"
+const val MANAGER_COLLECTION = "managers"
+const val CODES_COLLECTION = "codes"
+const val SURVEY_COLLECTION = "surveys"
+const val EVENT_COLLECTION = "events"
+const val SURVEY_FORM_COLLECTION = "surveyForms"
+const val ATTENDANCE_COLLECTION = "attendances"
+const val ATTENDANCE_STATUS_COLLECTION = "attendanceStatus"
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/di/FirebaseModule.kt b/core/network/src/main/java/com/wap/wapp/core/network/di/FirebaseModule.kt
new file mode 100644
index 000000000..d47105c58
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/di/FirebaseModule.kt
@@ -0,0 +1,24 @@
+package com.wap.wapp.core.network.di
+
+import com.google.firebase.auth.FirebaseAuth
+import com.google.firebase.auth.ktx.auth
+import com.google.firebase.firestore.FirebaseFirestore
+import com.google.firebase.firestore.ktx.firestore
+import com.google.firebase.ktx.Firebase
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object FirebaseModule {
+ @Provides
+ @Singleton
+ fun providesFirebaseAuth(): FirebaseAuth = Firebase.auth
+
+ @Provides
+ @Singleton
+ fun providesFirestore(): FirebaseFirestore = Firebase.firestore
+}
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/di/NetworkModule.kt b/core/network/src/main/java/com/wap/wapp/core/network/di/NetworkModule.kt
new file mode 100644
index 000000000..b5ce3ec86
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/di/NetworkModule.kt
@@ -0,0 +1,76 @@
+package com.wap.wapp.core.network.di
+
+import com.wap.wapp.core.network.source.attendance.AttendanceDataSource
+import com.wap.wapp.core.network.source.attendance.AttendanceDataSourceImpl
+import com.wap.wapp.core.network.source.attendancestatus.AttendanceStatusDataSource
+import com.wap.wapp.core.network.source.attendancestatus.AttendanceStatusDataSourceImpl
+import com.wap.wapp.core.network.source.auth.AuthDataSource
+import com.wap.wapp.core.network.source.auth.AuthDataSourceImpl
+import com.wap.wapp.core.network.source.event.EventDataSource
+import com.wap.wapp.core.network.source.event.EventDataSourceImpl
+import com.wap.wapp.core.network.source.management.ManagementDataSource
+import com.wap.wapp.core.network.source.management.ManagementDataSourceImpl
+import com.wap.wapp.core.network.source.survey.SurveyDataSource
+import com.wap.wapp.core.network.source.survey.SurveyDataSourceImpl
+import com.wap.wapp.core.network.source.survey.SurveyFormDataSource
+import com.wap.wapp.core.network.source.survey.SurveyFormDataSourceImpl
+import com.wap.wapp.core.network.source.user.UserDataSource
+import com.wap.wapp.core.network.source.user.UserDataSourceImpl
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+abstract class NetworkModule {
+
+ @Binds
+ @Singleton
+ abstract fun bindsAuthDataSource(
+ authDataSourceImpl: AuthDataSourceImpl,
+ ): AuthDataSource
+
+ @Binds
+ @Singleton
+ abstract fun bindsUserDataSource(
+ userDataSourceImpl: UserDataSourceImpl,
+ ): UserDataSource
+
+ @Binds
+ @Singleton
+ abstract fun bindsManagementDataSource(
+ managementDataSourceImpl: ManagementDataSourceImpl,
+ ): ManagementDataSource
+
+ @Binds
+ @Singleton
+ abstract fun bindsSurveyDataSoruce(
+ surveyDataSourceImpl: SurveyDataSourceImpl,
+ ): SurveyDataSource
+
+ @Binds
+ @Singleton
+ abstract fun bindsSurveyFormDateSource(
+ surveyFormDataSourceImpl: SurveyFormDataSourceImpl,
+ ): SurveyFormDataSource
+
+ @Binds
+ @Singleton
+ abstract fun bindsEventDataSource(
+ eventDataSourceImpl: EventDataSourceImpl,
+ ): EventDataSource
+
+ @Binds
+ @Singleton
+ abstract fun bindsAttendanceDataSource(
+ attendanceDataSourceImpl: AttendanceDataSourceImpl,
+ ): AttendanceDataSource
+
+ @Binds
+ @Singleton
+ abstract fun bindsAttendanceStatusDataSource(
+ attendanceStatueDataSourceImpl: AttendanceStatusDataSourceImpl,
+ ): AttendanceStatusDataSource
+}
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/di/SignInDataSourceModule.kt b/core/network/src/main/java/com/wap/wapp/core/network/di/SignInDataSourceModule.kt
new file mode 100644
index 000000000..29c8acc27
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/di/SignInDataSourceModule.kt
@@ -0,0 +1,19 @@
+package com.wap.wapp.core.network.di
+
+import com.wap.wapp.core.network.source.auth.SignInDataSource
+import com.wap.wapp.core.network.source.auth.SignInDataSourceImpl
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.android.components.ActivityComponent
+import dagger.hilt.android.scopes.ActivityScoped
+
+@Module
+@InstallIn(ActivityComponent::class)
+abstract class SignInDataSourceModule {
+ @Binds
+ @ActivityScoped
+ abstract fun bindsSignInDataSource(
+ signInDataSourceImpl: SignInDataSourceImpl,
+ ): SignInDataSource
+}
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/model/attendance/AttendanceRequest.kt b/core/network/src/main/java/com/wap/wapp/core/network/model/attendance/AttendanceRequest.kt
new file mode 100644
index 000000000..401133cd9
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/model/attendance/AttendanceRequest.kt
@@ -0,0 +1,7 @@
+package com.wap.wapp.core.network.model.attendance
+
+data class AttendanceRequest(
+ val eventId: String = "",
+ val code: String = "",
+ val deadline: String = "",
+)
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/model/attendance/AttendanceResponse.kt b/core/network/src/main/java/com/wap/wapp/core/network/model/attendance/AttendanceResponse.kt
new file mode 100644
index 000000000..7f1497e18
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/model/attendance/AttendanceResponse.kt
@@ -0,0 +1,16 @@
+package com.wap.wapp.core.network.model.attendance
+
+import com.wap.wapp.core.model.attendance.Attendance
+import com.wap.wapp.core.network.utils.toISOLocalDateTime
+
+data class AttendanceResponse(
+ val eventId: String = "",
+ val code: String = "",
+ val deadline: String = "",
+) {
+ fun toDomain() = Attendance(
+ eventId = eventId,
+ code = code,
+ deadline = deadline.toISOLocalDateTime(),
+ )
+}
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/model/attendancestatus/AttendanceStatusRequest.kt b/core/network/src/main/java/com/wap/wapp/core/network/model/attendancestatus/AttendanceStatusRequest.kt
new file mode 100644
index 000000000..c84bc97e3
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/model/attendancestatus/AttendanceStatusRequest.kt
@@ -0,0 +1,5 @@
+package com.wap.wapp.core.network.model.attendancestatus
+
+data class AttendanceStatusRequest(
+ val attendanceDateTime: String = "",
+)
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/model/attendancestatus/AttendanceStatusResponse.kt b/core/network/src/main/java/com/wap/wapp/core/network/model/attendancestatus/AttendanceStatusResponse.kt
new file mode 100644
index 000000000..f464a7006
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/model/attendancestatus/AttendanceStatusResponse.kt
@@ -0,0 +1,9 @@
+package com.wap.wapp.core.network.model.attendancestatus
+
+import com.wap.wapp.core.model.attendancestatus.AttendanceStatus
+
+data class AttendanceStatusResponse(
+ val attendanceDateTime: String = "",
+) {
+ fun toDomain() = AttendanceStatus(attendanceDateTime)
+}
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/model/event/EventRequest.kt b/core/network/src/main/java/com/wap/wapp/core/network/model/event/EventRequest.kt
new file mode 100644
index 000000000..fde307bd2
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/model/event/EventRequest.kt
@@ -0,0 +1,10 @@
+package com.wap.wapp.core.network.model.event
+
+data class EventRequest(
+ val title: String = "",
+ val content: String = "",
+ val location: String = "",
+ val startDateTime: String = "",
+ val endDateTime: String = "",
+ val eventId: String = "",
+)
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/model/event/EventResponse.kt b/core/network/src/main/java/com/wap/wapp/core/network/model/event/EventResponse.kt
new file mode 100644
index 000000000..45a9f74ac
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/model/event/EventResponse.kt
@@ -0,0 +1,22 @@
+package com.wap.wapp.core.network.model.event
+
+import com.wap.wapp.core.model.event.Event
+import com.wap.wapp.core.network.utils.toISOLocalDateTime
+
+data class EventResponse(
+ val content: String = "",
+ val eventId: String = "",
+ val location: String = "",
+ val title: String = "",
+ val startDateTime: String = "",
+ val endDateTime: String = "",
+) {
+ fun toDomain() = Event(
+ content = content,
+ eventId = eventId,
+ location = location,
+ title = title,
+ startDateTime = startDateTime.toISOLocalDateTime(),
+ endDateTime = endDateTime.toISOLocalDateTime(),
+ )
+}
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/model/survey/SurveyAnswerResponse.kt b/core/network/src/main/java/com/wap/wapp/core/network/model/survey/SurveyAnswerResponse.kt
new file mode 100644
index 000000000..819d94b0a
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/model/survey/SurveyAnswerResponse.kt
@@ -0,0 +1,31 @@
+package com.wap.wapp.core.network.model.survey
+
+import com.wap.wapp.core.model.survey.QuestionType
+import com.wap.wapp.core.model.survey.SurveyAnswer
+
+data class SurveyAnswerResponse(
+ val questionTitle: String,
+ val questionAnswer: String,
+ val questionType: QuestionTypeResponse,
+) {
+ constructor() : this(
+ "",
+ "",
+ QuestionTypeResponse.SUBJECTIVE,
+ )
+
+ fun toDomain() = SurveyAnswer(
+ questionTitle = questionTitle,
+ questionAnswer = questionAnswer,
+ questionType = questionType.toDomain(),
+ )
+}
+
+enum class QuestionTypeResponse {
+ OBJECTIVE, SUBJECTIVE
+}
+
+internal fun QuestionTypeResponse.toDomain(): QuestionType = when (this) {
+ QuestionTypeResponse.SUBJECTIVE -> { QuestionType.SUBJECTIVE }
+ QuestionTypeResponse.OBJECTIVE -> { QuestionType.OBJECTIVE }
+}
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/model/survey/SurveyRequest.kt b/core/network/src/main/java/com/wap/wapp/core/network/model/survey/SurveyRequest.kt
new file mode 100644
index 000000000..2e97ab2e7
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/model/survey/SurveyRequest.kt
@@ -0,0 +1,14 @@
+package com.wap.wapp.core.network.model.survey
+
+import com.wap.wapp.core.model.survey.SurveyAnswer
+
+data class SurveyRequest(
+ val surveyId: String,
+ val surveyFormId: String,
+ val eventId: String,
+ val userId: String,
+ val title: String,
+ val content: String,
+ val surveyAnswerList: List,
+ val surveyedAt: String,
+)
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/model/survey/SurveyResponse.kt b/core/network/src/main/java/com/wap/wapp/core/network/model/survey/SurveyResponse.kt
new file mode 100644
index 000000000..59d548a9b
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/model/survey/SurveyResponse.kt
@@ -0,0 +1,37 @@
+package com.wap.wapp.core.network.model.survey
+
+import com.wap.wapp.core.model.survey.Survey
+import com.wap.wapp.core.network.utils.toISOLocalDateTime
+
+data class SurveyResponse(
+ val surveyId: String,
+ val surveyFormId: String,
+ val eventId: String,
+ val userId: String,
+ val title: String,
+ val content: String,
+ val surveyAnswerList: List,
+ val surveyedAt: String,
+) {
+ constructor() : this(
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ emptyList(),
+ "",
+ )
+
+ fun toDomain(eventName: String, userName: String): Survey = Survey(
+ surveyId = surveyId,
+ surveyFormId = surveyFormId,
+ eventName = eventName,
+ userName = userName,
+ title = this.title,
+ content = this.content,
+ surveyAnswerList = this.surveyAnswerList.map { answer -> answer.toDomain() },
+ surveyedAt = surveyedAt.toISOLocalDateTime(),
+ )
+}
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/model/survey/form/SurveyFormRequest.kt b/core/network/src/main/java/com/wap/wapp/core/network/model/survey/form/SurveyFormRequest.kt
new file mode 100644
index 000000000..a35507e61
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/model/survey/form/SurveyFormRequest.kt
@@ -0,0 +1,21 @@
+package com.wap.wapp.core.network.model.survey.form
+
+import com.wap.wapp.core.model.survey.SurveyQuestion
+
+data class SurveyFormRequest(
+ val surveyFormId: String,
+ val eventId: String,
+ val title: String,
+ val content: String,
+ val surveyQuestionList: List,
+ val deadline: String,
+) {
+ constructor() : this(
+ "",
+ "",
+ "",
+ "",
+ emptyList(),
+ "",
+ )
+}
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/model/survey/form/SurveyFormResponse.kt b/core/network/src/main/java/com/wap/wapp/core/network/model/survey/form/SurveyFormResponse.kt
new file mode 100644
index 000000000..c29171e68
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/model/survey/form/SurveyFormResponse.kt
@@ -0,0 +1,33 @@
+package com.wap.wapp.core.network.model.survey.form
+
+import com.wap.wapp.core.model.survey.SurveyForm
+import com.wap.wapp.core.network.utils.toISOLocalDateTime
+
+data class SurveyFormResponse(
+ val surveyFormId: String,
+ val eventId: String,
+ val userId: String,
+ val title: String,
+ val content: String,
+ val surveyQuestionList: List,
+ val deadline: String,
+) {
+ constructor() : this(
+ "",
+ "",
+ "",
+ "",
+ "",
+ emptyList(),
+ "",
+ )
+
+ fun toDomain(): SurveyForm = SurveyForm(
+ surveyFormId = surveyFormId,
+ eventId = eventId,
+ title = title,
+ content = content,
+ surveyQuestionList = surveyQuestionList.map { it.toDomain() },
+ deadline = deadline.toISOLocalDateTime(),
+ )
+}
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/model/survey/form/SurveyQuestionResponse.kt b/core/network/src/main/java/com/wap/wapp/core/network/model/survey/form/SurveyQuestionResponse.kt
new file mode 100644
index 000000000..c919e8ab1
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/model/survey/form/SurveyQuestionResponse.kt
@@ -0,0 +1,19 @@
+package com.wap.wapp.core.network.model.survey.form
+
+import com.wap.wapp.core.model.survey.SurveyQuestion
+import com.wap.wapp.core.network.model.survey.QuestionTypeResponse
+import com.wap.wapp.core.network.model.survey.toDomain
+
+data class SurveyQuestionResponse(
+ val questionTitle: String,
+ val questionType: QuestionTypeResponse,
+) {
+ constructor() : this(
+ "",
+ QuestionTypeResponse.SUBJECTIVE,
+ )
+ fun toDomain() = SurveyQuestion(
+ questionTitle = questionTitle,
+ questionType = questionType.toDomain(),
+ )
+}
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/model/user/UserProfileRequest.kt b/core/network/src/main/java/com/wap/wapp/core/network/model/user/UserProfileRequest.kt
new file mode 100644
index 000000000..59968fd6f
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/model/user/UserProfileRequest.kt
@@ -0,0 +1,15 @@
+package com.wap.wapp.core.network.model.user
+
+data class UserProfileRequest(
+ val userId: String,
+ val userName: String,
+ val studentId: String,
+ val registeredAt: String,
+) {
+ constructor() : this(
+ "",
+ "",
+ "",
+ "",
+ )
+}
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/model/user/UserProfileResponse.kt b/core/network/src/main/java/com/wap/wapp/core/network/model/user/UserProfileResponse.kt
new file mode 100644
index 000000000..deb0f61f1
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/model/user/UserProfileResponse.kt
@@ -0,0 +1,24 @@
+package com.wap.wapp.core.network.model.user
+
+import com.wap.wapp.core.model.user.UserProfile
+
+data class UserProfileResponse(
+ val userId: String,
+ val userName: String,
+ val studentId: String,
+ val registeredAt: String,
+) {
+ constructor() : this(
+ "",
+ "",
+ "",
+ "",
+ )
+
+ fun toDomain(): UserProfile = UserProfile(
+ userId = userId,
+ userName = userName,
+ studentId = studentId,
+ registeredAt = registeredAt,
+ )
+}
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/source/attendance/AttendanceDataSource.kt b/core/network/src/main/java/com/wap/wapp/core/network/source/attendance/AttendanceDataSource.kt
new file mode 100644
index 000000000..9e8c423ef
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/source/attendance/AttendanceDataSource.kt
@@ -0,0 +1,15 @@
+package com.wap.wapp.core.network.source.attendance
+
+import com.wap.wapp.core.network.model.attendance.AttendanceResponse
+
+interface AttendanceDataSource {
+ suspend fun postAttendance(
+ eventId: String,
+ code: String,
+ deadline: String,
+ ): Result
+
+ suspend fun getAttendance(
+ eventId: String,
+ ): Result
+}
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/source/attendance/AttendanceDataSourceImpl.kt b/core/network/src/main/java/com/wap/wapp/core/network/source/attendance/AttendanceDataSourceImpl.kt
new file mode 100644
index 000000000..a36c8b28f
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/source/attendance/AttendanceDataSourceImpl.kt
@@ -0,0 +1,46 @@
+package com.wap.wapp.core.network.source.attendance
+
+import com.google.firebase.firestore.FirebaseFirestore
+import com.google.firebase.firestore.ktx.toObject
+import com.wap.wapp.core.network.constant.ATTENDANCE_COLLECTION
+import com.wap.wapp.core.network.model.attendance.AttendanceRequest
+import com.wap.wapp.core.network.model.attendance.AttendanceResponse
+import com.wap.wapp.core.network.utils.await
+import com.wap.wapp.core.network.utils.toISOLocalDateTimeString
+import java.time.LocalDateTime
+import javax.inject.Inject
+
+class AttendanceDataSourceImpl @Inject constructor(
+ private val firebaseFirestore: FirebaseFirestore,
+) : AttendanceDataSource {
+ override suspend fun postAttendance(
+ eventId: String,
+ code: String,
+ deadline: String,
+ ): Result = runCatching {
+ val attendanceRequest = AttendanceRequest(
+ eventId = eventId,
+ code = code,
+ deadline = deadline,
+ )
+
+ firebaseFirestore.collection(ATTENDANCE_COLLECTION)
+ .document(eventId)
+ .set(attendanceRequest)
+ .await()
+ }
+
+ override suspend fun getAttendance(eventId: String): Result = runCatching {
+ val task = firebaseFirestore.collection(ATTENDANCE_COLLECTION)
+ .document(eventId)
+ .get()
+ .await()
+
+ val attendanceResponse = task.toObject()
+ attendanceResponse ?: AttendanceResponse(
+ eventId = eventId,
+ "",
+ LocalDateTime.MIN.toISOLocalDateTimeString(),
+ )
+ }
+}
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/source/attendancestatus/AttendanceStatusDataSource.kt b/core/network/src/main/java/com/wap/wapp/core/network/source/attendancestatus/AttendanceStatusDataSource.kt
new file mode 100644
index 000000000..2cbc40e29
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/source/attendancestatus/AttendanceStatusDataSource.kt
@@ -0,0 +1,12 @@
+package com.wap.wapp.core.network.source.attendancestatus
+
+import com.wap.wapp.core.network.model.attendancestatus.AttendanceStatusResponse
+
+interface AttendanceStatusDataSource {
+ suspend fun getAttendanceStatus(
+ eventId: String,
+ userId: String,
+ ): Result
+
+ suspend fun postAttendanceStatus(eventId: String, userId: String): Result
+}
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/source/attendancestatus/AttendanceStatusDataSourceImpl.kt b/core/network/src/main/java/com/wap/wapp/core/network/source/attendancestatus/AttendanceStatusDataSourceImpl.kt
new file mode 100644
index 000000000..0c35bf89c
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/source/attendancestatus/AttendanceStatusDataSourceImpl.kt
@@ -0,0 +1,45 @@
+package com.wap.wapp.core.network.source.attendancestatus
+
+import com.google.firebase.firestore.FirebaseFirestore
+import com.google.firebase.firestore.ktx.toObject
+import com.wap.wapp.core.network.constant.ATTENDANCE_STATUS_COLLECTION
+import com.wap.wapp.core.network.constant.EVENT_COLLECTION
+import com.wap.wapp.core.network.model.attendancestatus.AttendanceStatusRequest
+import com.wap.wapp.core.network.model.attendancestatus.AttendanceStatusResponse
+import com.wap.wapp.core.network.utils.await
+import com.wap.wapp.core.network.utils.generateNowDateTime
+import com.wap.wapp.core.network.utils.toISOLocalDateTimeString
+import javax.inject.Inject
+
+class AttendanceStatusDataSourceImpl @Inject constructor(
+ private val firebaseFirestore: FirebaseFirestore,
+) : AttendanceStatusDataSource {
+ override suspend fun getAttendanceStatus(
+ eventId: String,
+ userId: String,
+ ): Result = runCatching {
+ val task = firebaseFirestore.collection(ATTENDANCE_STATUS_COLLECTION)
+ .document(userId)
+ .collection(EVENT_COLLECTION)
+ .document(eventId)
+ .get()
+ .await()
+
+ // null์ผ ๊ฒฝ์ฐ ์ถ์์ด ๋์ง ์์ (์ถ์ ์๊ฐ์ด empty๋ก ๋ฐํ)
+ val attendanceStatusResponse = task.toObject()
+ attendanceStatusResponse ?: AttendanceStatusResponse("")
+ }
+
+ override suspend fun postAttendanceStatus(eventId: String, userId: String): Result =
+ runCatching {
+ val attendanceStatusRequest =
+ AttendanceStatusRequest(generateNowDateTime().toISOLocalDateTimeString())
+
+ firebaseFirestore.collection(ATTENDANCE_STATUS_COLLECTION)
+ .document(userId)
+ .collection(EVENT_COLLECTION)
+ .document(eventId)
+ .set(attendanceStatusRequest)
+ .await()
+ }
+}
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/source/auth/AuthDataSource.kt b/core/network/src/main/java/com/wap/wapp/core/network/source/auth/AuthDataSource.kt
new file mode 100644
index 000000000..06bd34157
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/source/auth/AuthDataSource.kt
@@ -0,0 +1,11 @@
+package com.wap.wapp.core.network.source.auth
+
+interface AuthDataSource {
+ suspend fun signOut(): Result
+
+ suspend fun deleteUser(): Result
+
+ suspend fun isUserSignIn(): Result
+
+ suspend fun checkMemberCode(code: String): Result
+}
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/source/auth/AuthDataSourceImpl.kt b/core/network/src/main/java/com/wap/wapp/core/network/source/auth/AuthDataSourceImpl.kt
new file mode 100644
index 000000000..84def2902
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/source/auth/AuthDataSourceImpl.kt
@@ -0,0 +1,60 @@
+package com.wap.wapp.core.network.source.auth
+
+import com.google.firebase.auth.FirebaseAuth
+import com.google.firebase.auth.FirebaseAuth.AuthStateListener
+import com.google.firebase.firestore.FirebaseFirestore
+import com.wap.wapp.core.network.constant.CODES_COLLECTION
+import com.wap.wapp.core.network.utils.await
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.suspendCancellableCoroutine
+import javax.inject.Inject
+
+class AuthDataSourceImpl @Inject constructor(
+ private val firebaseAuth: FirebaseAuth,
+ private val firebaseFirestore: FirebaseFirestore,
+) : AuthDataSource {
+ override suspend fun signOut(): Result = runCatching {
+ firebaseAuth.signOut()
+ }
+
+ override suspend fun deleteUser(): Result = runCatching {
+ val user = checkNotNull(firebaseAuth.currentUser)
+
+ user.delete()
+ .await()
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ override suspend fun isUserSignIn(): Result = runCatching {
+ suspendCancellableCoroutine { cont ->
+ val authStateListener = object : AuthStateListener {
+ override fun onAuthStateChanged(remoteAuth: FirebaseAuth) {
+ val user = remoteAuth.currentUser
+ if (user != null) {
+ cont.resume(true, null)
+ } else {
+ cont.resume(false, null)
+ }
+
+ // ํ ๋ฒ ํธ์ถ๋ ํ์ Listener ์ญ์
+ firebaseAuth.removeAuthStateListener(this)
+ }
+ }
+
+ firebaseAuth.addAuthStateListener(authStateListener)
+
+ cont.invokeOnCancellation { // Coroutine์ด ์ทจ์๋๋ ๊ฒฝ์ฐ ๋ฆฌ์ค๋ ์ญ์
+ firebaseAuth.removeAuthStateListener(authStateListener)
+ }
+ }
+ }
+
+ override suspend fun checkMemberCode(code: String): Result = runCatching {
+ val result = firebaseFirestore.collection(CODES_COLLECTION)
+ .whereEqualTo("user", code)
+ .get()
+ .await()
+
+ result.isEmpty.not()
+ }
+}
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/source/auth/SignInDataSource.kt b/core/network/src/main/java/com/wap/wapp/core/network/source/auth/SignInDataSource.kt
new file mode 100644
index 000000000..44191e0a4
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/source/auth/SignInDataSource.kt
@@ -0,0 +1,5 @@
+package com.wap.wapp.core.network.source.auth
+
+interface SignInDataSource {
+ suspend fun signIn(email: String): Result
+}
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/source/auth/SignInDataSourceImpl.kt b/core/network/src/main/java/com/wap/wapp/core/network/source/auth/SignInDataSourceImpl.kt
new file mode 100644
index 000000000..e5ae18f79
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/source/auth/SignInDataSourceImpl.kt
@@ -0,0 +1,29 @@
+package com.wap.wapp.core.network.source.auth
+
+import android.app.Activity
+import android.content.Context
+import com.google.firebase.auth.FirebaseAuth
+import com.google.firebase.auth.OAuthProvider
+import com.wap.wapp.core.network.utils.await
+import dagger.hilt.android.qualifiers.ActivityContext
+import javax.inject.Inject
+
+class SignInDataSourceImpl @Inject constructor(
+ private val firebaseAuth: FirebaseAuth,
+ @ActivityContext private val context: Context,
+) : SignInDataSource {
+ override suspend fun signIn(email: String): Result = runCatching {
+ val provider = OAuthProvider.newBuilder("github.com")
+ provider.addCustomParameter("login", email)
+
+ val activityContext = context as Activity
+
+ val result = firebaseAuth.startActivityForSignInWithProvider(
+ activityContext,
+ provider.build(),
+ ).await()
+
+ val user = checkNotNull(result.user)
+ user.uid
+ }
+}
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/source/event/EventDataSource.kt b/core/network/src/main/java/com/wap/wapp/core/network/source/event/EventDataSource.kt
new file mode 100644
index 000000000..f0c4aabbc
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/source/event/EventDataSource.kt
@@ -0,0 +1,35 @@
+package com.wap.wapp.core.network.source.event
+
+import com.wap.wapp.core.network.model.event.EventResponse
+import java.time.LocalDate
+
+interface EventDataSource {
+ suspend fun getMonthEventList(date: LocalDate): Result>
+
+ suspend fun getDateEventList(date: LocalDate): Result>
+
+ suspend fun getEventList(): Result>
+
+ suspend fun getEventListFromDate(date: LocalDate): Result>
+
+ suspend fun getEvent(eventId: String): Result
+
+ suspend fun deleteEvent(eventId: String): Result
+
+ suspend fun postEvent(
+ title: String,
+ content: String,
+ location: String,
+ startDateTime: String,
+ endDateTime: String,
+ ): Result
+
+ suspend fun updateEvent(
+ eventId: String,
+ title: String,
+ content: String,
+ location: String,
+ startDateTime: String,
+ endDateTime: String,
+ ): Result
+}
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/source/event/EventDataSourceImpl.kt b/core/network/src/main/java/com/wap/wapp/core/network/source/event/EventDataSourceImpl.kt
new file mode 100644
index 000000000..62d4f6a45
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/source/event/EventDataSourceImpl.kt
@@ -0,0 +1,168 @@
+package com.wap.wapp.core.network.source.event
+
+import com.google.firebase.firestore.FirebaseFirestore
+import com.google.firebase.firestore.ktx.toObject
+import com.wap.wapp.core.network.constant.EVENT_COLLECTION
+import com.wap.wapp.core.network.model.event.EventRequest
+import com.wap.wapp.core.network.model.event.EventResponse
+import com.wap.wapp.core.network.utils.await
+import com.wap.wapp.core.network.utils.generateNowDateTime
+import com.wap.wapp.core.network.utils.toISOLocalDateTimeString
+import java.time.LocalDate
+import java.time.LocalTime
+import javax.inject.Inject
+
+class EventDataSourceImpl @Inject constructor(
+ private val firebaseFirestore: FirebaseFirestore,
+) : EventDataSource {
+ override suspend fun getEventList(): Result> = runCatching {
+ val result = mutableListOf()
+
+ val task = firebaseFirestore.collection(EVENT_COLLECTION)
+ .get()
+ .await()
+
+ for (document in task.documents) {
+ val event = document.toObject()
+ checkNotNull(event)
+ result.add(event)
+ }
+
+ result
+ }
+
+ override suspend fun getEventListFromDate(date: LocalDate): Result> =
+ runCatching {
+ val result = mutableListOf()
+
+ // ์ ํ๋ ๋ ์ง 1์ผ 00์ 00๋ถ 00์ด
+ val startDateTime = date.atStartOfDay().toISOLocalDateTimeString()
+ val currentDateTime = generateNowDateTime().toISOLocalDateTimeString()
+ val task = firebaseFirestore.collection(EVENT_COLLECTION)
+ .whereGreaterThanOrEqualTo("startDateTime", startDateTime)
+ .whereLessThanOrEqualTo("startDateTime", currentDateTime)
+ .get()
+ .await()
+
+ for (document in task.documents) {
+ val event = document.toObject()
+ checkNotNull(event)
+ result.add(event)
+ }
+
+ result
+ }
+
+ override suspend fun getMonthEventList(date: LocalDate): Result> =
+ runCatching {
+ val result = mutableListOf()
+
+ // ์ ํ๋ ๋ ์ง 1์ผ 00์ 00๋ถ 00์ด
+ val startDateTime = LocalDate.of(date.year, date.month, 1).atStartOfDay()
+ .toISOLocalDateTimeString()
+
+ // ์ ํ๋ ๋ ์ง์ ๋ง์ง๋ง ๋ (31์ผ) 23์ 59๋ถ 59์ด
+ val endDateTime =
+ LocalDate.of(date.year, date.month, date.lengthOfMonth()).atTime(LocalTime.MAX)
+ .toISOLocalDateTimeString()
+
+ val task = firebaseFirestore.collection(EVENT_COLLECTION)
+ .whereGreaterThanOrEqualTo("startDateTime", startDateTime)
+ .whereLessThanOrEqualTo("startDateTime", endDateTime)
+ .get()
+ .await()
+
+ for (document in task.documents) {
+ val event = document.toObject()
+ checkNotNull(event)
+ result.add(event)
+ }
+
+ result
+ }
+
+ override suspend fun getDateEventList(date: LocalDate): Result> =
+ runCatching {
+ val result = mutableListOf()
+
+ val startDateTime = date.atStartOfDay().toISOLocalDateTimeString()
+ val endDateTime = date.atTime(LocalTime.MAX).toISOLocalDateTimeString()
+
+ val task = firebaseFirestore.collection(EVENT_COLLECTION)
+ .whereGreaterThanOrEqualTo("startDateTime", startDateTime)
+ .whereLessThanOrEqualTo("startDateTime", endDateTime)
+ .get()
+ .await()
+
+ for (document in task.documents) {
+ val event = document.toObject()
+ checkNotNull(event)
+ result.add(event)
+ }
+
+ result
+ }
+
+ override suspend fun getEvent(eventId: String): Result = runCatching {
+ val document = firebaseFirestore.collection(EVENT_COLLECTION)
+ .document(eventId)
+ .get()
+ .await()
+
+ val eventResponse = document.toObject()
+ checkNotNull(eventResponse)
+ }
+
+ override suspend fun deleteEvent(eventId: String): Result = runCatching {
+ firebaseFirestore.collection(EVENT_COLLECTION)
+ .document(eventId)
+ .delete()
+ .await()
+ }
+
+ override suspend fun postEvent(
+ title: String,
+ content: String,
+ location: String,
+ startDateTime: String,
+ endDateTime: String,
+ ): Result = runCatching {
+ val documentId = firebaseFirestore.collection(EVENT_COLLECTION).document().id
+
+ val eventRequest = EventRequest(
+ title = title,
+ content = content,
+ location = location,
+ startDateTime = startDateTime,
+ endDateTime = endDateTime,
+ eventId = documentId,
+ )
+
+ firebaseFirestore.collection(EVENT_COLLECTION)
+ .document(documentId)
+ .set(eventRequest)
+ .await()
+ }
+
+ override suspend fun updateEvent(
+ eventId: String,
+ title: String,
+ content: String,
+ location: String,
+ startDateTime: String,
+ endDateTime: String,
+ ): Result = runCatching {
+ val updateData = mapOf(
+ "title" to title,
+ "content" to content,
+ "location" to location,
+ "startDateTime" to startDateTime,
+ "endDateTime" to endDateTime,
+ )
+
+ firebaseFirestore.collection(EVENT_COLLECTION)
+ .document(eventId)
+ .update(updateData)
+ .await()
+ }
+}
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/source/management/ManagementDataSource.kt b/core/network/src/main/java/com/wap/wapp/core/network/source/management/ManagementDataSource.kt
new file mode 100644
index 000000000..866f164b1
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/source/management/ManagementDataSource.kt
@@ -0,0 +1,11 @@
+package com.wap.wapp.core.network.source.management
+
+interface ManagementDataSource {
+ suspend fun isManager(userId: String): Result
+
+ suspend fun postManager(userId: String): Result
+
+ suspend fun checkManagementCode(code: String): Result
+
+ suspend fun deleteManager(userId: String): Result
+}
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/source/management/ManagementDataSourceImpl.kt b/core/network/src/main/java/com/wap/wapp/core/network/source/management/ManagementDataSourceImpl.kt
new file mode 100644
index 000000000..feb8599ef
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/source/management/ManagementDataSourceImpl.kt
@@ -0,0 +1,47 @@
+package com.wap.wapp.core.network.source.management
+
+import com.google.firebase.firestore.FirebaseFirestore
+import com.google.firebase.firestore.SetOptions
+import com.wap.wapp.core.network.constant.CODES_COLLECTION
+import com.wap.wapp.core.network.constant.MANAGER_COLLECTION
+import com.wap.wapp.core.network.utils.await
+import javax.inject.Inject
+
+class ManagementDataSourceImpl @Inject constructor(
+ private val firebaseFirestore: FirebaseFirestore,
+) : ManagementDataSource {
+ override suspend fun isManager(userId: String): Result = runCatching {
+ val result = firebaseFirestore.collection(MANAGER_COLLECTION)
+ .whereEqualTo("userId", userId)
+ .get()
+ .await()
+
+ result.isEmpty.not()
+ }
+
+ override suspend fun postManager(userId: String): Result = runCatching {
+ val userIdMap = mapOf("userId" to userId)
+ val setOption = SetOptions.merge()
+
+ firebaseFirestore.collection(MANAGER_COLLECTION)
+ .document(userId)
+ .set(userIdMap, setOption)
+ .await()
+ }
+
+ override suspend fun checkManagementCode(code: String): Result = runCatching {
+ val result = firebaseFirestore.collection(CODES_COLLECTION)
+ .whereEqualTo("management", code)
+ .get()
+ .await()
+
+ result.isEmpty.not()
+ }
+
+ override suspend fun deleteManager(userId: String): Result = runCatching {
+ firebaseFirestore.collection(MANAGER_COLLECTION)
+ .document(userId)
+ .delete()
+ .await()
+ }
+}
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/source/survey/SurveyDataSource.kt b/core/network/src/main/java/com/wap/wapp/core/network/source/survey/SurveyDataSource.kt
new file mode 100644
index 000000000..051c410c3
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/source/survey/SurveyDataSource.kt
@@ -0,0 +1,33 @@
+package com.wap.wapp.core.network.source.survey
+
+import com.wap.wapp.core.model.survey.SurveyAnswer
+import com.wap.wapp.core.network.model.survey.SurveyResponse
+
+interface SurveyDataSource {
+ suspend fun isSubmittedSurvey(
+ surveyFormId: String,
+ userId: String,
+ ): Result
+
+ suspend fun getSurveyList(): Result>
+
+ suspend fun getSurveyListByEventId(eventId: String): Result>
+
+ suspend fun getSurveyListBySurveyFormId(surveyFormId: String): Result>
+
+ suspend fun getUserRespondedSurveyList(userId: String): Result>
+
+ suspend fun getSurvey(surveyId: String): Result
+
+ suspend fun deleteSurvey(surveyId: String): Result
+
+ suspend fun postSurvey(
+ surveyFormId: String,
+ eventId: String,
+ userId: String,
+ title: String,
+ content: String,
+ surveyAnswerList: List,
+ surveyedAt: String,
+ ): Result
+}
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/source/survey/SurveyDataSourceImpl.kt b/core/network/src/main/java/com/wap/wapp/core/network/source/survey/SurveyDataSourceImpl.kt
new file mode 100644
index 000000000..8cb01873f
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/source/survey/SurveyDataSourceImpl.kt
@@ -0,0 +1,148 @@
+package com.wap.wapp.core.network.source.survey
+
+import com.google.firebase.firestore.FirebaseFirestore
+import com.google.firebase.firestore.SetOptions
+import com.wap.wapp.core.model.survey.SurveyAnswer
+import com.wap.wapp.core.network.constant.SURVEY_COLLECTION
+import com.wap.wapp.core.network.model.survey.SurveyRequest
+import com.wap.wapp.core.network.model.survey.SurveyResponse
+import com.wap.wapp.core.network.utils.await
+import javax.inject.Inject
+
+class SurveyDataSourceImpl @Inject constructor(
+ private val firebaseFirestore: FirebaseFirestore,
+) : SurveyDataSource {
+ override suspend fun getSurveyList(): Result> = runCatching {
+ val result: MutableList = mutableListOf()
+
+ val task = firebaseFirestore.collection(SURVEY_COLLECTION)
+ .get()
+ .await()
+
+ for (document in task.documents) {
+ val surveyResponse = document.toObject(SurveyResponse::class.java)
+ checkNotNull(surveyResponse)
+
+ result.add(surveyResponse)
+ }
+
+ result
+ }
+
+ override suspend fun getSurveyListByEventId(eventId: String): Result> =
+ runCatching {
+ val result: MutableList = mutableListOf()
+
+ val task = firebaseFirestore.collection(SURVEY_COLLECTION)
+ .whereEqualTo("eventId", eventId)
+ .get()
+ .await()
+
+ for (document in task.documents) {
+ val surveyResponse = document.toObject(SurveyResponse::class.java)
+ checkNotNull(surveyResponse)
+
+ result.add(surveyResponse)
+ }
+
+ result
+ }
+
+ override suspend fun getSurveyListBySurveyFormId(
+ surveyFormId: String,
+ ): Result> = runCatching {
+ val result: MutableList = mutableListOf()
+
+ val task = firebaseFirestore.collection(SURVEY_COLLECTION)
+ .whereEqualTo("surveyFormId", surveyFormId)
+ .get()
+ .await()
+
+ for (document in task.documents) {
+ val surveyResponse = document.toObject(SurveyResponse::class.java)
+ checkNotNull(surveyResponse)
+
+ result.add(surveyResponse)
+ }
+
+ result
+ }
+
+ override suspend fun getUserRespondedSurveyList(userId: String): Result> =
+ runCatching {
+ val result: MutableList = mutableListOf()
+
+ val task = firebaseFirestore.collection(SURVEY_COLLECTION)
+ .whereEqualTo("userId", userId)
+ .get()
+ .await()
+
+ for (document in task.documents) {
+ val surveyResponse = document.toObject(SurveyResponse::class.java)
+ checkNotNull(surveyResponse)
+
+ result.add(surveyResponse)
+ }
+
+ result
+ }
+
+ override suspend fun getSurvey(surveyId: String): Result = runCatching {
+ val result = firebaseFirestore.collection(SURVEY_COLLECTION)
+ .document(surveyId)
+ .get()
+ .await()
+
+ val surveyResponse = result.toObject(SurveyResponse::class.java)
+ checkNotNull(surveyResponse)
+ }
+
+ override suspend fun deleteSurvey(surveyId: String): Result = runCatching {
+ firebaseFirestore.collection(SURVEY_COLLECTION)
+ .document(surveyId)
+ .delete()
+ .await()
+ }
+
+ override suspend fun postSurvey(
+ surveyFormId: String,
+ eventId: String,
+ userId: String,
+ title: String,
+ content: String,
+ surveyAnswerList: List,
+ surveyedAt: String,
+ ): Result = runCatching {
+ val documentId = firebaseFirestore.collection(SURVEY_COLLECTION).document().id
+
+ val surveyRequest = SurveyRequest(
+ surveyId = documentId,
+ surveyFormId = surveyFormId,
+ eventId = eventId,
+ userId = userId,
+ title = title,
+ content = content,
+ surveyAnswerList = surveyAnswerList,
+ surveyedAt = surveyedAt,
+ )
+ val setOption = SetOptions.merge()
+
+ firebaseFirestore.collection(SURVEY_COLLECTION)
+ .document(documentId)
+ .set(surveyRequest, setOption)
+ .await()
+ }
+
+ override suspend fun isSubmittedSurvey(
+ surveyFormId: String,
+ userId: String,
+ ): Result = runCatching {
+ val result = firebaseFirestore.collection(SURVEY_COLLECTION)
+ .whereEqualTo("surveyFormId", surveyFormId)
+ .whereEqualTo("userId", userId)
+ .get()
+ .await()
+
+ result.isEmpty.not()
+ }
+}
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/source/survey/SurveyFormDataSource.kt b/core/network/src/main/java/com/wap/wapp/core/network/source/survey/SurveyFormDataSource.kt
new file mode 100644
index 000000000..63e366978
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/source/survey/SurveyFormDataSource.kt
@@ -0,0 +1,25 @@
+package com.wap.wapp.core.network.source.survey
+
+import com.wap.wapp.core.model.survey.SurveyQuestion
+import com.wap.wapp.core.network.model.survey.form.SurveyFormRequest
+import com.wap.wapp.core.network.model.survey.form.SurveyFormResponse
+
+interface SurveyFormDataSource {
+ suspend fun postSurveyForm(
+ eventId: String,
+ title: String,
+ content: String,
+ surveyQuestionList: List,
+ deadline: String,
+ ): Result
+
+ suspend fun getSurveyForm(surveyFormId: String): Result
+
+ suspend fun getSurveyFormList(): Result>
+
+ suspend fun getSurveyFormListByEventId(eventId: String): Result>
+
+ suspend fun deleteSurveyForm(surveyFormId: String): Result
+
+ suspend fun updateSurveyForm(surveyFormRequest: SurveyFormRequest): Result
+}
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/source/survey/SurveyFormDataSourceImpl.kt b/core/network/src/main/java/com/wap/wapp/core/network/source/survey/SurveyFormDataSourceImpl.kt
new file mode 100644
index 000000000..ca72a45b6
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/source/survey/SurveyFormDataSourceImpl.kt
@@ -0,0 +1,110 @@
+package com.wap.wapp.core.network.source.survey
+
+import com.google.firebase.firestore.FirebaseFirestore
+import com.google.firebase.firestore.SetOptions
+import com.wap.wapp.core.model.survey.SurveyQuestion
+import com.wap.wapp.core.network.constant.SURVEY_FORM_COLLECTION
+import com.wap.wapp.core.network.model.survey.form.SurveyFormRequest
+import com.wap.wapp.core.network.model.survey.form.SurveyFormResponse
+import com.wap.wapp.core.network.utils.await
+import javax.inject.Inject
+
+class SurveyFormDataSourceImpl @Inject constructor(
+ private val firebaseFirestore: FirebaseFirestore,
+) : SurveyFormDataSource {
+ override suspend fun getSurveyForm(surveyFormId: String): Result =
+ runCatching {
+ val result = firebaseFirestore.collection(SURVEY_FORM_COLLECTION)
+ .document(surveyFormId)
+ .get()
+ .await()
+
+ val surveyFormResponse = result.toObject(SurveyFormResponse::class.java)
+ checkNotNull(surveyFormResponse)
+ }
+
+ override suspend fun getSurveyFormList(): Result> = runCatching {
+ val result: MutableList = mutableListOf()
+
+ val task = firebaseFirestore.collection(SURVEY_FORM_COLLECTION)
+ .get()
+ .await()
+
+ for (document in task.documents) {
+ val surveyFormResponse = document.toObject(SurveyFormResponse::class.java)
+ checkNotNull(surveyFormResponse)
+
+ result.add(surveyFormResponse)
+ }
+
+ result
+ }
+
+ override suspend fun getSurveyFormListByEventId(
+ eventId: String,
+ ): Result> = runCatching {
+ val result: MutableList = mutableListOf()
+
+ val task = firebaseFirestore.collection(SURVEY_FORM_COLLECTION)
+ .whereEqualTo("eventId", eventId)
+ .get()
+ .await()
+
+ for (document in task.documents) {
+ val surveyFormResponse = document.toObject(SurveyFormResponse::class.java)
+ checkNotNull(surveyFormResponse)
+
+ result.add(surveyFormResponse)
+ }
+
+ result
+ }
+
+ override suspend fun deleteSurveyForm(surveyFormId: String): Result = runCatching {
+ firebaseFirestore.collection(SURVEY_FORM_COLLECTION)
+ .document(surveyFormId)
+ .delete()
+ .await()
+ }
+
+ override suspend fun postSurveyForm(
+ eventId: String,
+ title: String,
+ content: String,
+ surveyQuestionList: List,
+ deadline: String,
+ ): Result = runCatching {
+ val documentId = firebaseFirestore.collection(SURVEY_FORM_COLLECTION).document().id
+ val surveyFormRequest = SurveyFormRequest(
+ surveyFormId = documentId,
+ eventId = eventId,
+ title = title,
+ content = content,
+ surveyQuestionList = surveyQuestionList,
+ deadline = deadline,
+ )
+
+ val setOption = SetOptions.merge()
+ firebaseFirestore.collection(SURVEY_FORM_COLLECTION)
+ .document(documentId)
+ .set(surveyFormRequest, setOption)
+ .await()
+ }
+
+ override suspend fun updateSurveyForm(surveyFormRequest: SurveyFormRequest): Result =
+ runCatching {
+ val surveyFormMap = mapOf(
+ "surveyFormId" to surveyFormRequest.surveyFormId,
+ "eventId" to surveyFormRequest.eventId,
+ "title" to surveyFormRequest.title,
+ "content" to surveyFormRequest.content,
+ "surveyQuestionList" to surveyFormRequest.surveyQuestionList,
+ "deadline" to surveyFormRequest.deadline,
+ )
+
+ firebaseFirestore.collection(SURVEY_FORM_COLLECTION)
+ .document(surveyFormRequest.surveyFormId)
+ .update(surveyFormMap)
+ .await()
+ }
+}
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/source/user/UserDataSource.kt b/core/network/src/main/java/com/wap/wapp/core/network/source/user/UserDataSource.kt
new file mode 100644
index 000000000..93e48027c
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/source/user/UserDataSource.kt
@@ -0,0 +1,14 @@
+package com.wap.wapp.core.network.source.user
+
+import com.wap.wapp.core.network.model.user.UserProfileRequest
+import com.wap.wapp.core.network.model.user.UserProfileResponse
+
+interface UserDataSource {
+ suspend fun postUserProfile(userProfileRequest: UserProfileRequest): Result
+
+ suspend fun getUserProfile(userId: String): Result
+
+ suspend fun getUserId(): Result
+
+ suspend fun deleteUserProfile(userId: String): Result
+}
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/source/user/UserDataSourceImpl.kt b/core/network/src/main/java/com/wap/wapp/core/network/source/user/UserDataSourceImpl.kt
new file mode 100644
index 000000000..5a334ba4b
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/source/user/UserDataSourceImpl.kt
@@ -0,0 +1,54 @@
+package com.wap.wapp.core.network.source.user
+
+import com.google.firebase.auth.FirebaseAuth
+import com.google.firebase.firestore.FirebaseFirestore
+import com.google.firebase.firestore.SetOptions
+import com.wap.wapp.core.network.constant.USER_COLLECTION
+import com.wap.wapp.core.network.model.user.UserProfileRequest
+import com.wap.wapp.core.network.model.user.UserProfileResponse
+import com.wap.wapp.core.network.utils.await
+import javax.inject.Inject
+
+class UserDataSourceImpl @Inject constructor(
+ private val firebaseFirestore: FirebaseFirestore,
+ private val firebaseAuth: FirebaseAuth,
+) : UserDataSource {
+ override suspend fun postUserProfile(
+ userProfileRequest: UserProfileRequest,
+ ): Result = runCatching {
+ val userId = userProfileRequest.userId
+ val setOption = SetOptions.merge()
+
+ firebaseFirestore.collection(USER_COLLECTION)
+ .document(userId)
+ .set(
+ userProfileRequest,
+ setOption,
+ )
+ .await()
+ }
+
+ override suspend fun getUserId(): Result = runCatching {
+ val userId = checkNotNull(firebaseAuth.uid)
+ userId
+ }
+
+ override suspend fun getUserProfile(
+ userId: String,
+ ): Result = runCatching {
+ val result = firebaseFirestore.collection(USER_COLLECTION)
+ .document(userId)
+ .get()
+ .await()
+
+ val userProfileResponse = result.toObject(UserProfileResponse::class.java)
+ checkNotNull(userProfileResponse)
+ }
+
+ override suspend fun deleteUserProfile(userId: String): Result = runCatching {
+ firebaseFirestore.collection(USER_COLLECTION)
+ .document(userId)
+ .delete()
+ .await()
+ }
+}
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/utils/DateUtil.kt b/core/network/src/main/java/com/wap/wapp/core/network/utils/DateUtil.kt
new file mode 100644
index 000000000..3847fb853
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/utils/DateUtil.kt
@@ -0,0 +1,16 @@
+package com.wap.wapp.core.network.utils
+
+import java.time.LocalDateTime
+import java.time.ZoneId
+import java.time.format.DateTimeFormatter
+
+internal fun String.toISOLocalDateTime(): LocalDateTime = LocalDateTime.parse(
+ this,
+ DateTimeFormatter.ISO_LOCAL_DATE_TIME,
+)
+
+internal fun LocalDateTime.toISOLocalDateTimeString(): String =
+ this.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
+
+internal fun generateNowDateTime(zoneId: ZoneId = ZoneId.of("Asia/Seoul")): LocalDateTime =
+ LocalDateTime.now(zoneId)
diff --git a/core/network/src/main/java/com/wap/wapp/core/network/utils/SuspendCoroutine.kt b/core/network/src/main/java/com/wap/wapp/core/network/utils/SuspendCoroutine.kt
new file mode 100644
index 000000000..d3de37a80
--- /dev/null
+++ b/core/network/src/main/java/com/wap/wapp/core/network/utils/SuspendCoroutine.kt
@@ -0,0 +1,19 @@
+package com.wap.wapp.core.network.utils
+
+import com.google.android.gms.tasks.Task
+import kotlin.coroutines.resumeWithException
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+@OptIn(ExperimentalCoroutinesApi::class)
+suspend fun Task.await(): T {
+ return suspendCancellableCoroutine { cont ->
+ addOnCompleteListener {
+ if (it.exception != null) {
+ cont.resumeWithException(it.exception!!)
+ } else {
+ cont.resume(it.result, null)
+ }
+ }
+ }
+}
diff --git a/feature/attendance/.gitignore b/feature/attendance/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/feature/attendance/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/feature/attendance/build.gradle.kts b/feature/attendance/build.gradle.kts
new file mode 100644
index 000000000..7c5edd1f3
--- /dev/null
+++ b/feature/attendance/build.gradle.kts
@@ -0,0 +1,38 @@
+plugins {
+ id("com.wap.wapp.feature")
+ id("com.wap.wapp.hilt")
+}
+
+android {
+ namespace = "com.wap.wapp.feature.attendance"
+
+ defaultConfig {
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro",
+ )
+ }
+ }
+}
+
+dependencies {
+ implementation(project(":core:domain"))
+ implementation(project(":core:model"))
+ implementation(project(":core:designsystem"))
+ implementation(project(":core:designresource"))
+ implementation(project(":core:common"))
+
+ implementation(libs.bundles.androidx)
+ implementation(libs.material)
+
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.test.junit)
+ androidTestImplementation(libs.androidx.test.espresso)
+}
diff --git a/feature/attendance/consumer-rules.pro b/feature/attendance/consumer-rules.pro
new file mode 100644
index 000000000..e69de29bb
diff --git a/feature/attendance/proguard-rules.pro b/feature/attendance/proguard-rules.pro
new file mode 100644
index 000000000..481bb4348
--- /dev/null
+++ b/feature/attendance/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/feature/attendance/src/androidTest/java/com/wap/wapp/feature/attendance/ExampleInstrumentedTest.kt b/feature/attendance/src/androidTest/java/com/wap/wapp/feature/attendance/ExampleInstrumentedTest.kt
new file mode 100644
index 000000000..5d8dc66e7
--- /dev/null
+++ b/feature/attendance/src/androidTest/java/com/wap/wapp/feature/attendance/ExampleInstrumentedTest.kt
@@ -0,0 +1,23 @@
+package com.wap.wapp.feature.attendance
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Assert.assertEquals
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.wap.wapp.feature.attendance.test", appContext.packageName)
+ }
+}
diff --git a/feature/attendance/src/main/AndroidManifest.xml b/feature/attendance/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..8bdb7e14b
--- /dev/null
+++ b/feature/attendance/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/feature/attendance/src/main/java/com/wap/wapp/feature/attendance/AttendanceContent.kt b/feature/attendance/src/main/java/com/wap/wapp/feature/attendance/AttendanceContent.kt
new file mode 100644
index 000000000..a1b5506bc
--- /dev/null
+++ b/feature/attendance/src/main/java/com/wap/wapp/feature/attendance/AttendanceContent.kt
@@ -0,0 +1,36 @@
+package com.wap.wapp.feature.attendance
+
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.wap.wapp.feature.attendance.component.AttendanceItemCard
+import com.wap.wapp.feature.attendance.model.EventAttendanceStatus
+
+@Composable
+internal fun AttendanceContent(
+ eventsAttendanceStatus: List,
+ onSelectEventId: (String) -> Unit,
+ onSelectEventTitle: (String) -> Unit,
+ setAttendanceDialog: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ LazyColumn(
+ modifier = modifier,
+ ) {
+ items(items = eventsAttendanceStatus, key = { it.eventId }) { attendanceStatus ->
+ AttendanceItemCard(
+ eventTitle = attendanceStatus.title,
+ eventContent = attendanceStatus.content,
+ remainAttendanceDateTime =
+ attendanceStatus.remainAttendanceDateTime,
+ isAttendance = attendanceStatus.isAttendance,
+ onSelectItemCard = {
+ onSelectEventId(attendanceStatus.eventId)
+ onSelectEventTitle(attendanceStatus.title)
+ setAttendanceDialog()
+ },
+ )
+ }
+ }
+}
diff --git a/feature/attendance/src/main/java/com/wap/wapp/feature/attendance/AttendanceScreen.kt b/feature/attendance/src/main/java/com/wap/wapp/feature/attendance/AttendanceScreen.kt
new file mode 100644
index 000000000..d5427ae9e
--- /dev/null
+++ b/feature/attendance/src/main/java/com/wap/wapp/feature/attendance/AttendanceScreen.kt
@@ -0,0 +1,227 @@
+package com.wap.wapp.feature.attendance
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.SnackbarHost
+import androidx.compose.material3.SnackbarHostState
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.core.content.ContextCompat.getString
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.wap.designsystem.WappTheme
+import com.wap.designsystem.component.CircleLoader
+import com.wap.designsystem.component.NothingToShow
+import com.wap.designsystem.component.WappButton
+import com.wap.designsystem.component.WappLeftMainTopBar
+import com.wap.wapp.core.commmon.extensions.toSupportingText
+import com.wap.wapp.core.model.user.UserRole
+import com.wap.wapp.feature.attendance.AttendanceViewModel.AttendanceEvent.Failure
+import com.wap.wapp.feature.attendance.AttendanceViewModel.AttendanceEvent.Success
+import com.wap.wapp.feature.attendance.AttendanceViewModel.EventAttendanceStatusState
+import com.wap.wapp.feature.attendance.AttendanceViewModel.UserRoleState
+import com.wap.wapp.feature.attendance.component.AttendanceCheckButton
+import com.wap.wapp.feature.attendance.component.AttendanceDialog
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+@Composable
+internal fun AttendanceRoute(
+ viewModel: AttendanceViewModel = hiltViewModel(),
+ navigateToSignIn: () -> Unit,
+ navigateToAttendanceManagement: () -> Unit,
+) {
+ val snackBarHostState = remember { SnackbarHostState() }
+ val userRoleState by viewModel.userRole.collectAsStateWithLifecycle()
+ val eventsAttendanceStatusState
+ by viewModel.todayEventsAttendanceStatus.collectAsStateWithLifecycle()
+ val attendanceCode by viewModel.attendanceCode.collectAsStateWithLifecycle()
+ val selectedEventTitle by viewModel.selectedEventTitle.collectAsStateWithLifecycle()
+ val context = LocalContext.current
+
+ LaunchedEffect(true) {
+ viewModel.apply {
+ launch {
+ errorFlow.collectLatest { throwable ->
+ snackBarHostState.showSnackbar(message = throwable.toSupportingText())
+ }
+ }
+
+ launch {
+ attendanceEvent.collect { event ->
+ when (event) {
+ is Success -> {
+ getTodayEventsAttendanceStatus()
+ snackBarHostState.showSnackbar(
+ getString(context, R.string.attendance_success),
+ )
+ }
+
+ is Failure -> snackBarHostState.showSnackbar(event.message)
+ }
+ }
+ }
+ }
+ }
+
+ Column(modifier = Modifier.fillMaxSize()) {
+ when (userRoleState) {
+ is UserRoleState.Loading -> CircleLoader(modifier = Modifier.fillMaxSize())
+ is UserRoleState.Success -> {
+ when ((userRoleState as UserRoleState.Success).userRole) {
+ UserRole.GUEST -> AttendanceGuestScreen(onButtonClicked = navigateToSignIn)
+ UserRole.MANAGER, UserRole.MEMBER ->
+ AttendanceScreen(
+ userRole = (userRoleState as UserRoleState.Success).userRole,
+ snackBarHostState = snackBarHostState,
+ eventsAttendanceStatusState = eventsAttendanceStatusState,
+ attendanceCode = attendanceCode,
+ selectedEventTitle = selectedEventTitle,
+ clearAttendanceCode = viewModel::clearAttendanceCode,
+ onAttendanceCodeChanged = viewModel::setAttendanceCode,
+ onSelectEventId = viewModel::setSelectedEventId,
+ onSelectEventTitle = viewModel::setSelectedEventTitle,
+ verifyAttendanceCode = viewModel::verifyAttendanceCode,
+ navigateToAttendanceManagement = navigateToAttendanceManagement,
+ )
+ }
+ }
+ }
+ }
+}
+
+@Composable
+internal fun AttendanceScreen(
+ userRole: UserRole,
+ snackBarHostState: SnackbarHostState,
+ eventsAttendanceStatusState: EventAttendanceStatusState,
+ attendanceCode: String,
+ selectedEventTitle: String,
+ clearAttendanceCode: () -> Unit,
+ onAttendanceCodeChanged: (String) -> Unit,
+ onSelectEventId: (String) -> Unit,
+ onSelectEventTitle: (String) -> Unit,
+ verifyAttendanceCode: () -> Unit,
+ navigateToAttendanceManagement: () -> Unit,
+) {
+ var showAttendanceDialog by remember { mutableStateOf(false) }
+
+ if (showAttendanceDialog) {
+ AttendanceDialog(
+ attendanceCode = attendanceCode,
+ eventTitle = selectedEventTitle,
+ onAttendanceCodeChanged = onAttendanceCodeChanged,
+ onDismissRequest = { showAttendanceDialog = false },
+ onConfirmRequest = verifyAttendanceCode,
+ )
+ }
+
+ Scaffold(
+ containerColor = WappTheme.colors.backgroundBlack,
+ snackbarHost = { SnackbarHost(snackBarHostState) },
+ contentWindowInsets = WindowInsets(0.dp),
+ ) { paddingValues ->
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(paddingValues),
+ ) {
+ WappLeftMainTopBar(
+ titleRes = R.string.attendance,
+ contentRes = R.string.attendance_content,
+ )
+ when (eventsAttendanceStatusState) {
+ is EventAttendanceStatusState.Loading ->
+ CircleLoader(Modifier.fillMaxSize())
+
+ is EventAttendanceStatusState.Success -> {
+ Box {
+ if (eventsAttendanceStatusState.events.isEmpty()) {
+ NothingToShow(title = R.string.no_events_to_attendance)
+ } else {
+ AttendanceContent(
+ eventsAttendanceStatus =
+ eventsAttendanceStatusState.events,
+ onSelectEventId = onSelectEventId,
+ onSelectEventTitle = onSelectEventTitle,
+ setAttendanceDialog = {
+ clearAttendanceCode()
+ showAttendanceDialog = true
+ },
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(horizontal = 15.dp),
+ )
+ }
+ if (userRole == UserRole.MANAGER) {
+ AttendanceCheckButton(
+ onAttendanceCheckButtonClicked = navigateToAttendanceManagement,
+ modifier = Modifier
+ .align(Alignment.BottomEnd)
+ .padding(end = 16.dp, bottom = 16.dp),
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+internal fun AttendanceGuestScreen(
+ onButtonClicked: () -> Unit,
+) {
+ Surface(
+ color = WappTheme.colors.backgroundBlack,
+ modifier = Modifier.fillMaxSize(),
+ ) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center,
+ ) {
+ Text(
+ text = stringResource(R.string.attendance_guset_title),
+ style = WappTheme.typography.titleBold,
+ color = WappTheme.colors.white,
+ modifier = Modifier.fillMaxWidth(),
+ textAlign = TextAlign.Center,
+ )
+
+ Text(
+ text = stringResource(R.string.attendance_guest_content),
+ style = WappTheme.typography.captionMedium,
+ color = WappTheme.colors.white,
+ modifier = Modifier.fillMaxWidth(),
+ textAlign = TextAlign.Center,
+ )
+
+ Spacer(modifier = Modifier.padding(vertical = 16.dp))
+
+ WappButton(
+ textRes = R.string.go_to_signin,
+ onClick = onButtonClicked,
+ modifier = Modifier.padding(horizontal = 32.dp),
+ )
+ }
+ }
+}
diff --git a/feature/attendance/src/main/java/com/wap/wapp/feature/attendance/AttendanceViewModel.kt b/feature/attendance/src/main/java/com/wap/wapp/feature/attendance/AttendanceViewModel.kt
new file mode 100644
index 000000000..bba29007f
--- /dev/null
+++ b/feature/attendance/src/main/java/com/wap/wapp/feature/attendance/AttendanceViewModel.kt
@@ -0,0 +1,199 @@
+package com.wap.wapp.feature.attendance
+
+import androidx.core.text.isDigitsOnly
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.wap.wapp.core.commmon.util.DateUtil
+import com.wap.wapp.core.domain.usecase.attendance.GetEventListAttendanceUseCase
+import com.wap.wapp.core.domain.usecase.attendance.ValidationAttendanceCodeUseCase
+import com.wap.wapp.core.domain.usecase.attendancestatus.GetEventListAttendanceStatusUseCase
+import com.wap.wapp.core.domain.usecase.attendancestatus.PostAttendanceStatusUseCase
+import com.wap.wapp.core.domain.usecase.event.GetDateEventListUseCase
+import com.wap.wapp.core.domain.usecase.user.GetUserProfileUseCase
+import com.wap.wapp.core.domain.usecase.user.GetUserRoleUseCase
+import com.wap.wapp.core.model.event.Event
+import com.wap.wapp.core.model.user.UserProfile
+import com.wap.wapp.core.model.user.UserRole
+import com.wap.wapp.feature.attendance.model.EventAttendanceStatus
+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 AttendanceViewModel @Inject constructor(
+ private val getDateEventListUseCase: GetDateEventListUseCase,
+ private val getEventListAttendanceUseCase: GetEventListAttendanceUseCase,
+ private val getEventListAttendanceStatusUseCase: GetEventListAttendanceStatusUseCase,
+ private val getUserRoleUseCase: GetUserRoleUseCase,
+ private val getUserProfileUseCase: GetUserProfileUseCase,
+ private val postAttendanceStatusUseCase: PostAttendanceStatusUseCase,
+ private val validationAttendanceCodeUseCase: ValidationAttendanceCodeUseCase,
+) : ViewModel() {
+ private val _errorFlow: MutableSharedFlow = MutableSharedFlow()
+ val errorFlow: SharedFlow = _errorFlow.asSharedFlow()
+
+ private val _attendanceEvent: MutableSharedFlow = MutableSharedFlow()
+ val attendanceEvent = _attendanceEvent.asSharedFlow()
+
+ private val _userProfile = MutableStateFlow(DEFAULT_USER_PROFILE)
+ val userProfile: StateFlow = _userProfile.asStateFlow()
+
+ private val _userRole = MutableStateFlow