diff --git a/.github/workflows/cd_release.yml b/.github/workflows/cd_release.yml new file mode 100644 index 0000000..c12d935 --- /dev/null +++ b/.github/workflows/cd_release.yml @@ -0,0 +1,60 @@ +name: Android CD Release + +on: + push: + branches: + - master # master 브랜치에 push 될 때 실행 + +jobs: + release: + runs-on: ubuntu-latest + + # Secrets를 환경 변수로 정의하여 run 스크립트에서 $변수 형태로 사용 가능 + env: + KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }} + KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} + KEY_ALIAS: ${{ secrets.KEY_ALIAS }} + KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + + - name: Make gradlew executable + run: chmod +x ./gradlew + + # Secrets에서 Base64 문자열을 디코딩하여 app/keystore.jks 파일로 저장 + - name: Decode Keystore File + # base64 -d 명령어를 사용하여 복호화 + run: echo "$KEYSTORE_BASE64" | base64 -d > app/keystore.jks + + # 서명에 필요한 비밀번호와 별칭 정보를 signing.properties 파일로 생성 + - name: Set up Signing Properties + run: | + echo "storeFile=keystore.jks" > ./signing.properties + echo "storePassword=$KEYSTORE_PASSWORD" >> ./signing.properties + echo "keyAlias=$KEY_ALIAS" >> ./signing.properties + echo "keyPassword=$KEY_PASSWORD" >> ./signing.properties + + # 릴리즈 AAB 빌드 실행 + - name: Build Release AAB + # 프로젝트 설정을 통해 서명된다 + run: ./gradlew bundleRelease + + # GitHub Release 생성 및 고정 메시지 적용 + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + # Git Tag가 푸시될 때만 Release를 생성 (예: v1.0.0) + if: startsWith(github.ref, 'refs/tags/') + with: + # 빌드된 AAB 파일 업로드 + files: app/build/outputs/bundle/release/app-release.aab + name: Release ${{ github.ref_name }} + body: | + 더 나은 모멘토를 위해 버그를 수정하고, 사용성을 개선했어요 \ No newline at end of file diff --git a/.github/workflows/ci_build.yml b/.github/workflows/ci_build.yml new file mode 100644 index 0000000..80e6fff --- /dev/null +++ b/.github/workflows/ci_build.yml @@ -0,0 +1,54 @@ +name: Android CI Build + +on: + push: + branches: + - develop # develop 브랜치에 push 될 때 실행 + pull_request: + branches: + - develop # develop 브랜치로 PR이 열리거나 업데이트 될 때 실행 + + +jobs: + build: + runs-on: ubuntu-latest # 워크플로우를 실행할 가상 환경 + + env: + GOOGLE_SERVICES_BASE64: ${{ secrets.GOOGLE_SERVICES_BASE64 }} + KAKAO_NATIVE_APP_KEY: ${{ secrets.KAKAO_NATIVE_APP_KEY }} + NAVER_CLIENT_ID: ${{ secrets.NAVER_CLIENT_ID }} + NAVER_CLIENT_SECRET: ${{ secrets.NAVER_CLIENT_SECRET }} + + steps: + - name: Checkout Code + uses: actions/checkout@v4 # GitHub 저장소 코드를 워크스페이스로 가져오기 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + + - name: Make gradlew executable + run: chmod +x ./gradlew # gradlew 파일에 실행 권한 부여 + + - name: Decode Google Services File + run: echo "$GOOGLE_SERVICES_BASE64" | base64 -d > app/google-services.json + + # Secrets를 사용하여 local.properties 파일 생성 + - name: Create local.properties for CI + run: | + echo "kakao.native.app.key=$KAKAO_NATIVE_APP_KEY" > local.properties + echo "naver.client.id=$NAVER_CLIENT_ID" >> local.properties + echo "naver.client.secret=$NAVER_CLIENT_SECRET" >> local.properties + + - name: Build Debug APK + run: ./gradlew assembleDebug # 디버그 APK 빌드 명령어 실행 + + # 빌드된 APK 아티팩트 저장 + - name: Upload APK Artifact + uses: actions/upload-artifact@v4 + with: + name: dngo-app-debug + path: app/build/outputs/apk/debug/app-debug.apk + retention-days: 90 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..32fa39f --- /dev/null +++ b/.gitignore @@ -0,0 +1,52 @@ +# Built application files +*.apk +*.aab +*.aar +*.ap_ + +# Files for the ART/Dalvik VM +*.dex + +# Gradle files +.gradle/ +build/ +local.properties +.idea/caches/ +.idea/libraries/ +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/misc.xml +.idea/modules.xml +.idea/vcs.xml + +# Kotlin files +*.kt.log +*.kt.test.log +.kotlin/ + +# Android Studio 4.x +.idea/runConfigurations.xml + +# Local configuration files (e.g., used by third-party tools) +.env +.env.local +.DS_Store + +# IDE specific files +.idea/ +*.iws +*.iml +*.ipr +.idea/codeStyles/ +.idea/gradle.xml +.idea/misc.xml +.idea/modules.xml +.idea/vcs.xml + +# Keystore files +*.jks +*.keystore + +# firebase +google-services.json diff --git a/README.assets/architecture.png b/README.assets/architecture.png new file mode 100644 index 0000000..f60f3f0 Binary files /dev/null and b/README.assets/architecture.png differ diff --git a/README.assets/image.png b/README.assets/image.png new file mode 100644 index 0000000..e1bee00 Binary files /dev/null and b/README.assets/image.png differ diff --git a/README.assets/image2.png b/README.assets/image2.png new file mode 100644 index 0000000..d0c8a7b Binary files /dev/null and b/README.assets/image2.png differ diff --git a/README.assets/image3.png b/README.assets/image3.png new file mode 100644 index 0000000..32632e8 Binary files /dev/null and b/README.assets/image3.png differ diff --git a/README.assets/image4.png b/README.assets/image4.png new file mode 100644 index 0000000..78779d0 Binary files /dev/null and b/README.assets/image4.png differ diff --git a/README.assets/image5.png b/README.assets/image5.png new file mode 100644 index 0000000..181d26c Binary files /dev/null and b/README.assets/image5.png differ diff --git a/README.assets/momento.png b/README.assets/momento.png new file mode 100644 index 0000000..e5d05cd Binary files /dev/null and b/README.assets/momento.png differ diff --git a/README.assets/play.png b/README.assets/play.png new file mode 100644 index 0000000..7a06997 Binary files /dev/null and b/README.assets/play.png differ diff --git a/README.md b/README.md index ba05efd..a8f64e4 100644 --- a/README.md +++ b/README.md @@ -1 +1,34 @@ -# dngo-android \ No newline at end of file +# 모멘토 + +

+ momento +    + + play + +

+ + + +## 🏝️ 여행을 기록하는 가장 감성적인 방법, 모멘토 + +- 자신의 여행 이야기를 간단하게 기록할 수 있어요 +- 행복했던 순간들을 한눈에 모아 관리해보세요 +- 원한다면 여행기록을 다른 사용자와 공유할 수도 있어요 +- 공유된 여행은 새로운 여행지를 찾는 사람들에게 영감을 줍니다 + +
+ +

+ image + image2 + image3 + image4 + image5 +

+ +
+ +## Android 앱 아키텍처 (MVVM + Clean Architecture) + +play \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..f34d9ac --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,144 @@ +import java.util.Properties + +// Properties 객체를 생성해 key-value 쌍을 저장할 준비를 한다 +val properties = Properties() +// 프로젝트의 루트 디렉토리에 있는 local.properties 파일을 가져온다 +val propertiesFile = project.rootProject.file("local.properties") +// 파일이 존재하는지 확인 후 로드 +if (propertiesFile.exists()) { + // 파일의 InputStream을 열어 속성값을 안전하게 불러온다 + // use 함수를 사용하면 스트림이 자동으로 닫힌다 + propertiesFile.inputStream().use { properties.load(it) } +} + +// CI/CD 서명 설정 파일 로드 +val signingProps = Properties() +val signingPropsFile = project.rootProject.file("signing.properties") +if (signingPropsFile.exists()) { + signingPropsFile.inputStream().use { signingProps.load(it) } +} + +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.compose) + alias(libs.plugins.google.services) + alias(libs.plugins.ksp) + alias(libs.plugins.hilt) + alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.firebase.crashlytics) +} + +android { + namespace = "com.min.dnapp" + compileSdk = 36 + + defaultConfig { + applicationId = "com.min.dnapp" + minSdk = 30 + targetSdk = 36 + versionCode = 10 + versionName = "2.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + + val kakaoNativeAppKey = properties.getProperty("kakao.native.app.key") + val naverClientId = properties.getProperty("naver.client.id") + val naverClientSecret = properties.getProperty("naver.client.secret") + + // 네이티브 앱 키를 BuildConfig에 필드로 추가 + buildConfigField("String", "KAKAO_NATIVE_APP_KEY", "\"${kakaoNativeAppKey}\"") + // AndroidManifest.xml에 전달할 플레이스홀더 정의 + manifestPlaceholders["kakaoNativeAppKey"] = kakaoNativeAppKey + + // 네이버 + buildConfigField("String", "NAVER_CLIENT_ID", "\"${naverClientId}\"") + buildConfigField("String", "NAVER_CLIENT_SECRET", "\"${naverClientSecret}\"") + } + + // CI/CD 서명 설정 + signingConfigs { + create("release") { + storeFile = if (signingProps.containsKey("storeFile")) file("app/${signingProps["storeFile"] as String}") else null + storePassword = signingProps["storePassword"] as String? + keyAlias = signingProps["keyAlias"] as String? + keyPassword = signingProps["keyPassword"] as String? + } + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + // release 빌드 타입에 서명 설정 적용 + signingConfig = signingConfigs.getByName("release") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = "17" + } + buildFeatures { + compose = true + // BuildConfig 파일 생성을 활성화 + buildConfig = true + } +} + +dependencies { + val composeBom = platform(libs.androidx.compose.bom) + implementation(composeBom) + androidTestImplementation(composeBom) + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.activity.compose) + implementation(libs.androidx.ui) + implementation(libs.androidx.ui.graphics) + implementation(libs.androidx.ui.tooling.preview) + implementation(libs.androidx.material3) + + // firebase + implementation(platform(libs.firebase.bom)) + implementation(libs.firebase.crashlytics) + implementation(libs.firebase.analytics) + implementation(libs.firebase.auth) + implementation(libs.firebase.firestore) + implementation(libs.firebase.storage) + // kakao + implementation(libs.kakao.sdk) + // compose navigation + implementation(libs.androidx.navigation.compose) + // hilt + implementation(libs.hilt.android) + ksp(libs.hilt.compiler) + implementation(libs.hilt.navigation.compose) + // splash screen + implementation(libs.splash) + // retrofit2 & kotlin serialization + implementation(libs.retrofit) + implementation(libs.retrofit.converter.kotlinx.serialization) + implementation(libs.kotlinx.serialization.json) + // okhttp (logging interceptor) + implementation(libs.okhttp.logging) + // coil + implementation(libs.coil) + implementation(libs.coil.okhttp) + // lottie compose + implementation(libs.lottie.compose) + // preferences datastore + implementation(libs.datastore.preferences) + + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(libs.androidx.ui.test.junit4) + debugImplementation(libs.androidx.ui.tooling) + debugImplementation(libs.androidx.ui.test.manifest) +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /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/min/dnapp/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/min/dnapp/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..137e947 --- /dev/null +++ b/app/src/androidTest/java/com/min/dnapp/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.min.dnapp + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * 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.min.dnapp", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..bed265b --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/assets/fireworks.json b/app/src/main/assets/fireworks.json new file mode 100644 index 0000000..4f133f9 --- /dev/null +++ b/app/src/main/assets/fireworks.json @@ -0,0 +1 @@ +{"v":"5.1.4","fr":60,"ip":0,"op":240,"w":375,"h":375,"nm":"Comp 2","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[187.5,187.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[1,1.083]},"o":{"x":[0.012,1],"y":[0,0]},"n":["0p833_1_0p012_0","0p833_1p083_1_0"],"t":0,"s":[0,1],"e":[93.75,1]},{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0.083]},"n":["0p667_1_0p167_0","0p667_1_0p167_0p083"],"t":15,"s":[93.75,1],"e":[0,0]},{"t":30}],"ix":2},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"n":"0p833_0p833_0p333_0","t":0,"s":[-187.5,0],"e":[-93.75,0],"to":[15.625,0],"ti":[-31.25,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"n":"0p667_1_0p167_0p167","t":15,"s":[-93.75,0],"e":[0,0],"to":[31.25,0],"ti":[-15.625,0]},{"t":30}],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.490196078431,0.698039215686,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":15,"ix":10},"p":{"a":0,"k":[187.5,187.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.03,0.03,0.333],"y":[0,0,0]},"n":["0_1_0p03_0","0_1_0p03_0","0p667_1_0p333_0"],"t":36,"s":[0,0,100],"e":[100,100,100]},{"t":104}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"sr","sy":2,"d":1,"pt":{"a":0,"k":3,"ix":3},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":36,"s":[0,0],"e":[0,100],"to":[0,16.6666660308838],"ti":[0,-16.6666660308838]},{"t":104}],"ix":4},"r":{"a":0,"k":0,"ix":5},"or":{"a":0,"k":20,"ix":7},"os":{"a":0,"k":0,"ix":9},"ix":1,"nm":"Polystar Path 1","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.490196078431,0.698039215686,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-5.023,10.539],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.03,0.03],"y":[0,0]},"n":["0_1_0p03_0","0_1_0p03_0"],"t":36,"s":[60,0],"e":[0,100]},{"t":104}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"rp","c":{"a":0,"k":12,"ix":1},"o":{"a":0,"k":0,"ix":2},"m":1,"ix":2,"tr":{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":30,"ix":4},"so":{"a":0,"k":100,"ix":5},"eo":{"a":0,"k":100,"ix":6},"nm":"Transform"},"nm":"Repeater 1","mn":"ADBE Vector Filter - Repeater","hd":false}],"ip":34,"op":105,"st":28,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":7.5,"ix":10},"p":{"a":0,"k":[187.5,187.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.03,0.03,0.333],"y":[0,0,0]},"n":["0_1_0p03_0","0_1_0p03_0","0p667_1_0p333_0"],"t":32,"s":[0,0,100],"e":[100,100,100]},{"t":96}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"sr","sy":2,"d":1,"pt":{"a":0,"k":3,"ix":3},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":32,"s":[0,0],"e":[0,100],"to":[0,16.6666660308838],"ti":[0,-16.6666660308838]},{"t":96}],"ix":4},"r":{"a":0,"k":0,"ix":5},"or":{"a":0,"k":20,"ix":7},"os":{"a":0,"k":0,"ix":9},"ix":1,"nm":"Polystar Path 1","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.750827205882,0.852413042854,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-5.023,10.539],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.03,0.03],"y":[0,0]},"n":["0_1_0p03_0","0_1_0p03_0"],"t":32,"s":[60,0],"e":[0,100]},{"t":96}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"rp","c":{"a":0,"k":24,"ix":1},"o":{"a":0,"k":0,"ix":2},"m":1,"ix":2,"tr":{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":15,"ix":4},"so":{"a":0,"k":100,"ix":5},"eo":{"a":0,"k":100,"ix":6},"nm":"Transform"},"nm":"Repeater 1","mn":"ADBE Vector Filter - Repeater","hd":false}],"ip":30,"op":98,"st":28,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[187.5,187.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.03,0.03,0.333],"y":[0,0,0]},"n":["0_1_0p03_0","0_1_0p03_0","0p667_1_0p333_0"],"t":28,"s":[0,0,100],"e":[100,100,100]},{"t":88}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"sr","sy":2,"d":1,"pt":{"a":0,"k":3,"ix":3},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":28,"s":[0,0],"e":[0,100],"to":[0,16.6666660308838],"ti":[0,-16.6666660308838]},{"t":88}],"ix":4},"r":{"a":0,"k":0,"ix":5},"or":{"a":0,"k":20,"ix":7},"os":{"a":0,"k":0,"ix":9},"ix":1,"nm":"Polystar Path 1","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.490196078431,0.698039215686,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-5.023,10.539],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.03,0.03],"y":[0,0]},"n":["0_1_0p03_0","0_1_0p03_0"],"t":28,"s":[60,0],"e":[0,100]},{"t":88}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"rp","c":{"a":0,"k":12,"ix":1},"o":{"a":0,"k":0,"ix":2},"m":1,"ix":2,"tr":{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":30,"ix":4},"so":{"a":0,"k":100,"ix":5},"eo":{"a":0,"k":100,"ix":6},"nm":"Transform"},"nm":"Repeater 1","mn":"ADBE Vector Filter - Repeater","hd":false}],"ip":28,"op":91,"st":28,"bm":0}]},{"id":"comp_1","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[187.5,187.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[1,1.083]},"o":{"x":[0.012,1],"y":[0,0]},"n":["0p833_1_0p012_0","0p833_1p083_1_0"],"t":0,"s":[0,1],"e":[93.75,1]},{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0.083]},"n":["0p667_1_0p167_0","0p667_1_0p167_0p083"],"t":15,"s":[93.75,1],"e":[0,0]},{"t":30}],"ix":2},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"n":"0p833_0p833_0p333_0","t":0,"s":[-187.5,0],"e":[-93.75,0],"to":[15.625,0],"ti":[-31.25,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"n":"0p667_1_0p167_0p167","t":15,"s":[-93.75,0],"e":[0,0],"to":[31.25,0],"ti":[-15.625,0]},{"t":30}],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.486274509804,0.364705882353,0.929411764706,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":15,"ix":10},"p":{"a":0,"k":[187.5,187.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.03,0.03,0.333],"y":[0,0,0]},"n":["0_1_0p03_0","0_1_0p03_0","0p667_1_0p333_0"],"t":36,"s":[0,0,100],"e":[100,100,100]},{"t":104}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"sr","sy":2,"d":1,"pt":{"a":0,"k":3,"ix":3},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":36,"s":[0,0],"e":[0,100],"to":[0,16.6666660308838],"ti":[0,-16.6666660308838]},{"t":104}],"ix":4},"r":{"a":0,"k":0,"ix":5},"or":{"a":0,"k":20,"ix":7},"os":{"a":0,"k":0,"ix":9},"ix":1,"nm":"Polystar Path 1","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.486274509804,0.364705882353,0.929411764706,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-5.023,10.539],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.03,0.03],"y":[0,0]},"n":["0_1_0p03_0","0_1_0p03_0"],"t":36,"s":[60,0],"e":[0,100]},{"t":104}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"rp","c":{"a":0,"k":12,"ix":1},"o":{"a":0,"k":0,"ix":2},"m":1,"ix":2,"tr":{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":30,"ix":4},"so":{"a":0,"k":100,"ix":5},"eo":{"a":0,"k":100,"ix":6},"nm":"Transform"},"nm":"Repeater 1","mn":"ADBE Vector Filter - Repeater","hd":false}],"ip":34,"op":105,"st":28,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":7.5,"ix":10},"p":{"a":0,"k":[187.5,187.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.03,0.03,0.333],"y":[0,0,0]},"n":["0_1_0p03_0","0_1_0p03_0","0p667_1_0p333_0"],"t":32,"s":[0,0,100],"e":[100,100,100]},{"t":96}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"sr","sy":2,"d":1,"pt":{"a":0,"k":3,"ix":3},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":32,"s":[0,0],"e":[0,100],"to":[0,16.6666660308838],"ti":[0,-16.6666660308838]},{"t":96}],"ix":4},"r":{"a":0,"k":0,"ix":5},"or":{"a":0,"k":20,"ix":7},"os":{"a":0,"k":0,"ix":9},"ix":1,"nm":"Polystar Path 1","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.643137254902,0.556862745098,0.952941176471,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-5.023,10.539],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.03,0.03],"y":[0,0]},"n":["0_1_0p03_0","0_1_0p03_0"],"t":32,"s":[60,0],"e":[0,100]},{"t":96}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"rp","c":{"a":0,"k":24,"ix":1},"o":{"a":0,"k":0,"ix":2},"m":1,"ix":2,"tr":{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":15,"ix":4},"so":{"a":0,"k":100,"ix":5},"eo":{"a":0,"k":100,"ix":6},"nm":"Transform"},"nm":"Repeater 1","mn":"ADBE Vector Filter - Repeater","hd":false}],"ip":30,"op":98,"st":28,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[187.5,187.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.03,0.03,0.333],"y":[0,0,0]},"n":["0_1_0p03_0","0_1_0p03_0","0p667_1_0p333_0"],"t":28,"s":[0,0,100],"e":[100,100,100]},{"t":88}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"sr","sy":2,"d":1,"pt":{"a":0,"k":3,"ix":3},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":28,"s":[0,0],"e":[0,100],"to":[0,16.6666660308838],"ti":[0,-16.6666660308838]},{"t":88}],"ix":4},"r":{"a":0,"k":0,"ix":5},"or":{"a":0,"k":20,"ix":7},"os":{"a":0,"k":0,"ix":9},"ix":1,"nm":"Polystar Path 1","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.486274509804,0.364705882353,0.929411764706,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-5.023,10.539],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.03,0.03],"y":[0,0]},"n":["0_1_0p03_0","0_1_0p03_0"],"t":28,"s":[60,0],"e":[0,100]},{"t":88}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"rp","c":{"a":0,"k":12,"ix":1},"o":{"a":0,"k":0,"ix":2},"m":1,"ix":2,"tr":{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":30,"ix":4},"so":{"a":0,"k":100,"ix":5},"eo":{"a":0,"k":100,"ix":6},"nm":"Transform"},"nm":"Repeater 1","mn":"ADBE Vector Filter - Repeater","hd":false}],"ip":28,"op":91,"st":28,"bm":0}]},{"id":"comp_2","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[187.5,187.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[1,1.083]},"o":{"x":[0.012,1],"y":[0,0]},"n":["0p833_1_0p012_0","0p833_1p083_1_0"],"t":0,"s":[0,1],"e":[93.75,1]},{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0.083]},"n":["0p667_1_0p167_0","0p667_1_0p167_0p083"],"t":15,"s":[93.75,1],"e":[0,0]},{"t":30}],"ix":2},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"n":"0p833_0p833_0p333_0","t":0,"s":[-187.5,0],"e":[-93.75,0],"to":[15.625,0],"ti":[-31.25,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"n":"0p667_1_0p167_0p167","t":15,"s":[-93.75,0],"e":[0,0],"to":[31.25,0],"ti":[-15.625,0]},{"t":30}],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.494117647059,0.839215686275,0.956862745098,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":15,"ix":10},"p":{"a":0,"k":[187.5,187.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.03,0.03,0.333],"y":[0,0,0]},"n":["0_1_0p03_0","0_1_0p03_0","0p667_1_0p333_0"],"t":36,"s":[0,0,100],"e":[100,100,100]},{"t":104}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"sr","sy":2,"d":1,"pt":{"a":0,"k":3,"ix":3},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":36,"s":[0,0],"e":[0,100],"to":[0,16.6666660308838],"ti":[0,-16.6666660308838]},{"t":104}],"ix":4},"r":{"a":0,"k":0,"ix":5},"or":{"a":0,"k":20,"ix":7},"os":{"a":0,"k":0,"ix":9},"ix":1,"nm":"Polystar Path 1","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.494117647059,0.839215686275,0.956862745098,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-5.023,10.539],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.03,0.03],"y":[0,0]},"n":["0_1_0p03_0","0_1_0p03_0"],"t":36,"s":[60,0],"e":[0,100]},{"t":104}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"rp","c":{"a":0,"k":12,"ix":1},"o":{"a":0,"k":0,"ix":2},"m":1,"ix":2,"tr":{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":30,"ix":4},"so":{"a":0,"k":100,"ix":5},"eo":{"a":0,"k":100,"ix":6},"nm":"Transform"},"nm":"Repeater 1","mn":"ADBE Vector Filter - Repeater","hd":false}],"ip":34,"op":105,"st":28,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":7.5,"ix":10},"p":{"a":0,"k":[187.5,187.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.03,0.03,0.333],"y":[0,0,0]},"n":["0_1_0p03_0","0_1_0p03_0","0p667_1_0p333_0"],"t":32,"s":[0,0,100],"e":[100,100,100]},{"t":96}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"sr","sy":2,"d":1,"pt":{"a":0,"k":3,"ix":3},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":32,"s":[0,0],"e":[0,100],"to":[0,16.6666660308838],"ti":[0,-16.6666660308838]},{"t":96}],"ix":4},"r":{"a":0,"k":0,"ix":5},"or":{"a":0,"k":20,"ix":7},"os":{"a":0,"k":0,"ix":9},"ix":1,"nm":"Polystar Path 1","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.730839568493,0.892384906844,0.947457107843,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-5.023,10.539],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.03,0.03],"y":[0,0]},"n":["0_1_0p03_0","0_1_0p03_0"],"t":32,"s":[60,0],"e":[0,100]},{"t":96}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"rp","c":{"a":0,"k":24,"ix":1},"o":{"a":0,"k":0,"ix":2},"m":1,"ix":2,"tr":{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":15,"ix":4},"so":{"a":0,"k":100,"ix":5},"eo":{"a":0,"k":100,"ix":6},"nm":"Transform"},"nm":"Repeater 1","mn":"ADBE Vector Filter - Repeater","hd":false}],"ip":30,"op":98,"st":28,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[187.5,187.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.03,0.03,0.333],"y":[0,0,0]},"n":["0_1_0p03_0","0_1_0p03_0","0p667_1_0p333_0"],"t":28,"s":[0,0,100],"e":[100,100,100]},{"t":88}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"sr","sy":2,"d":1,"pt":{"a":0,"k":3,"ix":3},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":28,"s":[0,0],"e":[0,100],"to":[0,16.6666660308838],"ti":[0,-16.6666660308838]},{"t":88}],"ix":4},"r":{"a":0,"k":0,"ix":5},"or":{"a":0,"k":20,"ix":7},"os":{"a":0,"k":0,"ix":9},"ix":1,"nm":"Polystar Path 1","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.494117647059,0.839215686275,0.956862745098,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-5.023,10.539],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.03,0.03],"y":[0,0]},"n":["0_1_0p03_0","0_1_0p03_0"],"t":28,"s":[60,0],"e":[0,100]},{"t":88}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"rp","c":{"a":0,"k":12,"ix":1},"o":{"a":0,"k":0,"ix":2},"m":1,"ix":2,"tr":{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":30,"ix":4},"so":{"a":0,"k":100,"ix":5},"eo":{"a":0,"k":100,"ix":6},"nm":"Transform"},"nm":"Repeater 1","mn":"ADBE Vector Filter - Repeater","hd":false}],"ip":28,"op":91,"st":28,"bm":0}]},{"id":"comp_3","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[187.5,187.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[1,1.083]},"o":{"x":[0.012,1],"y":[0,0]},"n":["0p833_1_0p012_0","0p833_1p083_1_0"],"t":0,"s":[0,1],"e":[93.75,1]},{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0.083]},"n":["0p667_1_0p167_0","0p667_1_0p167_0p083"],"t":15,"s":[93.75,1],"e":[0,0]},{"t":30}],"ix":2},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"n":"0p833_0p833_0p333_0","t":0,"s":[-187.5,0],"e":[-93.75,0],"to":[15.625,0],"ti":[-31.25,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"n":"0p667_1_0p167_0p167","t":15,"s":[-93.75,0],"e":[0,0],"to":[31.25,0],"ti":[-15.625,0]},{"t":30}],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.717647058824,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":15,"ix":10},"p":{"a":0,"k":[187.5,187.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.03,0.03,0.333],"y":[0,0,0]},"n":["0_1_0p03_0","0_1_0p03_0","0p667_1_0p333_0"],"t":36,"s":[0,0,100],"e":[100,100,100]},{"t":104}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"sr","sy":2,"d":1,"pt":{"a":0,"k":3,"ix":3},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":36,"s":[0,0],"e":[0,100],"to":[0,16.6666660308838],"ti":[0,-16.6666660308838]},{"t":104}],"ix":4},"r":{"a":0,"k":0,"ix":5},"or":{"a":0,"k":20,"ix":7},"os":{"a":0,"k":0,"ix":9},"ix":1,"nm":"Polystar Path 1","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.717647058824,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-5.023,10.539],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.03,0.03],"y":[0,0]},"n":["0_1_0p03_0","0_1_0p03_0"],"t":36,"s":[70,0],"e":[0,100]},{"t":104}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"rp","c":{"a":0,"k":12,"ix":1},"o":{"a":0,"k":0,"ix":2},"m":1,"ix":2,"tr":{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":30,"ix":4},"so":{"a":0,"k":100,"ix":5},"eo":{"a":0,"k":100,"ix":6},"nm":"Transform"},"nm":"Repeater 1","mn":"ADBE Vector Filter - Repeater","hd":false}],"ip":34,"op":105,"st":28,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":7.5,"ix":10},"p":{"a":0,"k":[187.5,187.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.03,0.03,0.333],"y":[0,0,0]},"n":["0_1_0p03_0","0_1_0p03_0","0p667_1_0p333_0"],"t":32,"s":[0,0,100],"e":[100,100,100]},{"t":96}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"sr","sy":2,"d":1,"pt":{"a":0,"k":3,"ix":3},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":32,"s":[0,0],"e":[0,100],"to":[0,16.6666660308838],"ti":[0,-16.6666660308838]},{"t":96}],"ix":4},"r":{"a":0,"k":0,"ix":5},"or":{"a":0,"k":20,"ix":7},"os":{"a":0,"k":0,"ix":9},"ix":1,"nm":"Polystar Path 1","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.82023788153,0.36334252451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-5.023,10.539],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.03,0.03],"y":[0,0]},"n":["0_1_0p03_0","0_1_0p03_0"],"t":32,"s":[70,0],"e":[0,100]},{"t":96}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"rp","c":{"a":0,"k":24,"ix":1},"o":{"a":0,"k":0,"ix":2},"m":1,"ix":2,"tr":{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":15,"ix":4},"so":{"a":0,"k":100,"ix":5},"eo":{"a":0,"k":100,"ix":6},"nm":"Transform"},"nm":"Repeater 1","mn":"ADBE Vector Filter - Repeater","hd":false}],"ip":30,"op":98,"st":28,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[187.5,187.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.03,0.03,0.333],"y":[0,0,0]},"n":["0_1_0p03_0","0_1_0p03_0","0p667_1_0p333_0"],"t":28,"s":[0,0,100],"e":[100,100,100]},{"t":88}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"sr","sy":2,"d":1,"pt":{"a":0,"k":3,"ix":3},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":28,"s":[0,0],"e":[0,100],"to":[0,16.6666660308838],"ti":[0,-16.6666660308838]},{"t":88}],"ix":4},"r":{"a":0,"k":0,"ix":5},"or":{"a":0,"k":20,"ix":7},"os":{"a":0,"k":0,"ix":9},"ix":1,"nm":"Polystar Path 1","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.717647058824,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-5.023,10.539],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.03,0.03],"y":[0,0]},"n":["0_1_0p03_0","0_1_0p03_0"],"t":28,"s":[70,0],"e":[0,100]},{"t":88}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"rp","c":{"a":0,"k":12,"ix":1},"o":{"a":0,"k":0,"ix":2},"m":1,"ix":2,"tr":{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":30,"ix":4},"so":{"a":0,"k":100,"ix":5},"eo":{"a":0,"k":100,"ix":6},"nm":"Transform"},"nm":"Repeater 1","mn":"ADBE Vector Filter - Repeater","hd":false}],"ip":28,"op":91,"st":28,"bm":0}]},{"id":"comp_4","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[187.5,187.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[1,1.083]},"o":{"x":[0.012,1],"y":[0,0]},"n":["0p833_1_0p012_0","0p833_1p083_1_0"],"t":0,"s":[0,1],"e":[93.75,1]},{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0.083]},"n":["0p667_1_0p167_0","0p667_1_0p167_0p083"],"t":15,"s":[93.75,1],"e":[0,0]},{"t":30}],"ix":2},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"n":"0p833_0p833_0p333_0","t":0,"s":[-187.5,0],"e":[-93.75,0],"to":[15.625,0],"ti":[-31.25,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"n":"0p667_1_0p167_0p167","t":15,"s":[-93.75,0],"e":[0,0],"to":[31.25,0],"ti":[-15.625,0]},{"t":30}],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.839215686275,0.827450980392,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":15,"ix":10},"p":{"a":0,"k":[187.5,187.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.03,0.03,0.333],"y":[0,0,0]},"n":["0_1_0p03_0","0_1_0p03_0","0p667_1_0p333_0"],"t":36,"s":[0,0,100],"e":[100,100,100]},{"t":104}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"sr","sy":2,"d":1,"pt":{"a":0,"k":3,"ix":3},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":36,"s":[0,0],"e":[0,100],"to":[0,16.6666660308838],"ti":[0,-16.6666660308838]},{"t":104}],"ix":4},"r":{"a":0,"k":0,"ix":5},"or":{"a":0,"k":20,"ix":7},"os":{"a":0,"k":0,"ix":9},"ix":1,"nm":"Polystar Path 1","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.839215686275,0.827450980392,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-5.023,10.539],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.03,0.03],"y":[0,0]},"n":["0_1_0p03_0","0_1_0p03_0"],"t":36,"s":[75,0],"e":[0,100]},{"t":104}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"rp","c":{"a":0,"k":12,"ix":1},"o":{"a":0,"k":0,"ix":2},"m":1,"ix":2,"tr":{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":30,"ix":4},"so":{"a":0,"k":100,"ix":5},"eo":{"a":0,"k":100,"ix":6},"nm":"Transform"},"nm":"Repeater 1","mn":"ADBE Vector Filter - Repeater","hd":false}],"ip":34,"op":105,"st":28,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":7.5,"ix":10},"p":{"a":0,"k":[187.5,187.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.03,0.03,0.333],"y":[0,0,0]},"n":["0_1_0p03_0","0_1_0p03_0","0p667_1_0p333_0"],"t":32,"s":[0,0,100],"e":[100,100,100]},{"t":96}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"sr","sy":2,"d":1,"pt":{"a":0,"k":3,"ix":3},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":32,"s":[0,0],"e":[0,100],"to":[0,16.6666660308838],"ti":[0,-16.6666660308838]},{"t":96}],"ix":4},"r":{"a":0,"k":0,"ix":5},"or":{"a":0,"k":20,"ix":7},"os":{"a":0,"k":0,"ix":9},"ix":1,"nm":"Polystar Path 1","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.408093830183,0.822411151961,0.816602998621,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-5.023,10.539],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.03,0.03],"y":[0,0]},"n":["0_1_0p03_0","0_1_0p03_0"],"t":32,"s":[75,0],"e":[0,100]},{"t":96}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"rp","c":{"a":0,"k":24,"ix":1},"o":{"a":0,"k":0,"ix":2},"m":1,"ix":2,"tr":{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":15,"ix":4},"so":{"a":0,"k":100,"ix":5},"eo":{"a":0,"k":100,"ix":6},"nm":"Transform"},"nm":"Repeater 1","mn":"ADBE Vector Filter - Repeater","hd":false}],"ip":30,"op":98,"st":28,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[187.5,187.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.03,0.03,0.333],"y":[0,0,0]},"n":["0_1_0p03_0","0_1_0p03_0","0p667_1_0p333_0"],"t":28,"s":[0,0,100],"e":[100,100,100]},{"t":88}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"sr","sy":2,"d":1,"pt":{"a":0,"k":3,"ix":3},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":28,"s":[0,0],"e":[0,100],"to":[0,16.6666660308838],"ti":[0,-16.6666660308838]},{"t":88}],"ix":4},"r":{"a":0,"k":0,"ix":5},"or":{"a":0,"k":20,"ix":7},"os":{"a":0,"k":0,"ix":9},"ix":1,"nm":"Polystar Path 1","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.839215686275,0.827450980392,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-5.023,10.539],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.03,0.03],"y":[0,0]},"n":["0_1_0p03_0","0_1_0p03_0"],"t":28,"s":[75,0],"e":[0,100]},{"t":88}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"rp","c":{"a":0,"k":12,"ix":1},"o":{"a":0,"k":0,"ix":2},"m":1,"ix":2,"tr":{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":30,"ix":4},"so":{"a":0,"k":100,"ix":5},"eo":{"a":0,"k":100,"ix":6},"nm":"Transform"},"nm":"Repeater 1","mn":"ADBE Vector Filter - Repeater","hd":false}],"ip":28,"op":91,"st":28,"bm":0}]},{"id":"comp_5","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[187.5,187.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[1,1.083]},"o":{"x":[0.012,1],"y":[0,0]},"n":["0p833_1_0p012_0","0p833_1p083_1_0"],"t":0,"s":[0,1],"e":[93.75,1]},{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0.083]},"n":["0p667_1_0p167_0","0p667_1_0p167_0p083"],"t":15,"s":[93.75,1],"e":[0,0]},{"t":30}],"ix":2},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"n":"0p833_0p833_0p333_0","t":0,"s":[-187.5,0],"e":[-93.75,0],"to":[15.625,0],"ti":[-31.25,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"n":"0p667_1_0p167_0p167","t":15,"s":[-93.75,0],"e":[0,0],"to":[31.25,0],"ti":[-15.625,0]},{"t":30}],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.443137258291,0.372549027205,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":15,"ix":10},"p":{"a":0,"k":[187.5,187.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.03,0.03,0.333],"y":[0,0,0]},"n":["0_1_0p03_0","0_1_0p03_0","0p667_1_0p333_0"],"t":36,"s":[0,0,100],"e":[100,100,100]},{"t":104}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"sr","sy":2,"d":1,"pt":{"a":0,"k":3,"ix":3},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":36,"s":[0,0],"e":[0,100],"to":[0,16.6666660308838],"ti":[0,-16.6666660308838]},{"t":104}],"ix":4},"r":{"a":0,"k":0,"ix":5},"or":{"a":0,"k":20,"ix":7},"os":{"a":0,"k":0,"ix":9},"ix":1,"nm":"Polystar Path 1","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.443137258291,0.372549027205,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-5.023,10.539],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.03,0.03],"y":[0,0]},"n":["0_1_0p03_0","0_1_0p03_0"],"t":36,"s":[75,0],"e":[0,100]},{"t":104}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"rp","c":{"a":0,"k":12,"ix":1},"o":{"a":0,"k":0,"ix":2},"m":1,"ix":2,"tr":{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":30,"ix":4},"so":{"a":0,"k":100,"ix":5},"eo":{"a":0,"k":100,"ix":6},"nm":"Transform"},"nm":"Repeater 1","mn":"ADBE Vector Filter - Repeater","hd":false}],"ip":34,"op":105,"st":28,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":7.5,"ix":10},"p":{"a":0,"k":[187.5,187.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.03,0.03,0.333],"y":[0,0,0]},"n":["0_1_0p03_0","0_1_0p03_0","0p667_1_0p333_0"],"t":32,"s":[0,0,100],"e":[100,100,100]},{"t":96}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"sr","sy":2,"d":1,"pt":{"a":0,"k":3,"ix":3},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":32,"s":[0,0],"e":[0,100],"to":[0,16.6666660308838],"ti":[0,-16.6666660308838]},{"t":96}],"ix":4},"r":{"a":0,"k":0,"ix":5},"or":{"a":0,"k":20,"ix":7},"os":{"a":0,"k":0,"ix":9},"ix":1,"nm":"Polystar Path 1","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.607843160629,0.560784339905,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-5.023,10.539],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.03,0.03],"y":[0,0]},"n":["0_1_0p03_0","0_1_0p03_0"],"t":32,"s":[75,0],"e":[0,100]},{"t":96}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"rp","c":{"a":0,"k":24,"ix":1},"o":{"a":0,"k":0,"ix":2},"m":1,"ix":2,"tr":{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":15,"ix":4},"so":{"a":0,"k":100,"ix":5},"eo":{"a":0,"k":100,"ix":6},"nm":"Transform"},"nm":"Repeater 1","mn":"ADBE Vector Filter - Repeater","hd":false}],"ip":30,"op":98,"st":28,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[187.5,187.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.03,0.03,0.333],"y":[0,0,0]},"n":["0_1_0p03_0","0_1_0p03_0","0p667_1_0p333_0"],"t":28,"s":[0,0,100],"e":[100,100,100]},{"t":88}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"sr","sy":2,"d":1,"pt":{"a":0,"k":3,"ix":3},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":28,"s":[0,0],"e":[0,100],"to":[0,16.6666660308838],"ti":[0,-16.6666660308838]},{"t":88}],"ix":4},"r":{"a":0,"k":0,"ix":5},"or":{"a":0,"k":20,"ix":7},"os":{"a":0,"k":0,"ix":9},"ix":1,"nm":"Polystar Path 1","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.443137258291,0.372549027205,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-5.023,10.539],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.03,0.03],"y":[0,0]},"n":["0_1_0p03_0","0_1_0p03_0"],"t":28,"s":[75,0],"e":[0,100]},{"t":88}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"rp","c":{"a":0,"k":12,"ix":1},"o":{"a":0,"k":0,"ix":2},"m":1,"ix":2,"tr":{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":30,"ix":4},"so":{"a":0,"k":100,"ix":5},"eo":{"a":0,"k":100,"ix":6},"nm":"Transform"},"nm":"Repeater 1","mn":"ADBE Vector Filter - Repeater","hd":false}],"ip":28,"op":91,"st":28,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"Comp 7","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[307.473,158.568,0],"ix":2},"a":{"a":0,"k":[187.5,187.5,0],"ix":1},"s":{"a":0,"k":[50,50,100],"ix":6}},"ao":0,"w":375,"h":375,"ip":104,"op":224,"st":104,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"Comp 6","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[79.867,175.707,0],"ix":2},"a":{"a":0,"k":[187.5,187.5,0],"ix":1},"s":{"a":0,"k":[60,60,100],"ix":6}},"ao":0,"w":375,"h":375,"ip":120,"op":240,"st":120,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"Comp 5","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[184.758,96.182,0],"ix":2},"a":{"a":0,"k":[187.5,187.5,0],"ix":1},"s":{"a":0,"k":[70,70,100],"ix":6}},"ao":0,"w":375,"h":375,"ip":90,"op":210,"st":90,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"Comp 4","refId":"comp_3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[86.723,167.48,0],"ix":2},"a":{"a":0,"k":[187.5,187.5,0],"ix":1},"s":{"a":0,"k":[60,60,100],"ix":6}},"ao":0,"w":375,"h":375,"ip":25,"op":145,"st":25,"bm":0},{"ddd":0,"ind":5,"ty":0,"nm":"Comp 3","refId":"comp_4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[286.906,216.84,0],"ix":2},"a":{"a":0,"k":[187.5,187.5,0],"ix":1},"s":{"a":0,"k":[75,75,100],"ix":6}},"ao":0,"w":375,"h":375,"ip":16,"op":136,"st":16,"bm":0},{"ddd":0,"ind":6,"ty":0,"nm":"Comp 1","refId":"comp_5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[187.5,108.5,0],"ix":2},"a":{"a":0,"k":[187.5,187.5,0],"ix":1},"s":{"a":0,"k":[80,80,100],"ix":6}},"ao":0,"w":375,"h":375,"ip":0,"op":120,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/app/src/main/ic_momento-playstore.png b/app/src/main/ic_momento-playstore.png new file mode 100644 index 0000000..f4ee45f Binary files /dev/null and b/app/src/main/ic_momento-playstore.png differ diff --git a/app/src/main/java/com/min/dnapp/GlobalApp.kt b/app/src/main/java/com/min/dnapp/GlobalApp.kt new file mode 100644 index 0000000..b7c1a17 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/GlobalApp.kt @@ -0,0 +1,14 @@ +package com.min.dnapp + +import android.app.Application +import com.kakao.sdk.common.KakaoSdk +import dagger.hilt.android.HiltAndroidApp + +@HiltAndroidApp +class GlobalApp : Application() { + override fun onCreate() { + super.onCreate() + // 카카오 SDK 초기화 + KakaoSdk.init(this, BuildConfig.KAKAO_NATIVE_APP_KEY) + } +} diff --git a/app/src/main/java/com/min/dnapp/MainActivity.kt b/app/src/main/java/com/min/dnapp/MainActivity.kt new file mode 100644 index 0000000..6e1fdb0 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/MainActivity.kt @@ -0,0 +1,147 @@ +package com.min.dnapp + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController +import com.min.dnapp.presentation.AppStartViewModel +import com.min.dnapp.presentation.bell.BellScreen +import com.min.dnapp.presentation.find.FindDetailScreen +import com.min.dnapp.presentation.find.FindScreen +import com.min.dnapp.presentation.home.HomeScreen2 +import com.min.dnapp.presentation.login.LoginScreen2 +import com.min.dnapp.presentation.mypage.MyRecordScreen +import com.min.dnapp.presentation.mypage.MypageScreen +import com.min.dnapp.presentation.mypage.SettingScreen +import com.min.dnapp.presentation.navigation.AppInitHost +import com.min.dnapp.presentation.ui.component.MomentoBottomNav +import com.min.dnapp.presentation.ui.theme.DngoTheme +import com.min.dnapp.presentation.write.RecordWriteScreen +import com.min.dnapp.presentation.write.WriteFinishScreen +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + // 스플래시 화면 설정 + val splashScreen = installSplashScreen() + + super.onCreate(savedInstanceState) + + // 콘텐츠를 화면 끝까지 확장 (Insets 처리는 Composable에게 맡기기) + enableEdgeToEdge() + + setContent { + DngoTheme { + MomentoApp() + } + } + } +} + +@Composable +fun MomentoApp( + appStartViewModel: AppStartViewModel = hiltViewModel() +) { + val isLogin by appStartViewModel.isLogin.collectAsStateWithLifecycle() + val startDestination = if (isLogin) "init_host" else "login" + + // root navigation 담당 + val navController = rememberNavController() + + // root navigation 경로 + val navBackStackEntry by navController.currentBackStackEntryAsState() + val currentRoute = navBackStackEntry?.destination?.route ?: startDestination + + val showBottomBar = when (currentRoute) { + "home", "find", "my" -> true + else -> false + } + + Scaffold( + // Padding 중복을 막기 위해 모든 Insets 차단 + contentWindowInsets = WindowInsets(0, 0, 0, 0), + bottomBar = { + if (showBottomBar) { + MomentoBottomNav( + currentRoute = currentRoute, + onNavItemClick = { route -> + // 현재 경로와 다를때만 navigate 호출 + if (navController.currentDestination?.route != route) { + navController.navigate(route) + } + } + ) + } + } + ) { paddingValues -> + NavHost( + modifier = Modifier.padding(paddingValues), + navController = navController, + startDestination = startDestination + ) { + composable("login") { + LoginScreen2( + navController = navController, + onLoginSuccess = { + navController.navigate("init_host") { + popUpTo("login") { inclusive = true } + } + } + ) + } + + composable("init_host") { + AppInitHost( + onInitComplete = { + navController.navigate("home") { + popUpTo("init_host") { inclusive = true } + } + } + ) + } + + composable("home") { + HomeScreen2(navController = navController) + } + composable("find") { + FindScreen(navController = navController) + } + composable("my") { + MypageScreen(navController = navController) + } + + composable("bell") { + BellScreen(navController = navController) + } + composable("find_detail") { + FindDetailScreen(navController = navController) + } + composable("record_write") { + RecordWriteScreen(navController = navController) + } + composable("write_finish") { + WriteFinishScreen(navController = navController) + } + composable("my_record") { + MyRecordScreen(navController = navController) + } + composable("setting") { + SettingScreen(navController = navController) + } + } + } +} diff --git a/app/src/main/java/com/min/dnapp/data/datasource/AppPreferencesDataStore.kt b/app/src/main/java/com/min/dnapp/data/datasource/AppPreferencesDataStore.kt new file mode 100644 index 0000000..b76409c --- /dev/null +++ b/app/src/main/java/com/min/dnapp/data/datasource/AppPreferencesDataStore.kt @@ -0,0 +1,58 @@ +package com.min.dnapp.data.datasource + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.core.IOException +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.emptyPreferences +import androidx.datastore.preferences.preferencesDataStore +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.map +import javax.inject.Inject + +private val Context.dataStore: DataStore by preferencesDataStore(name = "app_init_prefs") + +class AppPreferencesDataStore @Inject constructor( + @ApplicationContext private val context: Context +) { + private object PreferencesKey { + // 온보딩 완료 여부 + val IS_ONBOARDING_COMPLETED = booleanPreferencesKey("is_onboarding_completed") + // 프로필 설정 완료 여부 + val IS_PROFILE_SETUP_COMPLETED = booleanPreferencesKey("is_profile_setup_completed") + } + + // 온보딩 완료 저장 + suspend fun setOnboardingCompleted(isCompleted: Boolean) { + context.dataStore.edit { preferences -> + preferences[PreferencesKey.IS_ONBOARDING_COMPLETED] = isCompleted + } + } + + // 프로필 설정 완료 저장 + suspend fun setProfileSetupCompleted(isCompleted: Boolean) { + context.dataStore.edit { preferences -> + preferences[PreferencesKey.IS_PROFILE_SETUP_COMPLETED] = isCompleted + } + } + + // 읽기 Flow + val initStatusFlow: Flow> = context.dataStore.data + .catch { exception -> + if (exception is IOException) { + emit(emptyPreferences()) + } else { + throw exception + } + } + .map { preferences -> + val onboarding = preferences[PreferencesKey.IS_ONBOARDING_COMPLETED] ?: false + val profile = preferences[PreferencesKey.IS_PROFILE_SETUP_COMPLETED] ?: false + // 온보딩 완료 상태, 프로필 설정 완료 상태 반환 + Pair(onboarding, profile) + } +} diff --git a/app/src/main/java/com/min/dnapp/data/di/FirebaseModule.kt b/app/src/main/java/com/min/dnapp/data/di/FirebaseModule.kt new file mode 100644 index 0000000..15a303f --- /dev/null +++ b/app/src/main/java/com/min/dnapp/data/di/FirebaseModule.kt @@ -0,0 +1,33 @@ +package com.min.dnapp.data.di + +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.firestore.FirebaseFirestore +import com.google.firebase.storage.FirebaseStorage +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 provideFirebaseAuth(): FirebaseAuth { + return FirebaseAuth.getInstance() + } + + @Provides + @Singleton + fun provideFirebaseFirestore(): FirebaseFirestore { + return FirebaseFirestore.getInstance() + } + + @Provides + @Singleton + fun provideFirebaseStorage(): FirebaseStorage { + return FirebaseStorage.getInstance() + } +} diff --git a/app/src/main/java/com/min/dnapp/data/di/NetworkModule.kt b/app/src/main/java/com/min/dnapp/data/di/NetworkModule.kt new file mode 100644 index 0000000..8186e63 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/data/di/NetworkModule.kt @@ -0,0 +1,90 @@ +package com.min.dnapp.data.di + +import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory +import com.min.dnapp.BuildConfig +import com.min.dnapp.data.remote.LocalSearchService +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import kotlinx.serialization.json.Json +import okhttp3.Interceptor +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import javax.inject.Qualifier +import javax.inject.Singleton + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class NaverApiKey + +@Module +@InstallIn(SingletonComponent::class) +object NetworkModule { + + private const val BASE_URL = "https://openapi.naver.com/" + + @Provides + @Singleton + fun provideJson(): Json { + return Json { + // API 응답에서 사용하지 않는 필드는 무시하도록 설정 + ignoreUnknownKeys = true + // 기본값이 있는 필드에 null이 오면 기본값 사용 + coerceInputValues = true + } + } + + @NaverApiKey + @Provides + fun provideNaverApiKey(): Pair { + val clientId = BuildConfig.NAVER_CLIENT_ID + val clientSecret = BuildConfig.NAVER_CLIENT_SECRET + return clientId to clientSecret + } + + @Provides + @Singleton + fun provideAuthInterceptor(@NaverApiKey naverApiKey: Pair): Interceptor { + val (clientId, clientSecret) = naverApiKey + return Interceptor { chain -> + val request = chain.request().newBuilder() + .header("X-Naver-Client-Id", clientId) + .header("X-Naver-Client-Secret", clientSecret) + .build() + chain.proceed(request) + } + } + + @Provides + @Singleton + fun provideOkHttpClient(authInterceptor: Interceptor): OkHttpClient { + val loggingInterceptor = HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.BODY + } + return OkHttpClient.Builder() + .addInterceptor(authInterceptor) + .addInterceptor(loggingInterceptor) + .build() + } + + @Provides + @Singleton + fun provideRetrofit(okHttpClient: OkHttpClient, json: Json): Retrofit { + val contentType = "application/json".toMediaType() + + return Retrofit.Builder() + .baseUrl(BASE_URL) + .client(okHttpClient) + .addConverterFactory(json.asConverterFactory(contentType)) + .build() + } + + @Provides + @Singleton + fun provideLocalSearchService(retrofit: Retrofit): LocalSearchService { + return retrofit.create(LocalSearchService::class.java) + } +} diff --git a/app/src/main/java/com/min/dnapp/data/di/RepositoryModule.kt b/app/src/main/java/com/min/dnapp/data/di/RepositoryModule.kt new file mode 100644 index 0000000..2bb6e47 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/data/di/RepositoryModule.kt @@ -0,0 +1,52 @@ +package com.min.dnapp.data.di + +import com.min.dnapp.data.repository.AppInitRepositoryImpl +import com.min.dnapp.data.repository.AuthRepositoryImpl +import com.min.dnapp.data.repository.LocalSearchRepositoryImpl +import com.min.dnapp.data.repository.RecordRepositoryImpl +import com.min.dnapp.data.repository.UserRepositoryImpl +import com.min.dnapp.domain.repository.AppInitRepository +import com.min.dnapp.domain.repository.AuthRepository +import com.min.dnapp.domain.repository.LocalSearchRepository +import com.min.dnapp.domain.repository.RecordRepository +import com.min.dnapp.domain.repository.UserRepository +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 RepositoryModule { + + @Binds + @Singleton + abstract fun bindAuthRepository( + authRepositoryImpl: AuthRepositoryImpl + ): AuthRepository + + @Binds + @Singleton + abstract fun bindLocalSearchRepository( + localSearchRepositoryImpl: LocalSearchRepositoryImpl + ): LocalSearchRepository + + @Binds + @Singleton + abstract fun bindRecordRepository( + recordRepositoryImpl: RecordRepositoryImpl + ): RecordRepository + + @Binds + @Singleton + abstract fun bindUserRepository( + userRepositoryImpl: UserRepositoryImpl + ): UserRepository + + @Binds + @Singleton + abstract fun bindAppInitRepository( + appInitRepositoryImpl: AppInitRepositoryImpl + ): AppInitRepository +} diff --git a/app/src/main/java/com/min/dnapp/data/mapper/PlaceMapper.kt b/app/src/main/java/com/min/dnapp/data/mapper/PlaceMapper.kt new file mode 100644 index 0000000..69d7209 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/data/mapper/PlaceMapper.kt @@ -0,0 +1,26 @@ +package com.min.dnapp.data.mapper + +import com.min.dnapp.data.remote.dto.PlaceEntity +import com.min.dnapp.domain.model.LocalPlace + +/** + * LocalPlace -> PlaceEntity 변환 + */ +fun LocalPlace.toEntity(): PlaceEntity { + return PlaceEntity( + title = this.title, + category = this.category, + roadAddress = this.roadAddress + ) +} + +/** + * PlaceEntity -> LocalPlace 변환 + */ +fun PlaceEntity.toDomain(): LocalPlace { + return LocalPlace( + title = this.title ?: "", + category = this.category ?: "", + roadAddress = this.roadAddress ?: "" + ) +} diff --git a/app/src/main/java/com/min/dnapp/data/mapper/RecordMapper.kt b/app/src/main/java/com/min/dnapp/data/mapper/RecordMapper.kt new file mode 100644 index 0000000..647a960 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/data/mapper/RecordMapper.kt @@ -0,0 +1,72 @@ +package com.min.dnapp.data.mapper + +import com.min.dnapp.data.remote.dto.RecordEntity +import com.min.dnapp.domain.model.TripRecord +import com.min.dnapp.domain.model.UserData +import com.min.dnapp.presentation.write.RecordWriteUiState + +object RecordMapper { + /** + * RecordWriteUiState -> TripRecord (Domain) 변환 + */ + fun fromUiState(uiState: RecordWriteUiState, imageUrl: String?): TripRecord { + return TripRecord( + title = uiState.recordTitle, + content = uiState.recordContent, + startDateMillis = uiState.selectedStartDateMillis ?: 0L, + endDateMillis = uiState.selectedEndDateMillis ?: 0L, + emotionKey = uiState.selectedEmotion?.key ?: "", + weatherKey = uiState.selectedWeather?.key ?: "", + selectedPlace = uiState.selectedPlace, + overseasPlace = uiState.overseasPlace, + isShareChecked = uiState.isShareChecked, + // 업로드 후 받은 URL + imageUrl = imageUrl ?: "", + createdAt = 0L, + userId = "", + userData = null, + ) + } + + /** + * TripRecord (Domain) -> RecordEntity 변환 + */ + fun fromDomain(tripRecord: TripRecord, userData: UserData): RecordEntity { + return RecordEntity( + title = tripRecord.title, + content = tripRecord.content, + startDateMillis = tripRecord.startDateMillis, + endDateMillis = tripRecord.endDateMillis, + emotionKey = tripRecord.emotionKey, + weatherKey = tripRecord.weatherKey, + selectedPlace = tripRecord.selectedPlace?.toEntity(), + overseasPlace = tripRecord.overseasPlace, + isShareChecked = tripRecord.isShareChecked, + // 업로드 후 받은 URL + imageUrl = tripRecord.imageUrl, + userData = UserDataMapper.fromUserData(userData) + ) + } + + /** + * RecordEntity -> TripRecord (Domain) 변환 + */ + fun fromEntity(entity: RecordEntity): TripRecord { + return TripRecord( +// recordId = entity.recordId ?: "", + userId = entity.userId ?: "", + title = entity.title ?: "", + content = entity.content ?: "", + startDateMillis = entity.startDateMillis ?: 0L, + endDateMillis = entity.endDateMillis ?: 0L, + emotionKey = entity.emotionKey ?: "", + weatherKey = entity.weatherKey ?: "", + selectedPlace = entity.selectedPlace?.toDomain(), + overseasPlace = entity.overseasPlace ?: "", + isShareChecked = entity.isShareChecked ?: false, + imageUrl = entity.imageUrl ?: "", + createdAt = entity.createdAt?.toDate()?.time ?: 0L, + userData = entity.userData?.let { UserDataMapper.fromUserDataEntity(it) } + ) + } +} diff --git a/app/src/main/java/com/min/dnapp/data/mapper/UserDataMapper.kt b/app/src/main/java/com/min/dnapp/data/mapper/UserDataMapper.kt new file mode 100644 index 0000000..536a1bd --- /dev/null +++ b/app/src/main/java/com/min/dnapp/data/mapper/UserDataMapper.kt @@ -0,0 +1,40 @@ +package com.min.dnapp.data.mapper + +import com.min.dnapp.data.remote.dto.UserDataEntity +import com.min.dnapp.domain.model.User +import com.min.dnapp.domain.model.UserData + +object UserDataMapper { + /** + * User -> UserData 변환 + */ + fun fromUser(user: User): UserData { + return UserData( + badgeLv = user.badgeLv, + nickname = user.nickname, + profileImageName = user.profileImageName + ) + } + + /** + * UserData -> UserDataEntity 변환 + */ + fun fromUserData(userData: UserData): UserDataEntity { + return UserDataEntity( + badgeLv = userData.badgeLv, + nickname = userData.nickname, + profileImageName = userData.profileImageName + ) + } + + /** + * UserDataEntity -> UserData 변환 + */ + fun fromUserDataEntity(userDataEntity: UserDataEntity): UserData { + return UserData( + badgeLv = userDataEntity.badgeLv ?: 1, + nickname = userDataEntity.nickname ?: "", + profileImageName = userDataEntity.profileImageName ?: "01_boat" + ) + } +} diff --git a/app/src/main/java/com/min/dnapp/data/mapper/UserMapper.kt b/app/src/main/java/com/min/dnapp/data/mapper/UserMapper.kt new file mode 100644 index 0000000..4375e9d --- /dev/null +++ b/app/src/main/java/com/min/dnapp/data/mapper/UserMapper.kt @@ -0,0 +1,40 @@ +package com.min.dnapp.data.mapper + +import com.min.dnapp.data.remote.dto.UserEntity +import com.min.dnapp.domain.model.User + +object UserMapper { + /** + * Entity -> Domain 변환 + */ + fun toDomain(entity: UserEntity): User { + return User( + userId = entity.userId ?: "", + nickname = entity.nickname ?: "", + profileImageName = entity.profileImageName ?: "01_boat", + badgeLv = entity.badgeLv ?: 1, + badgeName = entity.badgeName ?: "새내기", + recordCnt = entity.recordCnt ?: 0, + stampCnt = entity.stampCnt ?: 0, + // Timestamp -> Long 변환 + createdAt = entity.createdAt?.toDate()?.time ?: 0L + ) + } + + /** + * Domain -> Entity 변환 + */ + fun toEntity(domain: User): UserEntity { + return UserEntity( + userId = domain.userId, + nickname = domain.nickname, + profileImageName = domain.profileImageName, + badgeLv = domain.badgeLv, + badgeName = domain.badgeName, + recordCnt = domain.recordCnt, + stampCnt = domain.stampCnt, + // firestore 서버에서 시간 기록 + createdAt = null + ) + } +} diff --git a/app/src/main/java/com/min/dnapp/data/remote/LocalSearchResponse.kt b/app/src/main/java/com/min/dnapp/data/remote/LocalSearchResponse.kt new file mode 100644 index 0000000..d12d4c8 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/data/remote/LocalSearchResponse.kt @@ -0,0 +1,16 @@ +package com.min.dnapp.data.remote + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class LocalSearchResponse( + @SerialName("items") val items: List +) + +@Serializable +data class SearchItem( + @SerialName("title") val title: String, + @SerialName("category") val category: String, + @SerialName("roadAddress") val roadAddress: String +) diff --git a/app/src/main/java/com/min/dnapp/data/remote/LocalSearchService.kt b/app/src/main/java/com/min/dnapp/data/remote/LocalSearchService.kt new file mode 100644 index 0000000..ac01086 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/data/remote/LocalSearchService.kt @@ -0,0 +1,16 @@ +package com.min.dnapp.data.remote + +import retrofit2.http.GET +import retrofit2.http.Header +import retrofit2.http.Query + +interface LocalSearchService { + @GET("v1/search/local.json") + suspend fun search( +// @Header("X-Naver-Client-Id") clientId: String, +// @Header("X-Naver-Client-Secret") clientSecret: String, + @Query("query") query: String, + @Query("display") display: Int = 5, + @Query("sort") sort: String = "random" + ): LocalSearchResponse +} diff --git a/app/src/main/java/com/min/dnapp/data/remote/dto/RecordEntity.kt b/app/src/main/java/com/min/dnapp/data/remote/dto/RecordEntity.kt new file mode 100644 index 0000000..65c3a6b --- /dev/null +++ b/app/src/main/java/com/min/dnapp/data/remote/dto/RecordEntity.kt @@ -0,0 +1,46 @@ +package com.min.dnapp.data.remote.dto + +import com.google.firebase.Timestamp +import com.google.firebase.firestore.Exclude +import com.google.firebase.firestore.ServerTimestamp + +data class RecordEntity( + // firestore 문서 ID 값 + @get:Exclude + var recordId: String? = null, + + val userId: String? = null, + val userData: UserDataEntity? = null, + + val title: String? = null, + val content: String? = null, + + val startDateMillis: Long? = null, + val endDateMillis: Long? = null, + + val emotionKey: String? = null, + val weatherKey: String? = null, + + val selectedPlace: PlaceEntity? = null, + val overseasPlace: String? = null, + + val isShareChecked: Boolean? = null, + + val imageUrl: String? = null, + + // 서버 타임스탬프 + @ServerTimestamp + val createdAt: Timestamp? = null +) + +data class PlaceEntity( + val title: String? = null, + val category: String? = null, + val roadAddress: String? = null +) + +data class UserDataEntity( + val badgeLv: Int? = 1, + val nickname: String? = null, + val profileImageName: String? = "01_boat", +) diff --git a/app/src/main/java/com/min/dnapp/data/remote/dto/UserEntity.kt b/app/src/main/java/com/min/dnapp/data/remote/dto/UserEntity.kt new file mode 100644 index 0000000..55bc463 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/data/remote/dto/UserEntity.kt @@ -0,0 +1,14 @@ +package com.min.dnapp.data.remote.dto + +import com.google.firebase.Timestamp + +data class UserEntity( + val userId: String? = null, + val nickname: String? = null, + val profileImageName: String? = "01_boat", + val badgeLv: Int? = 1, + val badgeName: String? = "새내기", + val recordCnt: Int? = 0, + val stampCnt: Int? = 0, + val createdAt: Timestamp? = null +) diff --git a/app/src/main/java/com/min/dnapp/data/remote/dto/UserEntityExt.kt b/app/src/main/java/com/min/dnapp/data/remote/dto/UserEntityExt.kt new file mode 100644 index 0000000..02a4f76 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/data/remote/dto/UserEntityExt.kt @@ -0,0 +1,14 @@ +package com.min.dnapp.data.remote.dto + +fun UserEntity.toSaveMap(): MutableMap { + return mutableMapOf( + "userId" to this.userId, + "nickname" to this.nickname, + "profileImageName" to this.profileImageName, + "badgeLv" to this.badgeLv, + "badgeName" to this.badgeName, + "recordCnt" to this.recordCnt, + "stampCnt" to this.stampCnt, + "createdAt" to this.createdAt + ) +} diff --git a/app/src/main/java/com/min/dnapp/data/repository/AppInitRepositoryImpl.kt b/app/src/main/java/com/min/dnapp/data/repository/AppInitRepositoryImpl.kt new file mode 100644 index 0000000..c28cfd9 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/data/repository/AppInitRepositoryImpl.kt @@ -0,0 +1,23 @@ +package com.min.dnapp.data.repository + +import com.min.dnapp.data.datasource.AppPreferencesDataStore +import com.min.dnapp.domain.repository.AppInitRepository +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class AppInitRepositoryImpl @Inject constructor( + private val preferencesDataStore: AppPreferencesDataStore +) : AppInitRepository { + + override fun getInitStatus(): Flow> { + return preferencesDataStore.initStatusFlow + } + + override suspend fun setOnboardingCompleted() { + preferencesDataStore.setOnboardingCompleted(true) + } + + override suspend fun setProfileSetupCompleted() { + preferencesDataStore.setProfileSetupCompleted(true) + } +} diff --git a/app/src/main/java/com/min/dnapp/data/repository/AuthRepositoryImpl.kt b/app/src/main/java/com/min/dnapp/data/repository/AuthRepositoryImpl.kt new file mode 100644 index 0000000..f8dba15 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/data/repository/AuthRepositoryImpl.kt @@ -0,0 +1,263 @@ +package com.min.dnapp.data.repository + +import android.content.Context +import android.util.Log +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.firestore.FieldValue +import com.google.firebase.firestore.FirebaseFirestore +import com.kakao.sdk.auth.model.OAuthToken +import com.kakao.sdk.user.UserApiClient +import com.min.dnapp.data.mapper.UserMapper +import com.min.dnapp.data.remote.dto.toSaveMap +import com.min.dnapp.domain.model.User +import com.min.dnapp.domain.repository.AuthRepository +import kotlinx.coroutines.CancellableContinuation +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.tasks.await +import javax.inject.Inject + +class AuthRepositoryImpl @Inject constructor( + private val firebaseAuth: FirebaseAuth, + private val firestore: FirebaseFirestore +) : AuthRepository { + override suspend fun signInWithKakao(context: Context): Result = + suspendCancellableCoroutine { continuation -> + + // 카카오톡 앱 설치 여부에 따라 로그인 방식 결정 + if (UserApiClient.instance.isKakaoTalkLoginAvailable(context)) { + // 카카오톡 앱을 통한 로그인 + UserApiClient.instance.loginWithKakaoTalk(context) { token, error -> + // 코루틴이 이미 취소된 경우 처리하지 않음 + if (continuation.isCancelled) return@loginWithKakaoTalk + + handleKakaoLoginResult(token, error, continuation) + } + } else { + // 카카오 계정을 통한 웹 로그인 + UserApiClient.instance.loginWithKakaoAccount(context) { token, error -> + // 코루틴이 이미 취소된 경우 처리하지 않음 + if (continuation.isCancelled) return@loginWithKakaoAccount + + handleKakaoLoginResult(token, error, continuation) + } + } + } + + // 카카오 로그인 콜백 결과 처리 + private fun handleKakaoLoginResult( + token: OAuthToken?, + error: Throwable?, + continuation: CancellableContinuation> + ) { + if (error != null) { + // 카카오 로그인 실패 시 + continuation.resumeWith(Result.failure(error)) + } else if (token != null) { + // 카카오 로그인 성공 시, 사용자 정보 조회 + getUserInfoAndReadyAuth(continuation) + } else { + // 토큰이 null인 경우 + continuation.resumeWith(Result.failure(Exception("카카오 로그인 토큰을 받을 수 없습니다"))) + } + } + + // 카카오 사용자 정보 조회 및 인증 준비 + private fun getUserInfoAndReadyAuth( + continuation: CancellableContinuation> + ) { + UserApiClient.instance.me { user, error -> + // 코루틴이 취소된 경우 처리 중단 + if (continuation.isCancelled) return@me + + if (error != null) { + // 사용자 정보 조회 실패 + continuation.resumeWith(Result.failure(error)) + return@me + } + + if (user == null) { + continuation.resumeWith(Result.failure(Exception("카카오 사용자 정보를 가져올 수 없습니다"))) + return@me + } + + // 카카오 사용자 id를 firebase auth용 이메일/비밀번호로 변환 + val userId = user.id.toString() + val email = "$userId@kakao.com" + val password = userId + + // 카카오 닉네임 + val nickname = user.kakaoAccount?.profile?.nickname + + // firebase auth에 로그인/회원가입 + signInToFirebaseAuth(email, password, nickname, continuation) + } + } + + // firebase auth 기존 사용자 로그인 처리 + private fun signInToFirebaseAuth( + email: String, + password: String, + nickname: String?, + continuation: CancellableContinuation> + ) { + firebaseAuth.signInWithEmailAndPassword(email, password) + .addOnCompleteListener { task -> + if (continuation.isCancelled) return@addOnCompleteListener + + if (task.isSuccessful) { + // 기존 사용자 로그인 성공 + Log.d("auth", "signInToFirebaseAuth - 기존 사용자 로그인 성공") + continuation.resumeWith(Result.success(Result.success(Unit))) + } else { + // 기존 사용자가 아닌 경우, 새로 회원가입 진행 + Log.d("auth", "signInToFirebaseAuth - 새로운 사용자! 회원가입 진행") + createFirebaseUser(email, password, nickname, continuation) + } + } + } + + // firebase auth 회원가입 및 사용자 정보 저장 + private fun createFirebaseUser( + email: String, + password: String, + nickname: String?, + continuation: CancellableContinuation> + ) { + firebaseAuth.createUserWithEmailAndPassword(email, password) + .addOnCompleteListener { createTask -> + if (continuation.isCancelled) return@addOnCompleteListener + + if (createTask.isSuccessful) { + // firebase auth 회원가입 성공 + val firebaseUser = firebaseAuth.currentUser + + if (firebaseUser == null) { + continuation.resumeWith(Result.failure(Exception("firebase 사용자 정보를 찾을 수 없습니다"))) + return@addOnCompleteListener + } + + // 카카오 닉네임 사용하여 User Domain Model 생성 + val newUser = createUserDomainModel(firebaseUser.uid, nickname) + + // 콜백 내에서 suspend 함수를 호출하기 위해 코루틴 스코프 시작 + CoroutineScope(Dispatchers.IO).launch { + val saveResult = runCatching { + // firestore에 저장 + saveNewUser(newUser) + } + + if (saveResult.isSuccess) { + // firestore 저장 성공 + Log.d("auth", "createFirebaseUser - firestore 저장 성공. 닉네임 : $nickname") + continuation.resumeWith(Result.success(Result.success(Unit))) + } else { + // firestore 저장 실패 + continuation.resumeWith(Result.failure(saveResult.exceptionOrNull() ?: Exception("사용자 정보 저장 실패"))) + } + } + } else { + // 회원가입 실패 + val exception = createTask.exception ?: Exception("firebase 회원가입 실패") + continuation.resumeWith(Result.failure(exception)) + + } + } + } + + private fun createUserDomainModel( + firebaseUid: String, + nickname: String? + ): User { + return User( + userId = firebaseUid, + nickname = nickname ?: "카카오 사용자", + profileImageName = "01_boat", + badgeLv = 1, + badgeName = "새내기", + recordCnt = 0, + stampCnt = 0, + createdAt = 0L + ) + } + + /** + * firestore에 사용자 정보 저장 + */ + private suspend fun saveNewUser(user: User) { + val userEntity = UserMapper.toEntity(user) + val userId = userEntity.userId ?: throw IllegalArgumentException("saveNewUser - user ID null") + + // Entity를 Map으로 변환 + val dataMap = userEntity.toSaveMap().apply { + this["createdAt"] = FieldValue.serverTimestamp() + } + + // Map 사용하여 firestore에 저장 + firestore.collection("users").document(userId).set(dataMap).await() + } + + override suspend fun getUser(): User { + TODO("Not yet implemented") + } + + override suspend fun logout(): Result = suspendCancellableCoroutine { continuation -> + // 카카오 SDK 로그아웃 + UserApiClient.instance.logout { error -> + if (error != null) { + Log.e("auth", "logout - 카카오 로그아웃 실패", error) + continuation.resumeWith(Result.failure(error)) + return@logout + } + + // firebase auth 로그아웃 + firebaseAuth.signOut() + continuation.resumeWith(Result.success(Result.success(Unit))) + Log.d("auth", "logout - 카카오 & firebase 로그아웃 성공") + } + } + + override suspend fun unlinkUser(): Result = suspendCancellableCoroutine { continuation -> + val firebaseUser = firebaseAuth.currentUser + if (firebaseUser == null) { + continuation.resumeWith(Result.failure(Exception("로그인된 사용자가 없습니다"))) + return@suspendCancellableCoroutine + } + + // firebase auth 사용자 삭제 + firebaseUser.delete() + .addOnCompleteListener { authTask -> + Log.d("auth", "unlinkUser - firebase 계정 삭제 성공") + if (authTask.isSuccessful) { + // firestore 문서 삭제 + firestore.collection("users").document(firebaseUser.uid).delete() + .addOnSuccessListener { + Log.d("auth", "unlinkUser - firestore 문서 삭제 성공") + // 카카오 연결 끊기 + UserApiClient.instance.unlink { unlinkError -> + if (unlinkError != null) { + continuation.resumeWith(Result.failure(unlinkError)) + } else { + continuation.resumeWith(Result.success(Result.success(Unit))) + Log.d("auth", "unlinkUser - 카카오 연결 끊기 성공") + } + } + } + .addOnFailureListener { firestoreException -> + continuation.resumeWith(Result.failure(firestoreException)) + } + } else { + continuation.resumeWith(Result.failure(authTask.exception ?: Exception("firebase 계정 삭제 실패"))) + } + } + } + + /** + * 현재 로그인된 사용자의 UID를 반환 + */ + override suspend fun getCurrentUserId(): String? { + return firebaseAuth.currentUser?.uid + } +} diff --git a/app/src/main/java/com/min/dnapp/data/repository/LocalSearchRepositoryImpl.kt b/app/src/main/java/com/min/dnapp/data/repository/LocalSearchRepositoryImpl.kt new file mode 100644 index 0000000..6dc6d19 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/data/repository/LocalSearchRepositoryImpl.kt @@ -0,0 +1,49 @@ +package com.min.dnapp.data.repository + +import android.net.http.HttpEngine +import android.util.Log +import com.min.dnapp.data.remote.LocalSearchResponse +import com.min.dnapp.data.remote.LocalSearchService +import com.min.dnapp.domain.model.LocalPlace +import com.min.dnapp.domain.repository.LocalSearchRepository +import com.min.dnapp.util.Resource +import com.min.dnapp.util.extractLastCategory +import com.min.dnapp.util.removeTag +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import retrofit2.HttpException +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class LocalSearchRepositoryImpl @Inject constructor( + private val api: LocalSearchService +) : LocalSearchRepository { + override fun searchPlaces(query: String): Flow>> = flow { + // 로딩 상태 방출 + emit(Resource.Loading()) + + try { + val response: LocalSearchResponse = api.search( + query = query + ) + + val places = response.items.map { searchItem -> + LocalPlace( + title = searchItem.title.removeTag(), + category = searchItem.category.extractLastCategory(), + roadAddress = searchItem.roadAddress + ) + } + + // 성공 상태 방출 + emit(Resource.Success(places)) + } catch (e: HttpException) { + emit(Resource.Error("서버 오류 발생")) + } catch (e: java.io.IOException) { + emit(Resource.Error("네트워크 연결 문제")) + } catch (e: Exception) { + emit(Resource.Error("알 수 없는 오류 발생")) + } + } +} diff --git a/app/src/main/java/com/min/dnapp/data/repository/RecordRepositoryImpl.kt b/app/src/main/java/com/min/dnapp/data/repository/RecordRepositoryImpl.kt new file mode 100644 index 0000000..525e717 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/data/repository/RecordRepositoryImpl.kt @@ -0,0 +1,154 @@ +package com.min.dnapp.data.repository + +import android.net.Uri +import android.util.Log +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.firestore.FieldValue +import com.google.firebase.firestore.FirebaseFirestore +import com.google.firebase.firestore.Query +import com.google.firebase.firestore.toObjects +import com.google.firebase.storage.FirebaseStorage +import com.min.dnapp.data.mapper.RecordMapper +import com.min.dnapp.data.remote.dto.RecordEntity +import com.min.dnapp.domain.model.TripRecord +import com.min.dnapp.domain.repository.RecordRepository +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.tasks.await +import kotlinx.coroutines.withContext +import javax.inject.Inject + +class RecordRepositoryImpl @Inject constructor( + private val firestore: FirebaseFirestore, + private val storage: FirebaseStorage, + private val firebaseAuth: FirebaseAuth +) : RecordRepository { + + // 현재 로그인된 사용자 ID 가져오기 + private val currentUserId: String + get() = firebaseAuth.currentUser?.uid ?: throw IllegalStateException("user not authenticated") + + /** + * Storage에 이미지 업로드 및 URL 반환 + */ + override suspend fun uploadImageAndGetUrl(imageUri: Uri): String { + // 사용자 ID 가져오기 + val userId = currentUserId + + // firebase storage 경로는 사용자별로 분리 + val storageRef = storage.reference + .child("images") + .child(userId) + .child("${System.currentTimeMillis()}_${imageUri.lastPathSegment}") + + // 이미지 업로드 및 URL 가져오기 + storageRef.putFile(imageUri).await() + return storageRef.downloadUrl.await().toString() + } + + /** + * 개인 기록 컬렉션에 저장 + */ + override suspend fun savePrivateRecord(record: RecordEntity): RecordEntity { + // 사용자 ID 가져오기 + val userId = currentUserId + + val recordCollection = firestore + .collection("records") + .document(userId) + .collection("private_records") + + // firestore에서 새로운 문서 ID 생성 + val newDoc = recordCollection.document() + val recordId = newDoc.id + + // RecordEntity의 ID 관련 필드 채우기 + val recordWithId = record.copy( + recordId = recordId, + userId = userId + ) + + // set()을 사용하여 해당 ID로 저장 + newDoc.set(recordWithId).await() + + return recordWithId + } + + /** + * 전체공유 컬렉션에 저장 + */ + override suspend fun saveSharedRecord(record: RecordEntity) { + // 사용자 ID 가져오기 + val userId = currentUserId + + val sharedCollection = firestore.collection("shared_records") + + val recordWithId = record.copy(userId = userId) + + // set()을 사용하여 개인 기록과 동일한 ID로 저장 + recordWithId.recordId?.let { recordId -> + sharedCollection.document(recordId).set(recordWithId).await() + } + } + + override suspend fun getUserRecord(): List { + // 사용자 ID 가져오기 + val userId = currentUserId + + return withContext(Dispatchers.IO) { + try { + // querySnapshot 객체 가져오기 (사용자의 전체 문서) + val querySnapshot = firestore + .collection("records") + .document(userId) + .collection("private_records") + .orderBy("startDateMillis", Query.Direction.DESCENDING) + .get() + .await() + + val entityList = querySnapshot.toObjects() + val domainList = entityList.map { RecordMapper.fromEntity(it) } + + return@withContext domainList + } catch (e: Exception) { + Log.e("record", "getUserRecord error", e) + return@withContext emptyList() + } + } + } + + /** + * 기록/스탬프 수 1씩 증가 + */ + override suspend fun increaseRecordAndStamp() { + val userId = currentUserId + val userDoc = firestore.collection("users").document(userId) + + // Map으로 업데이트할 필드와 값을 지정 + val updateCnt = mapOf( + "recordCnt" to FieldValue.increment(1), + "stampCnt" to FieldValue.increment(1) + ) + + userDoc.update(updateCnt).await() + } + + override suspend fun getSharedRecord(): List { + return withContext(Dispatchers.IO) { + try { + val querySnapshot = firestore + .collection("shared_records") + .orderBy("createdAt", Query.Direction.DESCENDING) + .get() + .await() + + val entityList = querySnapshot.toObjects() + val domainList = entityList.map { RecordMapper.fromEntity(it) } + + return@withContext domainList + } catch (e: Exception) { + Log.e("record", "getSharedRecord error", e) + return@withContext emptyList() + } + } + } +} diff --git a/app/src/main/java/com/min/dnapp/data/repository/UserRepositoryImpl.kt b/app/src/main/java/com/min/dnapp/data/repository/UserRepositoryImpl.kt new file mode 100644 index 0000000..44813c2 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/data/repository/UserRepositoryImpl.kt @@ -0,0 +1,100 @@ +package com.min.dnapp.data.repository + +import android.util.Log +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.firestore.FirebaseFirestore +import com.min.dnapp.data.remote.dto.UserEntity +import com.min.dnapp.domain.model.Badge +import com.min.dnapp.domain.repository.UserRepository +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.tasks.await +import kotlinx.coroutines.withContext +import javax.inject.Inject + +class UserRepositoryImpl @Inject constructor( + private val firestore: FirebaseFirestore, + private val firebaseAuth: FirebaseAuth +) : UserRepository { + + // 사용자 ID 가져오기 + private val currentUserId: String + get() = firebaseAuth.currentUser?.uid ?: throw IllegalStateException("user not authenticated") + + override suspend fun getUserData(uid: String): UserEntity { + // I/O 작업을 위해 Dispatchers.IO로 스레드 전환 + return withContext(Dispatchers.IO) { + try { + val document = firestore + .collection("users") + .document(uid) + .get() + .await() + + val userEntity = document.toObject(UserEntity::class.java) + + return@withContext userEntity ?: throw Exception("user document problem") + } catch (e: Exception) { + throw Exception("failed getUserData: ${e.message}", e) + } + } + } + + override suspend fun updateProfileImage(profileImageName: String): Result { + val userID = currentUserId + val userDoc = firestore.collection("users").document(userID) + + val updateImage = mapOf( + "profileImageName" to profileImageName + ) + + return withContext(Dispatchers.IO) { + try { + userDoc.update(updateImage).await() + Result.success(Unit) + } catch (e: Exception) { + Log.e("record", "updateProfileImage error", e) + // 실패 정보 상위 레이어로 전달 + Result.failure(e) + } + } + } + + override suspend fun updateNickname(nickname: String): Result { + val userId = currentUserId + val userDoc = firestore.collection("users").document(userId) + + val newNickname = mapOf( + "nickname" to nickname + ) + + return withContext(Dispatchers.IO) { + try { + userDoc.update(newNickname).await() + Result.success(Unit) + } catch (e: Exception) { + Log.e("record", "updateNickname error", e) + Result.failure(e) + } + } + } + + override suspend fun updateBadge(badge: Badge): Result { + val userId = currentUserId + val userDoc = firestore.collection("users").document(userId) + + val newBadge = mapOf( + "badgeLv" to badge.level, + "badgeName" to badge.name + ) + + return withContext(Dispatchers.IO) { + try { + userDoc.update(newBadge).await() + Result.success(Unit) + } catch (e: Exception) { + Log.e("user", "updateBadge error", e) + Result.failure(e) + } + } + } +} diff --git a/app/src/main/java/com/min/dnapp/domain/BadgeUtil.kt b/app/src/main/java/com/min/dnapp/domain/BadgeUtil.kt new file mode 100644 index 0000000..e3f4a33 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/domain/BadgeUtil.kt @@ -0,0 +1,43 @@ +package com.min.dnapp.domain + +import com.min.dnapp.R +import com.min.dnapp.domain.model.Badge + +val AllBadges = listOf( + Badge( + level = 1, + name = "새내기", + description = "다음 뱃지까지 5개 남았어요", + minStamp = 0, + nextBadgeRemainStamp = 5, + resId = R.drawable.badge_new, + ), + Badge( + level = 2, + name = "여린이", + description = "다음 뱃지까지 15개 남았어요", + minStamp = 5, + nextBadgeRemainStamp = 15, + resId = R.drawable.badge_bronze + ), + Badge( + level = 3, + name = "여행자", + description = "다음 뱃지까지 10개 남았어요", + minStamp = 20, + nextBadgeRemainStamp = 10, + resId = R.drawable.badge_silver + ), + Badge( + level = 4, + name = "마스터", + description = "최고 뱃지에 도달하셨어요", + minStamp = 30, + nextBadgeRemainStamp = 0, + resId = R.drawable.badge_gold + ) +) + +fun getNewBadgeData(totalStamp: Int): Badge? { + return AllBadges.firstOrNull { it.minStamp == totalStamp } +} diff --git a/app/src/main/java/com/min/dnapp/domain/model/Badge.kt b/app/src/main/java/com/min/dnapp/domain/model/Badge.kt new file mode 100644 index 0000000..3389226 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/domain/model/Badge.kt @@ -0,0 +1,12 @@ +package com.min.dnapp.domain.model + +import androidx.annotation.DrawableRes + +data class Badge( + val level: Int, + val name: String, + val description: String, + val minStamp: Int, + val nextBadgeRemainStamp: Int, + @DrawableRes val resId: Int, +) diff --git a/app/src/main/java/com/min/dnapp/domain/model/EmotionType.kt b/app/src/main/java/com/min/dnapp/domain/model/EmotionType.kt new file mode 100644 index 0000000..bda48e1 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/domain/model/EmotionType.kt @@ -0,0 +1,17 @@ +package com.min.dnapp.domain.model + +import androidx.annotation.DrawableRes +import com.min.dnapp.R + +enum class EmotionType( + val key: String, + @DrawableRes val resId: Int +) { + HAPPY("happy", R.drawable.emotion_happy), + LOVE("love", R.drawable.emotion_love), + SURPRISE("surprise", R.drawable.emotion_surprise), + ANGRY("angry", R.drawable.emotion_angry), + FEEL("feel", R.drawable.emotion_feel), + SAD("sad", R.drawable.emotion_sad), + SHINE("shine", R.drawable.emotion_shine) +} diff --git a/app/src/main/java/com/min/dnapp/domain/model/LocalPlace.kt b/app/src/main/java/com/min/dnapp/domain/model/LocalPlace.kt new file mode 100644 index 0000000..b8f92b4 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/domain/model/LocalPlace.kt @@ -0,0 +1,7 @@ +package com.min.dnapp.domain.model + +data class LocalPlace( + val title: String, + val category: String, + val roadAddress: String +) diff --git a/app/src/main/java/com/min/dnapp/domain/model/TripRecord.kt b/app/src/main/java/com/min/dnapp/domain/model/TripRecord.kt new file mode 100644 index 0000000..92a713d --- /dev/null +++ b/app/src/main/java/com/min/dnapp/domain/model/TripRecord.kt @@ -0,0 +1,25 @@ +package com.min.dnapp.domain.model + +data class TripRecord( +// var recordId: String, + val userId: String, + val userData: UserData?, + val title: String, + val content: String, + val startDateMillis: Long, + val endDateMillis: Long, + val emotionKey: String, + val weatherKey: String, + // 복합 객체여서 nullable일 수 있음 + val selectedPlace: LocalPlace?, + val overseasPlace: String, + val isShareChecked: Boolean, + val imageUrl: String, + val createdAt: Long +) + +data class UserData( + val badgeLv: Int, + val nickname: String, + val profileImageName: String, +) diff --git a/app/src/main/java/com/min/dnapp/domain/model/User.kt b/app/src/main/java/com/min/dnapp/domain/model/User.kt new file mode 100644 index 0000000..b338020 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/domain/model/User.kt @@ -0,0 +1,12 @@ +package com.min.dnapp.domain.model + +data class User( + val userId: String, + val nickname: String, + val profileImageName: String, + val badgeLv: Int, + val badgeName: String, + val recordCnt: Int, + val stampCnt: Int, + val createdAt: Long +) diff --git a/app/src/main/java/com/min/dnapp/domain/model/WeatherType.kt b/app/src/main/java/com/min/dnapp/domain/model/WeatherType.kt new file mode 100644 index 0000000..74e6d81 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/domain/model/WeatherType.kt @@ -0,0 +1,17 @@ +package com.min.dnapp.domain.model + +import androidx.annotation.DrawableRes +import com.min.dnapp.R + +enum class WeatherType( + val key: String, + @DrawableRes val resId: Int +) { + SUN("sun", R.drawable.weather_sun), + WIND("wind", R.drawable.weather_wind), + MOON("moon", R.drawable.weather_moon), + THUNDER("thunder", R.drawable.weather_thunder), + RAIN("rain", R.drawable.weather_rain), + CLOUD("cloud", R.drawable.weather_cloud), + SNOW("snow", R.drawable.weather_snow) +} diff --git a/app/src/main/java/com/min/dnapp/domain/repository/AppInitRepository.kt b/app/src/main/java/com/min/dnapp/domain/repository/AppInitRepository.kt new file mode 100644 index 0000000..b4a7b87 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/domain/repository/AppInitRepository.kt @@ -0,0 +1,9 @@ +package com.min.dnapp.domain.repository + +import kotlinx.coroutines.flow.Flow + +interface AppInitRepository { + fun getInitStatus(): Flow> + suspend fun setOnboardingCompleted() + suspend fun setProfileSetupCompleted() +} diff --git a/app/src/main/java/com/min/dnapp/domain/repository/AuthRepository.kt b/app/src/main/java/com/min/dnapp/domain/repository/AuthRepository.kt new file mode 100644 index 0000000..99f5855 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/domain/repository/AuthRepository.kt @@ -0,0 +1,12 @@ +package com.min.dnapp.domain.repository + +import android.content.Context +import com.min.dnapp.domain.model.User + +interface AuthRepository { + suspend fun signInWithKakao(context: Context): Result + suspend fun logout(): Result + suspend fun unlinkUser(): Result + suspend fun getCurrentUserId(): String? + suspend fun getUser(): User +} diff --git a/app/src/main/java/com/min/dnapp/domain/repository/LocalSearchRepository.kt b/app/src/main/java/com/min/dnapp/domain/repository/LocalSearchRepository.kt new file mode 100644 index 0000000..a04d76a --- /dev/null +++ b/app/src/main/java/com/min/dnapp/domain/repository/LocalSearchRepository.kt @@ -0,0 +1,9 @@ +package com.min.dnapp.domain.repository + +import com.min.dnapp.domain.model.LocalPlace +import com.min.dnapp.util.Resource +import kotlinx.coroutines.flow.Flow + +interface LocalSearchRepository { + fun searchPlaces(query: String): Flow>> +} diff --git a/app/src/main/java/com/min/dnapp/domain/repository/RecordRepository.kt b/app/src/main/java/com/min/dnapp/domain/repository/RecordRepository.kt new file mode 100644 index 0000000..4ac3ca5 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/domain/repository/RecordRepository.kt @@ -0,0 +1,14 @@ +package com.min.dnapp.domain.repository + +import android.net.Uri +import com.min.dnapp.data.remote.dto.RecordEntity +import com.min.dnapp.domain.model.TripRecord + +interface RecordRepository { + suspend fun uploadImageAndGetUrl(imageUri: Uri): String + suspend fun savePrivateRecord(record: RecordEntity): RecordEntity + suspend fun saveSharedRecord(record: RecordEntity) + suspend fun getUserRecord(): List + suspend fun increaseRecordAndStamp() + suspend fun getSharedRecord(): List +} diff --git a/app/src/main/java/com/min/dnapp/domain/repository/UserRepository.kt b/app/src/main/java/com/min/dnapp/domain/repository/UserRepository.kt new file mode 100644 index 0000000..b06c3d9 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/domain/repository/UserRepository.kt @@ -0,0 +1,11 @@ +package com.min.dnapp.domain.repository + +import com.min.dnapp.data.remote.dto.UserEntity +import com.min.dnapp.domain.model.Badge + +interface UserRepository { + suspend fun getUserData(uid: String): UserEntity + suspend fun updateProfileImage(profileImageName: String): Result + suspend fun updateNickname(nickname: String): Result + suspend fun updateBadge(badge: Badge): Result +} diff --git a/app/src/main/java/com/min/dnapp/domain/usecase/AuthWithKakaoUseCase.kt b/app/src/main/java/com/min/dnapp/domain/usecase/AuthWithKakaoUseCase.kt new file mode 100644 index 0000000..ef5edef --- /dev/null +++ b/app/src/main/java/com/min/dnapp/domain/usecase/AuthWithKakaoUseCase.kt @@ -0,0 +1,13 @@ +package com.min.dnapp.domain.usecase + +import android.content.Context +import com.min.dnapp.domain.repository.AuthRepository +import javax.inject.Inject + +class AuthWithKakaoUseCase @Inject constructor( + private val authRepository: AuthRepository +) { + suspend operator fun invoke(context: Context): Result { + return authRepository.signInWithKakao(context) + } +} diff --git a/app/src/main/java/com/min/dnapp/domain/usecase/GetBadgeDialogDataUseCase.kt b/app/src/main/java/com/min/dnapp/domain/usecase/GetBadgeDialogDataUseCase.kt new file mode 100644 index 0000000..e9b1f63 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/domain/usecase/GetBadgeDialogDataUseCase.kt @@ -0,0 +1,11 @@ +package com.min.dnapp.domain.usecase + +import com.min.dnapp.domain.getNewBadgeData +import com.min.dnapp.domain.model.Badge +import javax.inject.Inject + +class GetBadgeDialogDataUseCase @Inject constructor() { + operator fun invoke(totalStamp: Int): Badge? { + return getNewBadgeData(totalStamp) + } +} diff --git a/app/src/main/java/com/min/dnapp/domain/usecase/GetCurrentUserIdUseCase.kt b/app/src/main/java/com/min/dnapp/domain/usecase/GetCurrentUserIdUseCase.kt new file mode 100644 index 0000000..dc89bad --- /dev/null +++ b/app/src/main/java/com/min/dnapp/domain/usecase/GetCurrentUserIdUseCase.kt @@ -0,0 +1,12 @@ +package com.min.dnapp.domain.usecase + +import com.min.dnapp.domain.repository.AuthRepository +import javax.inject.Inject + +class GetCurrentUserIdUseCase @Inject constructor( + private val authRepository: AuthRepository +) { + suspend operator fun invoke(): String? { + return authRepository.getCurrentUserId() + } +} diff --git a/app/src/main/java/com/min/dnapp/domain/usecase/GetInitStatusUseCase.kt b/app/src/main/java/com/min/dnapp/domain/usecase/GetInitStatusUseCase.kt new file mode 100644 index 0000000..a3e8360 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/domain/usecase/GetInitStatusUseCase.kt @@ -0,0 +1,13 @@ +package com.min.dnapp.domain.usecase + +import com.min.dnapp.domain.repository.AppInitRepository +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class GetInitStatusUseCase @Inject constructor( + private val appInitRepository: AppInitRepository +) { + operator fun invoke(): Flow> { + return appInitRepository.getInitStatus() + } +} diff --git a/app/src/main/java/com/min/dnapp/domain/usecase/GetSharedRecordUseCase.kt b/app/src/main/java/com/min/dnapp/domain/usecase/GetSharedRecordUseCase.kt new file mode 100644 index 0000000..b901321 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/domain/usecase/GetSharedRecordUseCase.kt @@ -0,0 +1,13 @@ +package com.min.dnapp.domain.usecase + +import com.min.dnapp.domain.model.TripRecord +import com.min.dnapp.domain.repository.RecordRepository +import javax.inject.Inject + +class GetSharedRecordUseCase @Inject constructor( + private val recordRepository: RecordRepository +) { + suspend operator fun invoke(): List { + return recordRepository.getSharedRecord() + } +} diff --git a/app/src/main/java/com/min/dnapp/domain/usecase/GetUserDataUseCase.kt b/app/src/main/java/com/min/dnapp/domain/usecase/GetUserDataUseCase.kt new file mode 100644 index 0000000..c593298 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/domain/usecase/GetUserDataUseCase.kt @@ -0,0 +1,17 @@ +package com.min.dnapp.domain.usecase + +import com.min.dnapp.data.mapper.UserMapper +import com.min.dnapp.domain.model.User +import com.min.dnapp.domain.repository.UserRepository +import javax.inject.Inject + +class GetUserDataUseCase @Inject constructor( + private val userRepository: UserRepository +) { + suspend operator fun invoke(uid: String): User { + val userEntity = userRepository.getUserData(uid) + + // UserEntity -> User 변환 + return UserMapper.toDomain(userEntity) + } +} diff --git a/app/src/main/java/com/min/dnapp/domain/usecase/GetUserRecordUseCase.kt b/app/src/main/java/com/min/dnapp/domain/usecase/GetUserRecordUseCase.kt new file mode 100644 index 0000000..2d98262 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/domain/usecase/GetUserRecordUseCase.kt @@ -0,0 +1,13 @@ +package com.min.dnapp.domain.usecase + +import com.min.dnapp.domain.model.TripRecord +import com.min.dnapp.domain.repository.RecordRepository +import javax.inject.Inject + +class GetUserRecordUseCase @Inject constructor( + private val recordRepository: RecordRepository +) { + suspend operator fun invoke(): List { + return recordRepository.getUserRecord() + } +} diff --git a/app/src/main/java/com/min/dnapp/domain/usecase/LocalSearchUseCase.kt b/app/src/main/java/com/min/dnapp/domain/usecase/LocalSearchUseCase.kt new file mode 100644 index 0000000..e33e229 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/domain/usecase/LocalSearchUseCase.kt @@ -0,0 +1,15 @@ +package com.min.dnapp.domain.usecase + +import com.min.dnapp.domain.model.LocalPlace +import com.min.dnapp.domain.repository.LocalSearchRepository +import com.min.dnapp.util.Resource +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class LocalSearchUseCase @Inject constructor( + private val repository: LocalSearchRepository +) { + operator fun invoke(query: String): Flow>> { + return repository.searchPlaces(query) + } +} diff --git a/app/src/main/java/com/min/dnapp/domain/usecase/LogoutUseCase.kt b/app/src/main/java/com/min/dnapp/domain/usecase/LogoutUseCase.kt new file mode 100644 index 0000000..fa8600b --- /dev/null +++ b/app/src/main/java/com/min/dnapp/domain/usecase/LogoutUseCase.kt @@ -0,0 +1,12 @@ +package com.min.dnapp.domain.usecase + +import com.min.dnapp.domain.repository.AuthRepository +import javax.inject.Inject + +class LogoutUseCase @Inject constructor( + private val authRepository: AuthRepository +) { + suspend operator fun invoke(): Result { + return authRepository.logout() + } +} diff --git a/app/src/main/java/com/min/dnapp/domain/usecase/SaveRecordUseCase.kt b/app/src/main/java/com/min/dnapp/domain/usecase/SaveRecordUseCase.kt new file mode 100644 index 0000000..c1d5951 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/domain/usecase/SaveRecordUseCase.kt @@ -0,0 +1,62 @@ +package com.min.dnapp.domain.usecase + +import android.net.Uri +import com.google.firebase.auth.FirebaseAuth +import com.min.dnapp.data.mapper.RecordMapper +import com.min.dnapp.data.mapper.UserDataMapper +import com.min.dnapp.domain.repository.RecordRepository +import com.min.dnapp.presentation.write.RecordWriteUiState +import javax.inject.Inject + +class SaveRecordUseCase @Inject constructor( + private val recordRepository: RecordRepository, + private val getUserDataUseCase: GetUserDataUseCase, + private val firebaseAuth: FirebaseAuth +) { + suspend operator fun invoke( + uiState: RecordWriteUiState, + imageUri: Uri? + ): Result { + return try { + // 이미지 업로드 (URL 획득) + val imageUrl = if (imageUri != null) { + recordRepository.uploadImageAndGetUrl(imageUri) + } else { + null + } + + // domain 모델 생성 (UiState -> TripRecord) + val tripRecord = RecordMapper.fromUiState(uiState, imageUrl) + + // 현재 사용자 ID + val userId = firebaseAuth.currentUser?.uid ?: return Result.failure(IllegalStateException("user not login")) + + // 사용자 데이터 가져오기 + val user = getUserDataUseCase(userId) + + // User -> UserData 변환 + val userData = UserDataMapper.fromUser(user) + + // TripRecord + UserData -> RecordEntity + val recordEntity = RecordMapper.fromDomain(tripRecord, userData) + .copy(userId = userId) + + // 개인 기록 컬렉션에 저장 (필수) + // recordId가 포함된 RecordEntity를 받아 새 변수에 저장 + val savedRecord = recordRepository.savePrivateRecord(recordEntity) + + // 공유 여부에 따라 전체공유 컬렉션에도 저장 (선택) + if (uiState.isShareChecked) { + // 동일한 recordId 포함된 savedRecord 사용 + recordRepository.saveSharedRecord(savedRecord) + } + + // 기록/스탬프 수 1씩 증가 + recordRepository.increaseRecordAndStamp() + + Result.success(Unit) + } catch (e: Exception) { + Result.failure(e) + } + } +} diff --git a/app/src/main/java/com/min/dnapp/domain/usecase/SetOnboardingCompletedUseCase.kt b/app/src/main/java/com/min/dnapp/domain/usecase/SetOnboardingCompletedUseCase.kt new file mode 100644 index 0000000..0193d5d --- /dev/null +++ b/app/src/main/java/com/min/dnapp/domain/usecase/SetOnboardingCompletedUseCase.kt @@ -0,0 +1,12 @@ +package com.min.dnapp.domain.usecase + +import com.min.dnapp.domain.repository.AppInitRepository +import javax.inject.Inject + +class SetOnboardingCompletedUseCase @Inject constructor( + private val appInitRepository: AppInitRepository +) { + suspend operator fun invoke() { + appInitRepository.setOnboardingCompleted() + } +} diff --git a/app/src/main/java/com/min/dnapp/domain/usecase/SetProfileSetupCompletedUseCase.kt b/app/src/main/java/com/min/dnapp/domain/usecase/SetProfileSetupCompletedUseCase.kt new file mode 100644 index 0000000..9864a1e --- /dev/null +++ b/app/src/main/java/com/min/dnapp/domain/usecase/SetProfileSetupCompletedUseCase.kt @@ -0,0 +1,12 @@ +package com.min.dnapp.domain.usecase + +import com.min.dnapp.domain.repository.AppInitRepository +import javax.inject.Inject + +class SetProfileSetupCompletedUseCase @Inject constructor( + private val appInitRepository: AppInitRepository +){ + suspend operator fun invoke() { + appInitRepository.setProfileSetupCompleted() + } +} diff --git a/app/src/main/java/com/min/dnapp/domain/usecase/UnlinkUseCase.kt b/app/src/main/java/com/min/dnapp/domain/usecase/UnlinkUseCase.kt new file mode 100644 index 0000000..e4e35fe --- /dev/null +++ b/app/src/main/java/com/min/dnapp/domain/usecase/UnlinkUseCase.kt @@ -0,0 +1,12 @@ +package com.min.dnapp.domain.usecase + +import com.min.dnapp.domain.repository.AuthRepository +import javax.inject.Inject + +class UnlinkUseCase @Inject constructor( + private val authRepository: AuthRepository +) { + suspend operator fun invoke(): Result { + return authRepository.unlinkUser() + } +} diff --git a/app/src/main/java/com/min/dnapp/domain/usecase/UpdateNicknameUseCase.kt b/app/src/main/java/com/min/dnapp/domain/usecase/UpdateNicknameUseCase.kt new file mode 100644 index 0000000..5d35e1c --- /dev/null +++ b/app/src/main/java/com/min/dnapp/domain/usecase/UpdateNicknameUseCase.kt @@ -0,0 +1,12 @@ +package com.min.dnapp.domain.usecase + +import com.min.dnapp.domain.repository.UserRepository +import javax.inject.Inject + +class UpdateNicknameUseCase @Inject constructor( + private val userRepository: UserRepository +) { + suspend operator fun invoke(nickname: String): Result { + return userRepository.updateNickname(nickname) + } +} diff --git a/app/src/main/java/com/min/dnapp/domain/usecase/UpdateProfileImageUseCase.kt b/app/src/main/java/com/min/dnapp/domain/usecase/UpdateProfileImageUseCase.kt new file mode 100644 index 0000000..87b8b72 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/domain/usecase/UpdateProfileImageUseCase.kt @@ -0,0 +1,12 @@ +package com.min.dnapp.domain.usecase + +import com.min.dnapp.domain.repository.UserRepository +import javax.inject.Inject + +class UpdateProfileImageUseCase @Inject constructor( + private val userRepository: UserRepository +) { + suspend operator fun invoke(profileImageName: String): Result { + return userRepository.updateProfileImage(profileImageName) + } +} diff --git a/app/src/main/java/com/min/dnapp/domain/usecase/UpdateUserBadgeUseCase.kt b/app/src/main/java/com/min/dnapp/domain/usecase/UpdateUserBadgeUseCase.kt new file mode 100644 index 0000000..e00417a --- /dev/null +++ b/app/src/main/java/com/min/dnapp/domain/usecase/UpdateUserBadgeUseCase.kt @@ -0,0 +1,13 @@ +package com.min.dnapp.domain.usecase + +import com.min.dnapp.domain.model.Badge +import com.min.dnapp.domain.repository.UserRepository +import javax.inject.Inject + +class UpdateUserBadgeUseCase @Inject constructor( + private val userRepository: UserRepository +) { + suspend operator fun invoke(badge: Badge): Result { + return userRepository.updateBadge(badge) + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/AppStartViewModel.kt b/app/src/main/java/com/min/dnapp/presentation/AppStartViewModel.kt new file mode 100644 index 0000000..f5bb575 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/AppStartViewModel.kt @@ -0,0 +1,19 @@ +package com.min.dnapp.presentation + +import androidx.lifecycle.ViewModel +import com.google.firebase.auth.FirebaseAuth +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import javax.inject.Inject + +@HiltViewModel +class AppStartViewModel @Inject constructor( + private val firebaseAuth: FirebaseAuth +) : ViewModel() { + + // 앱의 시작 상태(로그인 여부)를 저장 + private val _isLogin = MutableStateFlow(firebaseAuth.currentUser != null) + val isLogin: StateFlow = _isLogin.asStateFlow() +} diff --git a/app/src/main/java/com/min/dnapp/presentation/bell/BellScreen.kt b/app/src/main/java/com/min/dnapp/presentation/bell/BellScreen.kt new file mode 100644 index 0000000..4f0a48a --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/bell/BellScreen.kt @@ -0,0 +1,148 @@ +package com.min.dnapp.presentation.bell + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import com.min.dnapp.R +import com.min.dnapp.presentation.ui.icon.AppIcons +import com.min.dnapp.presentation.ui.icon.appicons.Back +import com.min.dnapp.presentation.ui.theme.DngoTheme +import com.min.dnapp.presentation.ui.theme.MomentoTheme + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun BellScreen(navController: NavHostController) { + Scaffold( + containerColor = MomentoTheme.colors.brownBg, + topBar = { + CenterAlignedTopAppBar( + title = { + Text( + text = "알림", + style = MomentoTheme.typography.title02, + color = MomentoTheme.colors.grayW20 + ) + }, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + MomentoTheme.colors.brownBg + ), + navigationIcon = { + Icon( + modifier = Modifier + .clickable { navController.popBackStack() } + .padding(16.dp), + imageVector = AppIcons.Back, + contentDescription = null + ) + } + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize() + ) { + HorizontalDivider(thickness = 1.dp, color = MomentoTheme.colors.grayW80) + +// BellCommentItem( +// image = painterResource(R.drawable.trip), +// nickname = "안녕하세요안녕하세요" +// ) +// +// for (i in 0 until 3) { +// BellCommentItem( +// image = painterResource(R.drawable.trip3), +// nickname = "박사과" +// ) +// } + } + } +} + +@Composable +fun BellCommentItem( + image: Painter, + nickname: String +) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp, vertical = 12.dp ), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Row { + Image( + painter = painterResource(R.drawable.bell_comment), + contentDescription = null + ) + + Spacer(Modifier.width(8.dp)) + + Column( + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Text( + text = "2분 전", + style = MomentoTheme.typography.label, + color = MomentoTheme.colors.grayW20 + ) + + Text( + text = "$nickname 님이", + style = MomentoTheme.typography.body02 , + color = MomentoTheme.colors.grayW20 + ) + Text( + text = "내 여행기록에 댓글을 달았어요.", + style = MomentoTheme.typography.body02 , + color = MomentoTheme.colors.grayW20 + ) + + Text( + text = "제주도 동쪽 투어!", + style = MomentoTheme.typography.caption, + color = MomentoTheme.colors.grayW40 + ) + } + } + + Image( + modifier = Modifier.size(56.dp), + painter = image, + contentDescription = null + ) + } + + HorizontalDivider(thickness = 1.dp, color = MomentoTheme.colors.grayW80) +} + +@Preview +@Composable +fun BellScreenPreview() { + DngoTheme { +// BellScreen() + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/common/BadgeMapper.kt b/app/src/main/java/com/min/dnapp/presentation/common/BadgeMapper.kt new file mode 100644 index 0000000..f8780de --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/common/BadgeMapper.kt @@ -0,0 +1,15 @@ +package com.min.dnapp.presentation.common + +import com.min.dnapp.R + +object BadgeMapper { + fun getBadgeImageResId(badgeLv: Int): Int { + return when (badgeLv) { + 1 -> R.drawable.badge_new + 2 -> R.drawable.badge_bronze + 3 -> R.drawable.badge_silver + 4 -> R.drawable.badge_gold + else -> R.drawable.badge_new + } + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/common/EmotionMapper.kt b/app/src/main/java/com/min/dnapp/presentation/common/EmotionMapper.kt new file mode 100644 index 0000000..06693fd --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/common/EmotionMapper.kt @@ -0,0 +1,18 @@ +package com.min.dnapp.presentation.common + +import com.min.dnapp.R + +object EmotionMapper { + fun getEmotionImageResId(emotionName: String): Int { + return when (emotionName) { + "happy" -> R.drawable.emotion_happy + "love" -> R.drawable.emotion_love + "surprise" -> R.drawable.emotion_surprise + "angry" -> R.drawable.emotion_angry + "feel" -> R.drawable.emotion_feel + "sad" -> R.drawable.emotion_sad + "shine" -> R.drawable.emotion_shine + else -> R.drawable.emotion_happy + } + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/common/ProfileMapper.kt b/app/src/main/java/com/min/dnapp/presentation/common/ProfileMapper.kt new file mode 100644 index 0000000..ee925c4 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/common/ProfileMapper.kt @@ -0,0 +1,19 @@ +package com.min.dnapp.presentation.common + +import com.min.dnapp.R + +object ProfileMapper { + fun getProfileImageResId(profileName: String): Int { + return when (profileName) { + "01_boat" -> R.drawable.logo_profile + "02_tent" -> R.drawable.logo_profile2 + "03_sea" -> R.drawable.logo_profile3 + "04_bag" -> R.drawable.logo_profile4 + "05_plane" -> R.drawable.logo_profile5 + "06_telescope" -> R.drawable.logo_profile6 + "07_map" -> R.drawable.logo_profile7 + "08_mount" -> R.drawable.logo_profile8 + else -> R.drawable.logo_profile + } + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/common/SnackbarMessage.kt b/app/src/main/java/com/min/dnapp/presentation/common/SnackbarMessage.kt new file mode 100644 index 0000000..0640995 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/common/SnackbarMessage.kt @@ -0,0 +1,5 @@ +package com.min.dnapp.presentation.common + +data class SnackbarMessage( + val message: String +) diff --git a/app/src/main/java/com/min/dnapp/presentation/common/WeatherMapper.kt b/app/src/main/java/com/min/dnapp/presentation/common/WeatherMapper.kt new file mode 100644 index 0000000..81c397c --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/common/WeatherMapper.kt @@ -0,0 +1,18 @@ +package com.min.dnapp.presentation.common + +import com.min.dnapp.R + +object WeatherMapper { + fun getWeatherImageResId(weatherName: String): Int { + return when (weatherName) { + "sun" -> R.drawable.weather_sun + "wind" -> R.drawable.weather_wind + "moon" -> R.drawable.weather_moon + "thunder" -> R.drawable.weather_thunder + "rain" -> R.drawable.weather_rain + "cloud" -> R.drawable.weather_cloud + "snow" -> R.drawable.weather_snow + else -> R.drawable.weather_sun + } + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/find/FindDetailScreen.kt b/app/src/main/java/com/min/dnapp/presentation/find/FindDetailScreen.kt new file mode 100644 index 0000000..c27d226 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/find/FindDetailScreen.kt @@ -0,0 +1,324 @@ +package com.min.dnapp.presentation.find + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +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.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import com.min.dnapp.R +import com.min.dnapp.presentation.find.component.SharedRecordContentSection +import com.min.dnapp.presentation.find.component.SharedRecordTimeSection +import com.min.dnapp.presentation.ui.icon.AppIcons +import com.min.dnapp.presentation.ui.icon.appicons.Back +import com.min.dnapp.presentation.ui.icon.appicons.More +import com.min.dnapp.presentation.ui.icon.appicons.RecordBest +import com.min.dnapp.presentation.ui.icon.appicons.RecordBookmark +import com.min.dnapp.presentation.ui.icon.appicons.RecordComment +import com.min.dnapp.presentation.ui.icon.appicons.RecordLike +import com.min.dnapp.presentation.ui.icon.appicons.RecordSurprise +import com.min.dnapp.presentation.ui.profile.ProfileImageCircle +import com.min.dnapp.presentation.ui.theme.DngoTheme +import com.min.dnapp.presentation.ui.theme.MomentoTheme + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun FindDetailScreen(navController: NavHostController) { + Scaffold( + containerColor = MomentoTheme.colors.brownBg, + topBar = { + CenterAlignedTopAppBar( + title = { + Text( + text = "상세 보기", + style = MomentoTheme.typography.title02, + color = MomentoTheme.colors.grayW20 + ) + }, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + containerColor = MomentoTheme.colors.brownBg + ), + navigationIcon = { + Icon( + modifier = Modifier + .clickable { navController.popBackStack() } + .padding(16.dp), + imageVector = AppIcons.Back, + contentDescription = null, + tint = MomentoTheme.colors.grayW20 + ) + } + ) + } + ) { paddingValues -> + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + ) { + // 기록 내용 영역 + item { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(20.dp) + ) { + // 프로필 이미지 +// ProfileImageCircle(modifier = Modifier.size(36.dp)) + + Spacer(Modifier.width(8.dp)) + + Column( + modifier = Modifier.weight(1f) + ) { + // 공유 경과시간 + 더보기 아이콘 영역 + SharedRecordMoreSection() + + Spacer(Modifier.height(8.dp)) + + Text( + text = "시골청년님이 여행 기록을 공유했어요.", + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.grayW20 + ) + + Spacer(Modifier.height(8.dp)) + + // 기록 내용 +// SharedRecordContentSection() + } + } + } + + // 반응 이모지 영역 + item { + HorizontalDivider(thickness = 1.dp, color = MomentoTheme.colors.grayW60) + FindDetailReactionSection( + bestNum = 1, + likeNum = 2, + surpriseNum = 12, + commentNum = 2 + ) + HorizontalDivider(thickness = 1.dp, color = MomentoTheme.colors.grayW60) + } + + // 댓글 목록 영역 + items(count = 5) { + CommentItem() + } + } + } +} + +@Composable +fun SharedRecordMoreSection() { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { +// SharedRecordTimeSection() + + Icon( + modifier = Modifier + .clickable { } + .padding(start = 16.dp), + imageVector = AppIcons.More, + contentDescription = null, + tint = MomentoTheme.colors.grayW20 + ) + } +} + +@Composable +fun FindDetailReactionSection( + bestNum: Int, + likeNum: Int, + surpriseNum: Int, + commentNum: Int +) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = AppIcons.RecordComment, + contentDescription = null, + tint = MomentoTheme.colors.grayW60 + ) + Spacer(Modifier.width(2.dp)) + Text( + text = commentNum.toString(), + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.grayW20 + ) + } + + Spacer(Modifier.width(20.dp)) + + + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = AppIcons.RecordBest, + contentDescription = null, + tint = MomentoTheme.colors.grayW60 + ) + Spacer(Modifier.width(2.dp)) + Text( + text = bestNum.toString(), + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.grayW20 + ) + } + + Spacer(Modifier.width(4.dp)) + + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = AppIcons.RecordLike, + contentDescription = null, + tint = MomentoTheme.colors.grayW60 + ) + Spacer(Modifier.width(2.dp)) + Text( + text = likeNum.toString(), + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.grayW20 + ) + } + + Spacer(Modifier.width(4.dp)) + + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = AppIcons.RecordSurprise, + contentDescription = null, + tint = MomentoTheme.colors.grayW60 + ) + Spacer(Modifier.width(2.dp)) + Text( + text = surpriseNum.toString(), + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.grayW20 + ) + } + } + + Icon( + modifier = Modifier + .clickable { } + .padding(16.dp), + imageVector = AppIcons.RecordBookmark, + contentDescription = null, + tint = MomentoTheme.colors.grayW60 + ) + } +} + +@Composable +fun CommentItem() { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp, vertical = 12.dp) + ) { + // 프로필 이미지 +// ProfileImageCircle(modifier = Modifier.size(24.dp)) + + Spacer(Modifier.width(8.dp)) + + Column { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "성민", + style = MomentoTheme.typography.body03, + color = MomentoTheme.colors.grayW20 + ) + Spacer(Modifier.width(4.dp)) + Image( + modifier = Modifier.size(20.dp), + painter = painterResource(R.drawable.badge_bronze), + contentDescription = null + ) + } + + Spacer(Modifier.height(8.dp)) + + Text( + text = "너무 예쁜데요??", + style = MomentoTheme.typography.body02, + color = MomentoTheme.colors.grayW20 + ) + + Spacer(Modifier.height(6.dp)) + + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "1시간 전", + style = MomentoTheme.typography.label, + color = MomentoTheme.colors.grayW60 + ) + Spacer(Modifier.width(4.dp)) + Text( + text = "・", + style = MomentoTheme.typography.label, + color = MomentoTheme.colors.grayW60 + ) + Spacer(Modifier.width(4.dp)) + Text( + text = "신고", + style = MomentoTheme.typography.label, + color = MomentoTheme.colors.grayW60 + ) + } + } + } +} + +@Preview +@Composable +fun FindDetailScreenPreview() { + DngoTheme { +// FindDetailScreen() + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/find/FindScreen.kt b/app/src/main/java/com/min/dnapp/presentation/find/FindScreen.kt new file mode 100644 index 0000000..f47f9dc --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/find/FindScreen.kt @@ -0,0 +1,134 @@ +package com.min.dnapp.presentation.find + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.navigation.NavHostController +import com.min.dnapp.R +import com.min.dnapp.presentation.find.component.SharedRecordItem +import com.min.dnapp.presentation.ui.icon.AppIcons +import com.min.dnapp.presentation.ui.icon.appicons.Bell +import com.min.dnapp.presentation.ui.theme.DngoTheme +import com.min.dnapp.presentation.ui.theme.MomentoTheme + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun FindScreen( + navController: NavHostController, + findViewModel: FindViewModel = hiltViewModel() +) { + val uiState by findViewModel.uiState.collectAsStateWithLifecycle() + + Scaffold( + contentWindowInsets = WindowInsets(0, 0, 0, 0), + containerColor = MomentoTheme.colors.brownBg, + topBar = { + CenterAlignedTopAppBar( + title = { Text("") }, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + containerColor = MomentoTheme.colors.brownBg + ), + navigationIcon = { + Image( + modifier = Modifier +// .clickable { } +// .padding(16.dp), + .padding(start = 16.dp), + painter = painterResource(R.drawable.logo_momento), + contentDescription = null + ) + }, + actions = { + Icon( + modifier = Modifier + .clickable { navController.navigate("bell") } + .padding(16.dp), + imageVector = AppIcons.Bell, + contentDescription = null, + tint = MomentoTheme.colors.grayW60 + ) + } + ) + } + ) { paddingValues -> + + when (uiState) { + is FindUiState.Loading -> { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator( + modifier = Modifier.size(40.dp), + color = MomentoTheme.colors.brownW20, + strokeWidth = 4.dp + ) + } + } + is FindUiState.Error -> {} + is FindUiState.Success -> { + // Success 데이터 추출 + val data = uiState as FindUiState.Success + + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + ) { + item { + HorizontalDivider(thickness = 1.dp, color = MomentoTheme.colors.grayW80) + } + + itemsIndexed(data.records) { idx, record -> + Box( + modifier = Modifier.padding(20.dp) + ) { + // 발견 탭에 공유된 여행기록 아이템 + SharedRecordItem( + record = record, + onClick = { +// navController.navigate("explore_detail") + } + ) + } + + if (idx < data.records.size - 1) { + HorizontalDivider(thickness = 1.dp, color = MomentoTheme.colors.grayW80) + } + } + } + } + } + } +} + +@Preview +@Composable +fun FindScreenPreview() { + DngoTheme { +// FindScreen() + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/find/FindUiState.kt b/app/src/main/java/com/min/dnapp/presentation/find/FindUiState.kt new file mode 100644 index 0000000..adc5987 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/find/FindUiState.kt @@ -0,0 +1,11 @@ +package com.min.dnapp.presentation.find + +import com.min.dnapp.domain.model.TripRecord + +sealed class FindUiState { + data object Loading: FindUiState() + data class Success( + val records: List = emptyList() + ) : FindUiState() + data class Error(val message: String): FindUiState() +} diff --git a/app/src/main/java/com/min/dnapp/presentation/find/FindViewModel.kt b/app/src/main/java/com/min/dnapp/presentation/find/FindViewModel.kt new file mode 100644 index 0000000..1ff7b39 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/find/FindViewModel.kt @@ -0,0 +1,47 @@ +package com.min.dnapp.presentation.find + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.min.dnapp.domain.usecase.GetSharedRecordUseCase +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class FindViewModel @Inject constructor( + private val getSharedRecordUseCase: GetSharedRecordUseCase +) : ViewModel() { + + private val _uiState = MutableStateFlow(FindUiState.Loading) + val uiState: StateFlow = _uiState.asStateFlow() + + init { + loaFindData() + } + + private fun loaFindData() { + viewModelScope.launch { + // 로딩 시작 + _uiState.value = FindUiState.Loading + + // 공유된 기록 목록 가져오기 + try { + val sharedRecords = getSharedRecordUseCase() + Log.d("record", "loaFindData - sharedRecords: $sharedRecords") + val successState = FindUiState.Success( + records = sharedRecords + ) + _uiState.value = successState + } catch (e: Exception) { + _uiState.value = FindUiState.Success( + records = emptyList() + ) + Log.e("record", "기록 목록 조회 실패", e) + } + } + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/find/component/FindReactionIconSection.kt b/app/src/main/java/com/min/dnapp/presentation/find/component/FindReactionIconSection.kt new file mode 100644 index 0000000..e578575 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/find/component/FindReactionIconSection.kt @@ -0,0 +1,121 @@ +package com.min.dnapp.presentation.find.component + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.min.dnapp.presentation.ui.icon.AppIcons +import com.min.dnapp.presentation.ui.icon.appicons.RecordBest +import com.min.dnapp.presentation.ui.icon.appicons.RecordBookmark +import com.min.dnapp.presentation.ui.icon.appicons.RecordComment +import com.min.dnapp.presentation.ui.icon.appicons.RecordLike +import com.min.dnapp.presentation.ui.icon.appicons.RecordSurprise +import com.min.dnapp.presentation.ui.theme.MomentoTheme + +@Composable +fun FindReactionIconSection( + bestNum: Int, + likeNum: Int, + surpriseNum: Int, + commentNum: Int +) { + Row( + modifier = Modifier + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = AppIcons.RecordBest, + contentDescription = null, + tint = MomentoTheme.colors.grayW60 + ) + Spacer(Modifier.width(2.dp)) + Text( + text = bestNum.toString(), + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.grayW20 + ) + } + + Spacer(Modifier.width(4.dp)) + + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = AppIcons.RecordLike, + contentDescription = null, + tint = MomentoTheme.colors.grayW60 + ) + Spacer(Modifier.width(2.dp)) + Text( + text = likeNum.toString(), + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.grayW20 + ) + } + + Spacer(Modifier.width(4.dp)) + + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = AppIcons.RecordSurprise, + contentDescription = null, + tint = MomentoTheme.colors.grayW60 + ) + Spacer(Modifier.width(2.dp)) + Text( + text = surpriseNum.toString(), + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.grayW20 + ) + } + } + + Row( + modifier = Modifier.clickable { }, + verticalAlignment = Alignment.CenterVertically + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = AppIcons.RecordComment, + contentDescription = null, + tint = MomentoTheme.colors.grayW60 + ) + Spacer(Modifier.width(2.dp)) + Text( + text = commentNum.toString(), + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.grayW20 + ) + } + + Spacer(Modifier.width(12.dp)) + + Icon( + imageVector = AppIcons.RecordBookmark, + contentDescription = null, + tint = MomentoTheme.colors.grayW60 + ) + } + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/find/component/SharedRecordContentSection.kt b/app/src/main/java/com/min/dnapp/presentation/find/component/SharedRecordContentSection.kt new file mode 100644 index 0000000..d16671b --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/find/component/SharedRecordContentSection.kt @@ -0,0 +1,149 @@ +package com.min.dnapp.presentation.find.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import coil3.compose.AsyncImage +import com.min.dnapp.R +import com.min.dnapp.presentation.common.EmotionMapper +import com.min.dnapp.presentation.common.WeatherMapper +import com.min.dnapp.presentation.ui.theme.MomentoTheme +import com.min.dnapp.util.toDateString + +@Composable +fun SharedRecordContentSection( + title: String, + content: String, + startDateMillis: Long, + endDateMillis: Long?, + weatherName: String, + emotionName: String, + placeName: String?, + imageUrl: String? +) { + val startDate = startDateMillis.toDateString("yy.MM.dd") + val endDate = endDateMillis?.toDateString("yy.MM.dd") + + // 여행 날짜 텍스트 계산 + val dateText = endDate?.let { "$startDate ~ $it" } ?: startDate + + // 날씨 & 감정 이미지 리소스로 변환 + val weatherImageResId = WeatherMapper.getWeatherImageResId(weatherName) + val emotionImageResId = EmotionMapper.getEmotionImageResId(emotionName) + + Column( + modifier = Modifier + .fillMaxWidth() + .background(color = MomentoTheme.colors.brownW90) + .padding(horizontal = 16.dp) + ) { + Spacer(Modifier.height(16.dp)) + + // 여행 제목 + Text( + text = title, +// style = MomentoTheme.typography.label, + style = MomentoTheme.typography.body02, + color = MomentoTheme.colors.grayW20 + ) + + Spacer(Modifier.height(6.dp)) + + HorizontalDivider(thickness = 1.dp, color = MomentoTheme.colors.pinkBase) + + Spacer(Modifier.height(4.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + // 여행 날짜 + Text( + text = dateText, + style = MomentoTheme.typography.body03, + color = MomentoTheme.colors.grayW20 + ) + + // 날씨 & 감정 + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Image( + modifier = Modifier.size(28.dp), + painter = painterResource(weatherImageResId), + contentDescription = null + ) + Spacer(Modifier.width(4.dp)) + Image( + modifier = Modifier.size(28.dp), + painter = painterResource(emotionImageResId), + contentDescription = null + ) + } + } + + Spacer(Modifier.height(4.dp)) + + // 여행 장소 + placeName?.let { place -> + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Image( + painter = painterResource(R.drawable.write_place), + contentDescription = null + ) + Spacer(Modifier.width(6.dp)) + Text( + text = place, + style = MomentoTheme.typography.body03 , + color = MomentoTheme.colors.grayW20 + ) + } + } + + Spacer(Modifier.height(6.dp)) + + // 여행 내용 + Text( + text = content, + style = MomentoTheme.typography.body02, + color = MomentoTheme.colors.grayW20 + ) + + Spacer(Modifier.height(6.dp)) + + // 여행 이미지 + imageUrl?.let { image -> + AsyncImage( + modifier = Modifier + .fillMaxWidth() + // 가로:세로 비율 1:1 + .aspectRatio(1f), + model = image, + contentDescription = null, + contentScale = ContentScale.Crop + ) + } + + Spacer(Modifier.height(12.dp)) + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/find/component/SharedRecordItem.kt b/app/src/main/java/com/min/dnapp/presentation/find/component/SharedRecordItem.kt new file mode 100644 index 0000000..8d213b1 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/find/component/SharedRecordItem.kt @@ -0,0 +1,90 @@ +package com.min.dnapp.presentation.find.component + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.min.dnapp.domain.model.TripRecord +import com.min.dnapp.presentation.ui.profile.ProfileImageCircle +import com.min.dnapp.presentation.ui.theme.DngoTheme +import com.min.dnapp.presentation.ui.theme.MomentoTheme + +@Composable +fun SharedRecordItem( + record: TripRecord, + onClick: () -> Unit +) { + Row( + modifier = Modifier +// .clickable { onClick() } + .fillMaxWidth() + ) { + // 프로필 이미지 + ProfileImageCircle( + profileImageName = record.userData?.profileImageName, + modifier = Modifier.size(36.dp) + ) + + Spacer(Modifier.width(8.dp)) + + Column( + modifier = Modifier.weight(1f) + ) { + // 공유 경과시간 영역 + SharedRecordTimeSection( + nickname = record.userData?.nickname ?: "", + badgeLv = record.userData?.badgeLv ?: 1, + createdAtMillis = record.createdAt + ) + + Spacer(Modifier.height(8.dp)) + + Text( + text = "${record.userData?.nickname}님이 여행 기록을 공유했어요.", + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.grayW20 + ) + + Spacer(Modifier.height(8.dp)) + + // 기록 전체내용 + SharedRecordContentSection( + title = record.title, + content = record.content, + startDateMillis = record.startDateMillis, + endDateMillis = if (record.endDateMillis == 0L) null else record.endDateMillis, + weatherName = record.weatherKey, + emotionName = record.emotionKey, + placeName = record.selectedPlace?.title, + imageUrl = if (record.imageUrl.isEmpty()) null else record.imageUrl + ) + +// Spacer(Modifier.height(12.dp)) +// +// // 반응 이모지 영역 +// FindReactionIconSection( +// bestNum = 1, +// likeNum = 2, +// surpriseNum = 12, +// commentNum = 2 +// ) + } + } +} + +@Preview +@Composable +fun RecordItemPreview() { + DngoTheme { +// SharedRecordItem() + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/find/component/SharedRecordTimeSection.kt b/app/src/main/java/com/min/dnapp/presentation/find/component/SharedRecordTimeSection.kt new file mode 100644 index 0000000..101d777 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/find/component/SharedRecordTimeSection.kt @@ -0,0 +1,51 @@ +package com.min.dnapp.presentation.find.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +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.unit.dp +import com.min.dnapp.R +import com.min.dnapp.presentation.common.BadgeMapper +import com.min.dnapp.presentation.ui.theme.MomentoTheme +import com.min.dnapp.util.toTimeAgoString + +@Composable +fun SharedRecordTimeSection( + nickname: String, + badgeLv: Int, + createdAtMillis: Long +) { + val imageResId = BadgeMapper.getBadgeImageResId(badgeLv) + + // 경과 시간 텍스트 (현재 시간 기준으로 공유된 시간 계산) + val timeAgoText = createdAtMillis.toTimeAgoString() + + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = nickname, + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.grayW20 + ) + Image( + modifier = Modifier.size(20.dp), +// painter = painterResource(R.drawable.badge_bronze), + painter = painterResource(imageResId), + contentDescription = null + ) + Spacer(Modifier.width(4.dp)) + Text( + text = timeAgoText, + style = MomentoTheme.typography.body02, + color = MomentoTheme.colors.grayW20 + ) + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/home/HomeScreen.kt b/app/src/main/java/com/min/dnapp/presentation/home/HomeScreen.kt new file mode 100644 index 0000000..4d7282f --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/home/HomeScreen.kt @@ -0,0 +1,85 @@ +package com.min.dnapp.presentation.home + +import android.widget.Toast +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.NavController +import com.min.dnapp.presentation.login.LoginViewModel + +@Composable +fun HomeScreen( + loginViewModel: LoginViewModel = hiltViewModel(), + navController: NavController +) { + val context = LocalContext.current + val snackbarHostState = remember { SnackbarHostState() } + + Scaffold ( + snackbarHost = { SnackbarHost(snackbarHostState) } + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text("홈 화면") + + Spacer(modifier = Modifier.height(32.dp)) + + Button( + onClick = { + loginViewModel.onLogoutClicked( + onSuccess = { + navController.navigate("login") { + popUpTo("login") { inclusive = true } + } + }, + onFailure = { + Toast.makeText(context, "로그아웃 실패 : ${it.message}", Toast.LENGTH_SHORT).show() + } + ) + } + ) { + Text("로그아웃") + } + + Spacer(modifier = Modifier.height(16.dp)) + + Button( + onClick = { +// loginViewModel.onUnlinkClicked( +// onSuccess = { +// Toast.makeText(context, "회원탈퇴 성공", Toast.LENGTH_SHORT).show() +// navController.navigate("login") { +// popUpTo("login") { inclusive = true } +// } +// }, +// onFailure = { +// Toast.makeText(context, "회원탈퇴 실패 : ${it.message}", Toast.LENGTH_SHORT).show() +// } +// ) + } + ) { + Text("회원탈퇴") + } + } + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/home/HomeScreen2.kt b/app/src/main/java/com/min/dnapp/presentation/home/HomeScreen2.kt new file mode 100644 index 0000000..e90f30b --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/home/HomeScreen2.kt @@ -0,0 +1,524 @@ +package com.min.dnapp.presentation.home + +import android.util.Log +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +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.Row +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.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.VerticalDivider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.navigation.NavHostController +import androidx.navigation.compose.rememberNavController +import coil3.compose.AsyncImage +import com.min.dnapp.R +import com.min.dnapp.domain.model.TripRecord +import com.min.dnapp.presentation.ui.component.CustomFloatingActionButton +import com.min.dnapp.presentation.ui.component.UserBadge +import com.min.dnapp.presentation.ui.icon.AppIcons +import com.min.dnapp.presentation.ui.icon.appicons.ArrowRight +import com.min.dnapp.presentation.ui.icon.appicons.Bell +import com.min.dnapp.presentation.ui.icon.appicons.Year +import com.min.dnapp.presentation.ui.theme.DngoTheme +import com.min.dnapp.presentation.ui.theme.MomentoTheme +import com.min.dnapp.util.toDateString + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun HomeScreen2( + navController: NavHostController, + homeViewModel: HomeViewModel = hiltViewModel() +) { + val uiState by homeViewModel.uiState.collectAsStateWithLifecycle() + + Scaffold( + contentWindowInsets = WindowInsets(0, 0, 0, 0), + containerColor = MomentoTheme.colors.brownW90, + topBar = { + CenterAlignedTopAppBar( + title = { Text("") }, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + MomentoTheme.colors.brownW90 + ), + navigationIcon = { + Image( + modifier = Modifier +// .clickable { } +// .padding(16.dp), + .padding(start = 16.dp), + painter = painterResource(R.drawable.logo_momento), + contentDescription = null + ) + }, + actions = { + Icon( + modifier = Modifier + .clickable { navController.navigate("bell") } + .padding(16.dp), + imageVector = AppIcons.Bell, + contentDescription = null, + tint = MomentoTheme.colors.grayW60 + ) + } + ) + }, + floatingActionButton = { + CustomFloatingActionButton( + onClick = { + navController.navigate("record_write") + } + ) + } + ) { paddingValues -> + + when (uiState) { + is HomeUiState.Loading -> { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator( + modifier = Modifier.size(40.dp), + color = MomentoTheme.colors.brownW20, + strokeWidth = 4.dp + ) + } + } + is HomeUiState.Error -> { + Log.e("home", "home 데이터 로드 실패") + } + is HomeUiState.Success -> { + // Success 데이터 추출 + val data = uiState as HomeUiState.Success + + Column( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize() + ) { + Spacer(Modifier.height(20.dp)) + + HomeHeaderSection( + nickname = data.nickname, + badgeLv = data.badgeLv, + badgeName = data.badgeName, + recordCnt = data.recordCnt, + stampCnt = data.stampCnt + ) + + Spacer(Modifier.height(20.dp)) + + // 여행 기록 영역 (카드형 + 타임라인형) + Column( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + .background(color = MomentoTheme.colors.brownBg, shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp)) + .padding(horizontal = 20.dp) + ) { + Spacer(Modifier.height(20.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "내 여행 기록", + style = MomentoTheme.typography.title02, + color = MomentoTheme.colors.grayW20 + ) + Icon( + modifier = Modifier.clickable { + navController.navigate("my_record") + }, + imageVector = AppIcons.ArrowRight, + contentDescription = null, + tint = MomentoTheme.colors.grayW20 + ) + } + + Spacer(Modifier.height(12.dp)) + + if (data.records.isEmpty()) { + // 기록 없는 경우 + Column( + modifier = Modifier + .fillMaxWidth() + .weight(1f), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(Modifier.height(20.dp)) + Image( + painter = painterResource(R.drawable.record_empty), + contentDescription = null + ) + Spacer(Modifier.height(12.dp)) + Text( + text = "아직 기록이 없네요. \n첫 여행을 기록해보세요!", + style = MomentoTheme.typography.title01, + color = MomentoTheme.colors.grayW20, + textAlign = TextAlign.Center + ) + } + } else { + // 카드형 영역 + HomeCardSection(records = data.records) + + Spacer(Modifier.height(20.dp)) + + // 타임라인형 영역 + TimelineSection(records = data.records) + } + } + } + } + } + } +} + +@Composable +fun HomeHeaderSection( + nickname: String, + recordCnt: Int, + stampCnt: Int, + badgeLv: Int, + badgeName: String, +// badgeImageResId: Int +) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp) + ) { + UserBadge( + badgeLv = badgeLv, + badgeName = badgeName, +// badgeImageResId = badgeImageResId + ) + + Spacer(Modifier.height(12.dp)) + + Text( + text = "$nickname 님,", + style = MomentoTheme.typography.title02, + color = MomentoTheme.colors.grayW20 + ) + Spacer(Modifier.height(4.dp)) + Text( +// text = "이번 달엔 총0번 여행을 다녀왔어요!", + text = "지금까지 총 ${recordCnt}번 여행을 다녀왔어요!", + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.grayW20 + ) + + Spacer(Modifier.height(12.dp)) + + // 스탬프 영역 + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Image( + painter = painterResource(R.drawable.my_stamp), + contentDescription = null + ) + Spacer(Modifier.width(4.dp)) + Text( + text = "모은 스탬프 ${stampCnt}개", + style = MomentoTheme.typography.label, + color = MomentoTheme.colors.grayW20 + ) + } + } +} + +@Composable +fun HomeCardSection( + records: List +) { + LazyRow( + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + items(records) { record -> + if (record.imageUrl.isEmpty()) { + HomeCardNoImage(record = record) + } else { + HomeCardImage(record = record) + } + } + } +} + +@Composable +fun HomeCardNoImage( + record: TripRecord +) { + Box( + modifier = Modifier + .size(100.dp) + .background(color = MomentoTheme.colors.greenW80) + .padding(horizontal = 12.dp), + contentAlignment = Alignment.Center + ) { + Image( + painter = painterResource(R.drawable.image_basic), + contentDescription = null + ) + record.selectedPlace?.let { selectedPlace -> + Text( + text = selectedPlace.title, + style = MomentoTheme.typography.label, + color = MomentoTheme.colors.grayW20, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + } +} + +@Composable +fun HomeCardImage( + record: TripRecord +) { + Box( + modifier = Modifier, + contentAlignment = Alignment.Center + ) { + AsyncImage( + modifier = Modifier.size(100.dp), + model = record.imageUrl, + contentDescription = null, + contentScale = ContentScale.Crop + ) + + // 이미지 어둡게 처리 + Box( + modifier = Modifier + .matchParentSize() + .background(color = Color.Black.copy(alpha = 0.3f)) + ) + + record.selectedPlace?.let { selectedPlace -> + Text( + modifier = Modifier + .width(88.dp) + .padding(start = 12.dp), + text = selectedPlace.title, + style = MomentoTheme.typography.label, + color = MomentoTheme.colors.white, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + } +} + +@Composable +fun TimelineSection( + records: List +) { + LazyColumn( + modifier = Modifier + .fillMaxWidth() + .padding(top = 20.dp), + ) { + itemsIndexed(records) { idx, record -> + // 현재 항목의 년도 + val currentYear = record.startDateMillis.toDateString("yyyy") + Log.d("home", "toDateString - currentYear: $currentYear") + + // 직전 항목의 년도와 비교 + val previousYear = if (idx > 0) { + records[idx-1].startDateMillis.toDateString("yyyy") + } else { + null + } + + // 년도별 첫번째 항목 여부 + val isYearStart = (idx == 0) || (currentYear != previousYear) + + // 1번째 항목인 경우 or 년도가 바뀌는 시점에 년도 표시 + if (idx == 0 || currentYear != previousYear) { + if (idx != 0) { + Spacer(Modifier.height(20.dp)) + } + YearHeader(year = currentYear) + Spacer(Modifier.height(12.dp)) + } + + TimelineItem( + isYearStart = isYearStart, + startDateMillis = record.startDateMillis, + endDateMillis = if (record.endDateMillis == 0L) null else record.endDateMillis, + imageUrl = if (record.imageUrl.isEmpty()) null else record.imageUrl, + title = record.title, + placeName = record.selectedPlace?.title + ) + } + + item { + Spacer(Modifier.height(20.dp)) + } + } +} + +@Composable +fun YearHeader( + year: String +) { + Row { + Icon( + imageVector = AppIcons.Year, + contentDescription = null, + tint = MomentoTheme.colors.brownBase + ) + Spacer(Modifier.width(6.dp)) + Text( + text = "${year}년", + style = MomentoTheme.typography.title02, + color = MomentoTheme.colors.grayW20 + ) + } +} + +@Composable +fun TimelineItem( + isYearStart: Boolean, + startDateMillis: Long, + endDateMillis: Long?, + title: String, + placeName: String?, + imageUrl: String? +) { + // 월.일 추출 (예: 07.25) + val startDate = startDateMillis.toDateString("MM.dd") + val endDate = endDateMillis?.toDateString("MM.dd") + + // 여행 날짜 텍스트 계산 + val dateText = endDate?.let { "$startDate ~ $endDate" } ?: startDate + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row( + modifier = Modifier.weight(1f) + ) { + HomeGrayLine(isYearStart = isYearStart) + + Spacer(Modifier.width(12.dp)) + + Column( + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Spacer(Modifier.height(4.dp)) + Text( + text = dateText, + style = MomentoTheme.typography.body03, + color = MomentoTheme.colors.grayW20 + ) + Text( + text = title, + style = MomentoTheme.typography.body02, + color = MomentoTheme.colors.grayW20, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Image( + painter = painterResource(R.drawable.write_place), + contentDescription = null + ) + Spacer(Modifier.width(6.dp)) + placeName?.let { place -> + Text( + text = place, + style = MomentoTheme.typography.body03, + color = MomentoTheme.colors.grayW20 + ) + } + } + } + } + + imageUrl?.let { image -> + AsyncImage( + modifier = Modifier.size(90.dp), + model = image, + contentDescription = null, + contentScale = ContentScale.Crop + ) + } + } +} + +@Composable +fun HomeGrayLine( + isYearStart: Boolean +) { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + if (isYearStart) { + Box( + modifier = Modifier + .size(8.dp) + .background(color = MomentoTheme.colors.grayW60, shape = CircleShape) + ) + } + VerticalDivider(modifier = Modifier.height(100.dp), thickness = 1.dp, color = MomentoTheme.colors.grayW60) + Box( + modifier = Modifier + .size(8.dp) + .background(color = MomentoTheme.colors.grayW60, shape = CircleShape) + ) + } +} + +@Preview +@Composable +fun HomeScreen2Preview() { + DngoTheme { + HomeScreen2( + navController = rememberNavController() + ) + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/home/HomeUiState.kt b/app/src/main/java/com/min/dnapp/presentation/home/HomeUiState.kt new file mode 100644 index 0000000..e49a643 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/home/HomeUiState.kt @@ -0,0 +1,17 @@ +package com.min.dnapp.presentation.home + +import com.min.dnapp.domain.model.TripRecord + +sealed class HomeUiState { + data object Loading : HomeUiState() + data class Success( + val nickname: String, + val badgeLv: Int, + val badgeName: String, + val recordCnt: Int, + val stampCnt: Int, + // 기록 목록 + val records: List = emptyList() + ) : HomeUiState() + data class Error(val message: String) : HomeUiState() +} diff --git a/app/src/main/java/com/min/dnapp/presentation/home/HomeViewModel.kt b/app/src/main/java/com/min/dnapp/presentation/home/HomeViewModel.kt new file mode 100644 index 0000000..20c6597 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/home/HomeViewModel.kt @@ -0,0 +1,84 @@ +package com.min.dnapp.presentation.home + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.min.dnapp.domain.model.User +import com.min.dnapp.domain.usecase.GetCurrentUserIdUseCase +import com.min.dnapp.domain.usecase.GetUserDataUseCase +import com.min.dnapp.domain.usecase.GetUserRecordUseCase +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class HomeViewModel @Inject constructor( + private val getUserDataUseCase: GetUserDataUseCase, + private val getCurrentUserIdUseCase: GetCurrentUserIdUseCase, + private val getUserRecordUseCase: GetUserRecordUseCase +) : ViewModel() { + + private val _uiState = MutableStateFlow(HomeUiState.Loading) + val uiState: StateFlow = _uiState.asStateFlow() + + init { + loadHomeData() + } + + private fun loadHomeData() { + viewModelScope.launch { + // 로딩 시작 + _uiState.value = HomeUiState.Loading + + // 인증 정보 요청 + val uid = try { + getCurrentUserIdUseCase() ?: throw Exception("사용자 인증 정보 없음") + } catch (e: Exception) { + _uiState.value = HomeUiState.Error("인증 정보 로드 실패: ${e.message}") + return@launch + } + + // 사용자 정보 로드 및 Success 상태 초기화 + val successState: HomeUiState.Success + try { + val user = getUserDataUseCase(uid) + Log.d("home", "loadHomeData - user: $user") + + successState = mapUserToHomeUiState(user) + + _uiState.value = successState + } catch (e: Exception) { + _uiState.value = HomeUiState.Error("사용자 정보 로드 실패: ${e.message}") + return@launch + } + + // 여행기록 정보 로드 및 상태 업데이트 + try { + val userRecords = getUserRecordUseCase() + Log.d("home", "loadHomeData - userRecords: $userRecords") + val finalSuccessState = successState.copy( + records = userRecords + ) + _uiState.value = finalSuccessState + } catch (e: Exception) { + _uiState.value = successState.copy( + records = emptyList() + ) + Log.e("home", "기록 정보 로드 실패", e) + } + } + } + + private fun mapUserToHomeUiState(user: User): HomeUiState.Success { + return HomeUiState.Success( + nickname = user.nickname, + badgeLv = user.badgeLv, + badgeName = user.badgeName, + recordCnt = user.recordCnt, + stampCnt = user.stampCnt + ) + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/init/AppInitViewModel.kt b/app/src/main/java/com/min/dnapp/presentation/init/AppInitViewModel.kt new file mode 100644 index 0000000..0273343 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/init/AppInitViewModel.kt @@ -0,0 +1,113 @@ +package com.min.dnapp.presentation.init + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.min.dnapp.domain.usecase.GetInitStatusUseCase +import com.min.dnapp.domain.usecase.SetOnboardingCompletedUseCase +import com.min.dnapp.domain.usecase.SetProfileSetupCompletedUseCase +import com.min.dnapp.domain.usecase.UpdateProfileImageUseCase +import com.min.dnapp.presentation.mypage.ProfileImageType +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +enum class InitRoute { + LOADING, + ONBOARDING, + PROFILE_SETUP, + MAIN +} + +data class InitUiState( + val route: InitRoute = InitRoute.LOADING +) + +@HiltViewModel +class AppInitViewModel @Inject constructor( + getInitStatusUseCase: GetInitStatusUseCase, + private val setOnboardingCompletedUseCase: SetOnboardingCompletedUseCase, + private val setProfileSetupCompletedUseCase: SetProfileSetupCompletedUseCase, + private val updateProfileImageUseCase: UpdateProfileImageUseCase +) : ViewModel() { + + private val _uiState = MutableStateFlow(InitUiState()) + val uiState: StateFlow = _uiState.asStateFlow() + + // 선택된 프로필 이미지 + private val _selectedImage = MutableStateFlow(null) + val selectedImage: StateFlow = _selectedImage.asStateFlow() + + // 프로필 저장 상태 + private val _saveImageState = MutableStateFlow(SaveImageState.Init) + val saveImageState: StateFlow = _saveImageState.asStateFlow() + + init { + viewModelScope.launch { + getInitStatusUseCase().collect { (isOnboardingCompleted, isProfileSetupCompleted) -> + val nextRoute = when { + // 2단계 완료 + isProfileSetupCompleted -> InitRoute.MAIN + // 1단계 완료 + isOnboardingCompleted -> InitRoute.PROFILE_SETUP + // 둘다 완료되지 않음 + else -> InitRoute.ONBOARDING + } + _uiState.update { it.copy(route = nextRoute) } + } + } + } + + /** + * 온보딩 3개 완료 후 호출 + */ + fun onOnboardingFinished() { + viewModelScope.launch { + setOnboardingCompletedUseCase() + } + } + + /** + * 프로필 선택 완료 후 호출 + */ + private fun onProfileSetupFinished() { + viewModelScope.launch { + setProfileSetupCompletedUseCase() + } + } + + /** + * 프로필 이미지 선택 + */ + fun selectImage(image: ProfileImageType) { + // 선택된 이미지 다시 선택하면 해제 + _selectedImage.value = if (_selectedImage.value == image) null else image + } + + /** + * 프로필 이미지 저장 + */ + fun saveProfileImage() { + viewModelScope.launch { + _selectedImage.value?.let { image -> + + // 로딩 시작 + _saveImageState.value = SaveImageState.Loading + + val result = updateProfileImageUseCase(image.key) + + result.onSuccess { + onProfileSetupFinished() + _saveImageState.value = SaveImageState.Success + }.onFailure { exception -> + Log.e("init", "saveProfileImage 실패", exception) + _saveImageState.value = SaveImageState.Error("이미지 저장 실패") + } + } + } + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/init/LoadingScreen.kt b/app/src/main/java/com/min/dnapp/presentation/init/LoadingScreen.kt new file mode 100644 index 0000000..92efd05 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/init/LoadingScreen.kt @@ -0,0 +1,33 @@ +package com.min.dnapp.presentation.init + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.size +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.min.dnapp.presentation.ui.theme.MomentoTheme + +@Composable +fun LoadingScreen() { + Surface( + modifier = Modifier.fillMaxSize(), + color = MomentoTheme.colors.brownBg + ) { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + CircularProgressIndicator( + modifier = Modifier.size(40.dp), + color = MomentoTheme.colors.brownW20, + strokeWidth = 4.dp + ) + } + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/init/OnboardingScreen.kt b/app/src/main/java/com/min/dnapp/presentation/init/OnboardingScreen.kt new file mode 100644 index 0000000..d41a875 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/init/OnboardingScreen.kt @@ -0,0 +1,63 @@ +package com.min.dnapp.presentation.init + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.material3.Surface +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.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.min.dnapp.R +import com.min.dnapp.presentation.init.component.OnboardingButtonSection +import com.min.dnapp.presentation.ui.theme.DngoTheme +import com.min.dnapp.presentation.ui.theme.MomentoTheme + +@Composable +fun OnboardingScreen( + onFinish: () -> Unit +) { + Surface( + modifier = Modifier + .systemBarsPadding() + .fillMaxSize(), + color = MomentoTheme.colors.brownW90 + ) { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.SpaceBetween + ) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.End + ) { + Spacer(Modifier.height(144.dp)) + Image( + painter = painterResource(R.drawable.logo_onboarding), + contentDescription = null + ) + } + + OnboardingButtonSection( + onboardingNum = 1, + title = "모멘토와 함께하는 따뜻하고 진솔한 공간. \n제목, 날짜, 장소, 감정을 \n입력하여 내 기록을 남겨보세요.", + onClick = { onFinish() } + ) + } + } +} + +@Preview +@Composable +fun OnboardingScreenPreview() { + DngoTheme { +// OnboardingScreen() + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/init/OnboardingScreen2.kt b/app/src/main/java/com/min/dnapp/presentation/init/OnboardingScreen2.kt new file mode 100644 index 0000000..c63a77d --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/init/OnboardingScreen2.kt @@ -0,0 +1,90 @@ +package com.min.dnapp.presentation.init + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +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.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import com.min.dnapp.R +import com.min.dnapp.presentation.init.component.OnboardingButtonSection +import com.min.dnapp.presentation.ui.icon.AppIcons +import com.min.dnapp.presentation.ui.icon.appicons.Back +import com.min.dnapp.presentation.ui.theme.DngoTheme +import com.min.dnapp.presentation.ui.theme.MomentoTheme + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun OnboardingScreen2( + navController: NavHostController, + onFinish: () -> Unit +) { + Scaffold( + containerColor = MomentoTheme.colors.brownW90, + topBar = { + CenterAlignedTopAppBar( + title = { Text("") }, + navigationIcon = { + Icon( + modifier = Modifier + .clickable { navController.popBackStack() } + .padding(16.dp), + imageVector = AppIcons.Back, + contentDescription = null + ) + }, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + containerColor = MomentoTheme.colors.brownW90 + ) + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize(), + verticalArrangement = Arrangement.SpaceBetween + ) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.End + ) { + Spacer(Modifier.height(76.dp)) + Image( + painter = painterResource(R.drawable.logo_onboarding2), + contentDescription = null + ) + } + + OnboardingButtonSection( + onboardingNum = 2, + title = "여행을 기록할 때마다 스탬프가 적립돼요. \n하나의 기록이 모여 나만의 컬렉션이 완성됩니다. \n", + onClick = { onFinish() } + ) + } + } +} + +@Preview +@Composable +fun OnboardingScreen2Preview() { + DngoTheme { +// OnboardingScreen2() + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/init/OnboardingScreen3.kt b/app/src/main/java/com/min/dnapp/presentation/init/OnboardingScreen3.kt new file mode 100644 index 0000000..5296fa8 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/init/OnboardingScreen3.kt @@ -0,0 +1,90 @@ +package com.min.dnapp.presentation.init + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +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.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import com.min.dnapp.R +import com.min.dnapp.presentation.init.component.OnboardingButtonSection +import com.min.dnapp.presentation.ui.icon.AppIcons +import com.min.dnapp.presentation.ui.icon.appicons.Back +import com.min.dnapp.presentation.ui.theme.DngoTheme +import com.min.dnapp.presentation.ui.theme.MomentoTheme + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun OnboardingScreen3( + navController: NavHostController, + onFinish: () -> Unit +) { + Scaffold( + containerColor = MomentoTheme.colors.brownW90, + topBar = { + CenterAlignedTopAppBar( + title = { Text("") }, + navigationIcon = { + Icon( + modifier = Modifier + .clickable { navController.popBackStack() } + .padding(16.dp), + imageVector = AppIcons.Back, + contentDescription = null + ) + }, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + containerColor = MomentoTheme.colors.brownW90 + ) + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize(), + verticalArrangement = Arrangement.SpaceBetween + ) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(Modifier.height(40.dp)) + Image( + painter = painterResource(R.drawable.logo_onboarding3), + contentDescription = null + ) + } + + OnboardingButtonSection( + onboardingNum = 3, + title = "솔직한 기록을 다른 사람과 가볍게 나누기 \n공감과 영감을 주고받는 공간입니다. \n", + onClick = { onFinish() } + ) + } + } +} + +@Preview +@Composable +fun OnboardingScreen3Preview() { + DngoTheme { +// OnboardingScreen3() + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/init/ProfileSetupScreen.kt b/app/src/main/java/com/min/dnapp/presentation/init/ProfileSetupScreen.kt new file mode 100644 index 0000000..3796175 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/init/ProfileSetupScreen.kt @@ -0,0 +1,144 @@ +package com.min.dnapp.presentation.init + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.min.dnapp.presentation.mypage.ProfileImageType +import com.min.dnapp.presentation.mypage.component.ProfileImageItem +import com.min.dnapp.presentation.ui.component.SelectButton +import com.min.dnapp.presentation.ui.theme.DngoTheme +import com.min.dnapp.presentation.ui.theme.MomentoTheme + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ProfileSetupScreen( + viewModel: AppInitViewModel = hiltViewModel(), + onFinish: () -> Unit +) { + val selectedImage by viewModel.selectedImage.collectAsStateWithLifecycle() + val saveImageState by viewModel.saveImageState.collectAsStateWithLifecycle() + + Scaffold( + containerColor = MomentoTheme.colors.brownBg, + topBar = { + CenterAlignedTopAppBar( + title = { + Text( + text = "프로필 설정", + style = MomentoTheme.typography.title02, + color = MomentoTheme.colors.grayW20 + ) + }, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + containerColor = MomentoTheme.colors.brownBg + ) + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize(), + verticalArrangement = Arrangement.SpaceBetween + ) { + // 프로필 이미지 목록 (8개) + val allImages = ProfileImageType.entries.toList() + + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + for (idx in 0 until allImages.size step 2) { + + // 처음 Row가 아니면, Row 위에 간격 추가 + if (idx > 0) { + Spacer(Modifier.height(16.dp)) + } + + Row( + horizontalArrangement = Arrangement.spacedBy(20.dp) + ) { + ProfileImageItem( + profileImageType = allImages[idx], + isSelected = selectedImage == allImages[idx], + onClick = { image -> viewModel.selectImage(image) } + ) + ProfileImageItem( + profileImageType = allImages[idx+1], + isSelected = selectedImage == allImages[idx+1], + onClick = { image -> viewModel.selectImage(image) } + ) + } + } + + Spacer(Modifier.height(20.dp)) + + Text( + text = "원하는 프로필을 선택해주세요!", + style = MomentoTheme.typography.title01, + color = MomentoTheme.colors.grayW20 + ) + } + + // 확인 버튼 + Column( + modifier = Modifier.padding(horizontal = 20.dp) + ) { + SelectButton( + enabled = selectedImage != null, + onConfirm = { viewModel.saveProfileImage() } + ) + Spacer(Modifier.height(20.dp)) + } + } + } + + when (saveImageState) { + is SaveImageState.Loading -> { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator( + modifier = Modifier.size(40.dp), + color = MomentoTheme.colors.brownW20, + strokeWidth = 4.dp + ) + } + } + is SaveImageState.Success -> { onFinish() } + is SaveImageState.Error -> {} + else -> { + // Init 또는 Success + } + } +} + +@Preview +@Composable +fun ProfileSelectScreenPreview() { + DngoTheme { +// ProfileSetupScreen() + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/init/SaveImageState.kt b/app/src/main/java/com/min/dnapp/presentation/init/SaveImageState.kt new file mode 100644 index 0000000..a8e4ff0 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/init/SaveImageState.kt @@ -0,0 +1,8 @@ +package com.min.dnapp.presentation.init + +sealed class SaveImageState { + data object Init : SaveImageState() + data object Loading : SaveImageState() + data object Success : SaveImageState() + data class Error(val message: String) : SaveImageState() +} diff --git a/app/src/main/java/com/min/dnapp/presentation/init/component/OnboardingButtonSection.kt b/app/src/main/java/com/min/dnapp/presentation/init/component/OnboardingButtonSection.kt new file mode 100644 index 0000000..03dc659 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/init/component/OnboardingButtonSection.kt @@ -0,0 +1,99 @@ +package com.min.dnapp.presentation.init.component + +import androidx.compose.foundation.background +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.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.min.dnapp.presentation.ui.theme.MomentoTheme + +@Composable +fun OnboardingButtonSection( + onboardingNum: Int, + title: String, + onClick: () -> Unit +) { + Box( + modifier = Modifier + .fillMaxWidth() + .background(color = MomentoTheme.colors.white, shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp)), + ) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(Modifier.height(40.dp)) + Text( + text = title, + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.black, + textAlign = TextAlign.Center + ) + Spacer(Modifier.height(20.dp)) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row { + OnboardingIndicator( + isActive = onboardingNum == 1 + ) + Spacer(Modifier.width(4.dp)) + OnboardingIndicator( + isActive = onboardingNum == 2 + ) + Spacer(Modifier.width(4.dp)) + OnboardingIndicator( + isActive = onboardingNum == 3 + ) + } + Box( + modifier = Modifier + .clickable { onClick() } + .background(color = MomentoTheme.colors.pinkW80, shape = RoundedCornerShape(10.dp)) + .padding(horizontal = 20.dp, vertical = 12.dp) + ) { + Text( + text = "Next", + style = MomentoTheme.typography.label, + color = MomentoTheme.colors.black, + textAlign = TextAlign.Center + ) + } + } + Spacer(Modifier.height(20.dp)) + } + } +} + +@Composable +private fun OnboardingIndicator(isActive: Boolean) { + val indicatorColor = if (isActive) MomentoTheme.colors.pinkBase else MomentoTheme.colors.pinkW40 + val indicatorShape = if (isActive) RoundedCornerShape(20.dp) else CircleShape + val indicatorWidth = if (isActive) 16.dp else 8.dp + val indicatorHeight = 8.dp + + Box( + modifier = Modifier + .width(indicatorWidth) + .height(indicatorHeight) + .background(color = indicatorColor, shape = indicatorShape) + ) +} diff --git a/app/src/main/java/com/min/dnapp/presentation/login/LoginScreen.kt b/app/src/main/java/com/min/dnapp/presentation/login/LoginScreen.kt new file mode 100644 index 0000000..68659c4 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/login/LoginScreen.kt @@ -0,0 +1,79 @@ +package com.min.dnapp.presentation.login + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.navigation.NavController + +@Composable +fun LoginScreen( + loginViewModel: LoginViewModel = hiltViewModel(), + navController: NavController +) { + // viewModel의 상태를 구독 + val isLoading by loginViewModel.isLoading.collectAsStateWithLifecycle() + val context = LocalContext.current + + // 스택바 상태 관리 + val snackbarHostState = remember { SnackbarHostState() } + + // 로그인 결과에 따른 처리 +// LaunchedEffect(loginResult) { +// loginResult?.let { result -> +// result.onSuccess { +// navController.navigate("home") { +// popUpTo("login") { inclusive = true } +// } +// loginViewModel.clearLoginResult() +// }.onFailure { exception -> +// val errorMessage = when { +// exception.message?.contains("network", ignoreCase = true) == true -> +// "네트워크 연결을 확인해주세요" +// exception.message?.contains("cancelled", ignoreCase = true ) == true -> +// "로그인이 취소되었습니다" +// else -> "로그인에 실패했습니다" +// } +// snackbarHostState.showSnackbar(errorMessage) +// } +// } +// } + + Scaffold( + snackbarHost = { SnackbarHost(snackbarHostState) } + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + ) { + if (isLoading) { + CircularProgressIndicator() + Text( + text = "로그인 중..." + ) + } else { + Button( + onClick = { +// loginViewModel.onKakaoLoginClicked(context) + } + ) { + Text("카카오 로그인") + } + } + } + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/login/LoginScreen2.kt b/app/src/main/java/com/min/dnapp/presentation/login/LoginScreen2.kt new file mode 100644 index 0000000..f12372f --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/login/LoginScreen2.kt @@ -0,0 +1,183 @@ +package com.min.dnapp.presentation.login + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +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.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Icon +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.navigation.NavHostController +import com.min.dnapp.R +import com.min.dnapp.presentation.ui.icon.AppIcons +import com.min.dnapp.presentation.ui.icon.appicons.Kakao +import com.min.dnapp.presentation.ui.theme.DngoTheme +import com.min.dnapp.presentation.ui.theme.KakaoYellow +import com.min.dnapp.presentation.ui.theme.MomentoTheme + +@Composable +fun LoginScreen2( + navController: NavHostController, + viewModel: LoginViewModel = hiltViewModel(), + onLoginSuccess: () -> Unit +) { + val context = LocalContext.current + val isLoading by viewModel.isLoading.collectAsStateWithLifecycle() + + Surface( + modifier = Modifier + // 시스템바 영역 피하기 (status bar, navigation bar 모두 적용) + .systemBarsPadding() + .fillMaxSize(), + color = MomentoTheme.colors.brownW80 + ) { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.SpaceBetween + ) { + LoginHeaderSection() + LoginButtonSection( + onClick = { + viewModel.onKakaoLoginClicked( + context = context, + onSuccess = { onLoginSuccess() }, + onFailure = { + + } + ) + } + ) + } + } + + if (isLoading) { + Box( + modifier = Modifier + .fillMaxSize() + .background(MomentoTheme.colors.black.copy(alpha = 0.5f)), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator( + modifier = Modifier.size(40.dp), + color = MomentoTheme.colors.white, + strokeWidth = 4.dp + ) + } + } +} + +@Composable +fun LoginHeaderSection() { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(Modifier.height(60.dp)) + + Box( + modifier = Modifier.fillMaxWidth(), + contentAlignment = Alignment.BottomCenter + ) { + Image( + painter = painterResource(R.drawable.login_cloud), + contentDescription = null, + contentScale = ContentScale.Crop + ) + Image( + modifier = Modifier.offset(y = 28.dp), + painter = painterResource(R.drawable.login_momento), + contentDescription = null + ) + } + + Spacer(Modifier.height(48.dp)) + + Text( + text = "모멘토에 오신 것을 환영합니다!", + style = MomentoTheme.typography.title01, + color = MomentoTheme.colors.brownB60 + ) + } +} + +@Composable +fun LoginButtonSection( + onClick: () -> Unit +) { + Box( + modifier = Modifier.fillMaxWidth(), + contentAlignment = Alignment.BottomCenter + ) { + Image( + modifier = Modifier.offset(y = 20.dp), + painter = painterResource(R.drawable.login_mount), + contentDescription = null, + contentScale = ContentScale.Crop + ) + + Column( + modifier = Modifier.fillMaxWidth() + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp) + .background(color = KakaoYellow, shape = RoundedCornerShape(10.dp)) + ) { + Row( + modifier = Modifier + .clickable { onClick() } + .fillMaxWidth() + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = AppIcons.Kakao, + contentDescription = null + ) + Text( + modifier = Modifier.fillMaxWidth(), + text = "카카오로 로그인하기", + style = MomentoTheme.typography.title02, + color = MomentoTheme.colors.grayW20, + textAlign = TextAlign.Center + ) + } + } + Spacer(Modifier.height(60.dp)) + } + } +} + +@Preview +@Composable +fun LoginScreen2Preview() { + DngoTheme { +// LoginScreen2() + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/login/LoginViewModel.kt b/app/src/main/java/com/min/dnapp/presentation/login/LoginViewModel.kt new file mode 100644 index 0000000..904e2ed --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/login/LoginViewModel.kt @@ -0,0 +1,122 @@ +package com.min.dnapp.presentation.login + +import android.content.Context +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.min.dnapp.domain.usecase.LogoutUseCase +import com.min.dnapp.domain.usecase.AuthWithKakaoUseCase +import com.min.dnapp.domain.usecase.UnlinkUseCase +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class LoginViewModel @Inject constructor( + private val authWithKakaoUseCase: AuthWithKakaoUseCase, + private val logoutUseCase: LogoutUseCase, + private val unlinkUseCase: UnlinkUseCase +) : ViewModel() { + + // 로딩 상태 + private val _isLoading = MutableStateFlow(false) + val isLoading: StateFlow = _isLoading.asStateFlow() + + // 회원탈퇴 완료 모달창 상태 + private val _showUserRemoveSuccessDialog = MutableStateFlow(false) + val showUserRemoveSuccessDialog: StateFlow = _showUserRemoveSuccessDialog.asStateFlow() + + /** + * 카카오 로그인 + */ + fun onKakaoLoginClicked( + context: Context, + onSuccess: () -> Unit, + onFailure: (Throwable) -> Unit + ) { + viewModelScope.launch { + _isLoading.value = true + + try { + val result = authWithKakaoUseCase(context) + Log.d("auth", "onKakaoLoginClicked - result : $result") + + result.onSuccess { + onSuccess() + }.onFailure { exception -> + onFailure(exception) + } + } catch (e: Exception) { + Log.e("auth", "onKakaoLoginClicked - unexpected error", e) + onFailure(e) + } finally { + _isLoading.value = false + } + } + } + + /** + * 로그아웃 처리 + * - 카카오 SDK 로그아웃 + * - firebase auth 로그아웃 + */ + fun onLogoutClicked( + onSuccess: () -> Unit, + onFailure: (Throwable) -> Unit + ) { + viewModelScope.launch { + try { + val result = logoutUseCase() + Log.d("auth", "onLogoutClicked - result : $result") + + result.onSuccess { + onSuccess() + }.onFailure { exception -> + onFailure(exception) + } + } catch (e: Exception) { + Log.e("auth", "onLogoutClicked - unexpected error", e) + onFailure(e) + } + } + } + + /** + * 회원탈퇴 완료 모달창 열기 + */ + fun openDialog() { + _showUserRemoveSuccessDialog.value = true + } + + /** + * 회원탈퇴 처리 + * - firebase auth 사용자 삭제 + * - firestore "users" 문서 삭제 + * - 카카오 연결 끊기 + */ + fun onUnlinkClicked( +// onSuccess: () -> Unit, +// onFailure: (Throwable) -> Unit + ) { + viewModelScope.launch { + try { + val result = unlinkUseCase() + Log.d("auth", "onUnlinkClicked - result : $result") + + result.onSuccess { +// onSuccess() + openDialog() + }.onFailure { exception -> +// onFailure(exception) + Log.e("auth", "회원탈퇴 처리 실패", exception) + } + } catch (e: Exception) { + Log.e("auth", "onUnlinkClicked - unexpected error", e) +// onFailure(e) + } + } + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/mypage/MyRecordScreen.kt b/app/src/main/java/com/min/dnapp/presentation/mypage/MyRecordScreen.kt new file mode 100644 index 0000000..0fcb3a9 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/mypage/MyRecordScreen.kt @@ -0,0 +1,293 @@ +package com.min.dnapp.presentation.mypage + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +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.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.navigation.NavHostController +import coil3.compose.AsyncImage +import com.min.dnapp.R +import com.min.dnapp.presentation.common.EmotionMapper +import com.min.dnapp.presentation.common.WeatherMapper +import com.min.dnapp.presentation.home.YearHeader +import com.min.dnapp.presentation.ui.icon.AppIcons +import com.min.dnapp.presentation.ui.icon.appicons.Back +import com.min.dnapp.presentation.ui.theme.MomentoTheme +import com.min.dnapp.util.toDateString + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MyRecordScreen( + navController: NavHostController, + myRecordViewModel: MyRecordViewModel = hiltViewModel() +) { + val uiState by myRecordViewModel.uiState.collectAsStateWithLifecycle() + + Scaffold( + containerColor = MomentoTheme.colors.brownBg, + topBar = { + CenterAlignedTopAppBar( + title = { + Text( + text = "내 기록 모아보기", + style = MomentoTheme.typography.title02, + color = MomentoTheme.colors.grayW20 + ) + }, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + MomentoTheme.colors.brownBg + ), + navigationIcon = { + Icon( + modifier = Modifier + .clickable { navController.popBackStack() } + .padding(16.dp), + imageVector = AppIcons.Back, + contentDescription = null + ) + } + ) + } + ) { paddingValues -> + + when (uiState) { + is MyRecordUiState.Loading -> { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator( + modifier = Modifier.size(40.dp), + color = MomentoTheme.colors.brownW20, + strokeWidth = 4.dp + ) + } + } + is MyRecordUiState.Error -> {} + is MyRecordUiState.Success -> { + // Success 데이터 추출 + val data = uiState as MyRecordUiState.Success + + LazyColumn( + modifier = Modifier + .padding(paddingValues) + .padding(horizontal = 20.dp) + .fillMaxSize() + ) { + itemsIndexed(data.records) { idx, record -> + + // 현재 항목의 년도 + val currentYear = record.startDateMillis.toDateString("yyyy") + + // 직전 항목의 년도와 비교 + val previousYear = if (idx > 0) { + data.records[idx-1].startDateMillis.toDateString("yyyy") + } else { + null + } + + // 년도별 첫번째 항목 여부 (1번째 항목인 경우 or 년도가 바뀌는 시점) + val isYearStart = (idx == 0) || (currentYear != previousYear) + + // 년도 표시 + if (isYearStart) { + Spacer(Modifier.height(20.dp)) + YearHeader(year = currentYear) + Spacer(Modifier.height(12.dp)) + } + + // 날짜, 날씨+감정 + MyRecordItemHeaderSection( + startDateMillis = record.startDateMillis, + endDateMillis = if (record.endDateMillis == 0L) null else record.endDateMillis, + weatherName = record.weatherKey, + emotionName = record.emotionKey, + ) + + Spacer(Modifier.height(8.dp)) + + // 내 기록 아이템 + MyRecordItem( + title = record.title, + content = record.content, + placeName = record.selectedPlace?.title, + imageUrl = if (record.imageUrl.isEmpty()) null else record.imageUrl + ) + + Spacer(Modifier.height(20.dp)) + } + } + } + } + } +} + +@Composable +fun MyRecordItemHeaderSection( + startDateMillis: Long, + endDateMillis: Long?, + weatherName: String, + emotionName: String, +) { + val startDate = startDateMillis.toDateString("MM.dd") + val endDate = endDateMillis?.toDateString("MM.dd") + + // 여행 날짜 텍스트 계산 + val dateText = endDate?.let { "$startDate ~ $it" } ?: startDate + + // 날씨 & 감정 이미지 리소스로 변환 + val weatherImageResId = WeatherMapper.getWeatherImageResId(weatherName) + val emotionImageResId = EmotionMapper.getEmotionImageResId(emotionName) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + // 날짜 (08.20 ~ 08.22) + Text( + text = dateText, + style = MomentoTheme.typography.body02, + color = MomentoTheme.colors.grayW20 + ) + + // 날씨 & 감정 + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "날씨", + style = MomentoTheme.typography.body02, + color = MomentoTheme.colors.grayW20 + ) + Spacer(Modifier.width(4.dp)) + Image( + modifier = Modifier.size(28.dp), + painter = painterResource(weatherImageResId), + contentDescription = null + ) + } + + Spacer(Modifier.width(8.dp)) + + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "감정", + style = MomentoTheme.typography.body02, + color = MomentoTheme.colors.grayW20 + ) + Spacer(Modifier.width(4.dp)) + Image( + modifier = Modifier.size(28.dp), + painter = painterResource(emotionImageResId), + contentDescription = null + ) + } + } + } +} + +@Composable +fun MyRecordItem( + title: String, + content: String, + placeName: String?, + imageUrl: String? +) { + Column( + modifier = Modifier + .fillMaxWidth() + .background(color = MomentoTheme.colors.brownW90) + .padding(16.dp) + ) { + // 여행 제목 + Text( + text = title, + style = MomentoTheme.typography.body02, + color = MomentoTheme.colors.grayW20 + ) + + Spacer(Modifier.height(8.dp)) + + HorizontalDivider(thickness = 1.dp, color = MomentoTheme.colors.pinkBase) + + Spacer(Modifier.height(8.dp)) + + // 여행 장소 + placeName?.let { place -> + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Image( + painter = painterResource(R.drawable.write_place), + contentDescription = null + ) + Spacer(Modifier.width(6.dp)) + Text( + text = place, + style = MomentoTheme.typography.body03 , + color = MomentoTheme.colors.grayW20 + ) + } + } + + Spacer(Modifier.height(8.dp)) + + // 여행 내용 + Text( + text = content, + style = MomentoTheme.typography.body02, + color = MomentoTheme.colors.grayW20 + ) + + // 여행 이미지 + imageUrl?.let { image -> + Spacer(Modifier.height(8.dp)) + + AsyncImage( + modifier = Modifier + .fillMaxWidth() + // 가로:세로 비율 1:1 + .aspectRatio(1f), + model = image, + contentDescription = null, + contentScale = ContentScale.Crop + ) + } + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/mypage/MyRecordUiState.kt b/app/src/main/java/com/min/dnapp/presentation/mypage/MyRecordUiState.kt new file mode 100644 index 0000000..4be113b --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/mypage/MyRecordUiState.kt @@ -0,0 +1,11 @@ +package com.min.dnapp.presentation.mypage + +import com.min.dnapp.domain.model.TripRecord + +sealed class MyRecordUiState { + data object Loading: MyRecordUiState() + data class Success( + val records: List = emptyList() + ) : MyRecordUiState() + data class Error(val message: String): MyRecordUiState() +} \ No newline at end of file diff --git a/app/src/main/java/com/min/dnapp/presentation/mypage/MyRecordViewModel.kt b/app/src/main/java/com/min/dnapp/presentation/mypage/MyRecordViewModel.kt new file mode 100644 index 0000000..a95a207 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/mypage/MyRecordViewModel.kt @@ -0,0 +1,47 @@ +package com.min.dnapp.presentation.mypage + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.min.dnapp.domain.usecase.GetUserRecordUseCase +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class MyRecordViewModel @Inject constructor( + private val getUserRecordUseCase: GetUserRecordUseCase +) : ViewModel() { + + private val _uiState = MutableStateFlow(MyRecordUiState.Loading) + val uiState: StateFlow = _uiState.asStateFlow() + + init { + loadMyRecordData() + } + + private fun loadMyRecordData() { + viewModelScope.launch { + // 로딩 시작 + _uiState.value = MyRecordUiState.Loading + + // 내 여행기록 목록 가져오기 + try { + val myRecords = getUserRecordUseCase() + Log.d("my", "loadMyRecordData - myRecords: $myRecords") + val successState = MyRecordUiState.Success( + records = myRecords + ) + _uiState.value = successState + } catch (e: Exception) { + _uiState.value = MyRecordUiState.Success( + records = emptyList() + ) + Log.e("my", "내 여행기록 조회 실패", e) + } + } + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/mypage/MypageScreen.kt b/app/src/main/java/com/min/dnapp/presentation/mypage/MypageScreen.kt new file mode 100644 index 0000000..41171cf --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/mypage/MypageScreen.kt @@ -0,0 +1,455 @@ +package com.min.dnapp.presentation.mypage + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +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.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +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.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.navigation.NavHostController +import com.min.dnapp.R +import com.min.dnapp.presentation.common.ProfileMapper +import com.min.dnapp.presentation.mypage.component.NicknameUpdateDialog +import com.min.dnapp.presentation.mypage.component.ProfileImageDialog +import com.min.dnapp.presentation.ui.component.UserBadge +import com.min.dnapp.presentation.ui.icon.AppIcons +import com.min.dnapp.presentation.ui.icon.appicons.Bell +import com.min.dnapp.presentation.ui.icon.appicons.PenSmall +import com.min.dnapp.presentation.ui.theme.DngoTheme +import com.min.dnapp.presentation.ui.theme.MomentoTheme + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MypageScreen( + navController: NavHostController, + mypageViewModel: MypageViewModel = hiltViewModel() +) { + val uiState by mypageViewModel.uiState.collectAsStateWithLifecycle() + val showImageUpdateDialog by mypageViewModel.showImageUpdateDialog.collectAsStateWithLifecycle() + val selectedImage by mypageViewModel.selectedImage.collectAsStateWithLifecycle() + val nicknameState by mypageViewModel.nicknameState.collectAsStateWithLifecycle() + + var showNicknameUpdateDialog by remember { mutableStateOf(false) } + + Scaffold( + containerColor = MomentoTheme.colors.brownW90, + topBar = { + CenterAlignedTopAppBar( + title = { Text("") }, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + containerColor = MomentoTheme.colors.brownW90 + ), + navigationIcon = { + Image( + modifier = Modifier +// .clickable { } +// .padding(16.dp), + .padding(start = 16.dp), + painter = painterResource(R.drawable.logo_momento), + contentDescription = null + ) + }, + actions = { + Icon( + modifier = Modifier + .clickable { navController.navigate("bell") } + .padding(16.dp), + imageVector = AppIcons.Bell, + contentDescription = null, + tint = MomentoTheme.colors.grayW60 + ) + } + ) + } + ) { paddingValues -> + + when (uiState) { + is MypageUiState.Loading -> { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator( + modifier = Modifier.size(40.dp), + color = MomentoTheme.colors.brownW20, + strokeWidth = 4.dp + ) + } + } + is MypageUiState.Error -> {} + is MypageUiState.Success -> { + // Success 데이터 추출 + val data = uiState as MypageUiState.Success + + Column( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize() + ) { + Spacer(Modifier.height(20.dp)) + + MypageProfileSection( + profileImageName = data.user.profileImageName, + badgeLv = data.user.badgeLv, + badgeName = data.user.badgeName, + nickname = data.user.nickname, + recordCnt = data.user.recordCnt, + stampCnt = data.user.stampCnt, + onImageClick = { mypageViewModel.openDialog() }, + onNicknameClick = { showNicknameUpdateDialog = true } + ) + + Spacer(Modifier.height(20.dp)) + + HorizontalDivider(thickness = 1.dp, color = MomentoTheme.colors.brownW60) + + Spacer(Modifier.height(20.dp)) + + MypageMenuSection( + onRecordClick = { + navController.navigate("my_record") + }, + onSettingClick = { + navController.navigate("setting") + } + ) + } + } + } + } + + // 프로필 이미지 변경 모달창 + if (showImageUpdateDialog) { + ProfileImageDialog( + selectedImage = selectedImage, + onImageClick = { profileImageType -> + mypageViewModel.selectImage(profileImageType) + }, + onDismiss = { mypageViewModel.closeDialog() }, + onConfirm = { mypageViewModel.updateProfileImage() } + ) + } + + // 닉네임 변경 모달창 + if (showNicknameUpdateDialog) { + NicknameUpdateDialog( + state = nicknameState, + onValueChange = { newValue -> + mypageViewModel.onNicknameChange(newValue) + }, + onDismiss = { showNicknameUpdateDialog = false }, + onCancel = { showNicknameUpdateDialog = false }, + onConfirm = { + mypageViewModel.updateNickname( + onSuccess = { showNicknameUpdateDialog = false } + ) + } + ) + } +} + +@Composable +fun MypageProfileSection( + profileImageName: String, + badgeLv: Int, + badgeName: String, + nickname: String, + recordCnt: Int, + stampCnt: Int, + onImageClick: () -> Unit, + onNicknameClick: () -> Unit +) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + // 프로필 이미지 (수정 가능) + UserProfileImage( + profileImageName = profileImageName, + onClick = { onImageClick() } + ) + + Spacer(Modifier.height(12.dp)) + + // 뱃지 + UserBadge( + badgeLv = badgeLv, + badgeName = badgeName + ) + + Spacer(Modifier.height(16.dp)) + + // 닉네임 + UserNickname( + nickname = nickname, + onClick = { onNicknameClick() } + ) + + Spacer(Modifier.height(28.dp)) + + // 기록/스탬프 개수 + RecordAndStampNum( + recordCnt = recordCnt, + stampCnt = stampCnt + ) + } +} + +@Composable +fun UserProfileImage( + profileImageName: String, + onClick: () -> Unit +) { + val profileImageResId = ProfileMapper.getProfileImageResId(profileImageName) + + Box( + modifier = Modifier + .clickable { onClick() } + .size(120.dp) + ) { + Box( + modifier = Modifier + .size(120.dp) + .border(width = 2.dp, color = MomentoTheme.colors.grayW80, shape = RoundedCornerShape(10.dp)) + ) { + Image( + painter = painterResource(profileImageResId), + contentDescription = null + ) + } + + Box( + modifier = Modifier + .align(Alignment.BottomEnd) + .offset(x = 12.dp, y = 12.dp) + .size(28.dp) + .background(color = MomentoTheme.colors.grayW90, shape = CircleShape), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = AppIcons.PenSmall, + contentDescription = null, + tint = MomentoTheme.colors.grayW60 + ) + } + } +} + +@Composable +fun UserNickname( + nickname: String, + onClick: () -> Unit +) { + Column( + modifier = Modifier + .clickable { onClick() } + .width(120.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Spacer(Modifier.width(16.dp)) + Text( + text = nickname, + style = MomentoTheme.typography.title01, + color = MomentoTheme.colors.grayW20 + ) + Icon( + modifier = Modifier.size(16.dp), + imageVector = AppIcons.PenSmall, + contentDescription = null, + tint = MomentoTheme.colors.grayW60 + ) + } + + Spacer(Modifier.height(4.dp)) + + HorizontalDivider(thickness = 1.dp, color = MomentoTheme.colors.grayW60) + } +} + +@Composable +fun RecordAndStampNum( + recordCnt: Int, + stampCnt: Int +) { + Row( + modifier = Modifier + .padding(horizontal = 20.dp) + .fillMaxWidth() + ) { + // 여행 기록 영역 + Box( + modifier = Modifier + .weight(1f) + .background(color = MomentoTheme.colors.pinkW60, shape = RoundedCornerShape(10.dp)) + ) { + Column( + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxWidth() + ) { + Spacer(Modifier.height(12.dp)) + + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Image( + painter = painterResource(R.drawable.my_record), + contentDescription = null + ) + Spacer(Modifier.width(8.dp)) + Text( + text = "여행 기록", + style = MomentoTheme.typography.label, + color = MomentoTheme.colors.grayW20 + ) + } + + Spacer(Modifier.height(12.dp)) + + Text( + modifier = Modifier.fillMaxWidth(), + text = "${recordCnt}개", + style = MomentoTheme.typography.label, + color = MomentoTheme.colors.grayW20, + textAlign = TextAlign.End + ) + + Spacer(Modifier.height(12.dp)) + } + } + + Spacer(Modifier.width(16.dp)) + + // 수집 스탬프 영역 + Box( + modifier = Modifier + .weight(1f) + .background(color = MomentoTheme.colors.pinkW60, shape = RoundedCornerShape(10.dp)) + ) { + Column( + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxWidth() + ) { + Spacer(Modifier.height(12.dp)) + + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Image( + painter = painterResource(R.drawable.my_stamp), + contentDescription = null + ) + Spacer(Modifier.width(8.dp)) + Text( + text = "수집 스탬프", + style = MomentoTheme.typography.label, + color = MomentoTheme.colors.grayW20 + ) + } + + Spacer(Modifier.height(12.dp)) + + Text( + modifier = Modifier.fillMaxWidth(), + text = "${stampCnt}개", + style = MomentoTheme.typography.label, + color = MomentoTheme.colors.grayW20, + textAlign = TextAlign.End + ) + + Spacer(Modifier.height(12.dp)) + } + } + } +} + +@Composable +fun MypageMenuSection( + onRecordClick: () -> Unit, + onSettingClick: () -> Unit +) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp) + ) { + MypageMenuItem( + text = "내 기록 모아보기", + onClick = { onRecordClick() } + ) + Spacer(Modifier.height(28.dp)) + MypageMenuItem( + text = "북마크", + onClick = {} + ) + Spacer(Modifier.height(28.dp)) + MypageMenuItem( + text = "설정", + onClick = { onSettingClick() } + ) + } +} + +@Composable +fun MypageMenuItem( + text: String, + onClick: () -> Unit +) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { onClick() } + ) { + Text( + text = text, + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.grayW20 + ) + } +} + +@Preview +@Composable +fun MypageScreenPreview() { + DngoTheme { +// MypageScreen() + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/mypage/MypageUiState.kt b/app/src/main/java/com/min/dnapp/presentation/mypage/MypageUiState.kt new file mode 100644 index 0000000..c7740a2 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/mypage/MypageUiState.kt @@ -0,0 +1,11 @@ +package com.min.dnapp.presentation.mypage + +import com.min.dnapp.domain.model.User + +sealed class MypageUiState { + data object Loading : MypageUiState() + data class Success( + val user: User + ) : MypageUiState() + data class Error(val message: String) : MypageUiState() +} diff --git a/app/src/main/java/com/min/dnapp/presentation/mypage/MypageViewModel.kt b/app/src/main/java/com/min/dnapp/presentation/mypage/MypageViewModel.kt new file mode 100644 index 0000000..be60c9a --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/mypage/MypageViewModel.kt @@ -0,0 +1,193 @@ +package com.min.dnapp.presentation.mypage + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.min.dnapp.domain.usecase.GetCurrentUserIdUseCase +import com.min.dnapp.domain.usecase.GetUserDataUseCase +import com.min.dnapp.domain.usecase.UpdateNicknameUseCase +import com.min.dnapp.domain.usecase.UpdateProfileImageUseCase +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class MypageViewModel @Inject constructor( + private val getCurrentUserIdUseCase: GetCurrentUserIdUseCase, + private val getUserDataUseCase: GetUserDataUseCase, + private val updateProfileImageUseCase: UpdateProfileImageUseCase, + private val saveNicknameUseCase: UpdateNicknameUseCase +) : ViewModel() { + + private val _uiState = MutableStateFlow(MypageUiState.Loading) + val uiState: StateFlow = _uiState.asStateFlow() + + // 프로필 이미지 변경 모달창 상태 + private val _showImageUpdateDialog = MutableStateFlow(false) + val showImageUpdateDialog: StateFlow = _showImageUpdateDialog.asStateFlow() + + // 현재 선택된 프로필 이미지 + private val _selectedImage = MutableStateFlow(null) + val selectedImage: StateFlow = _selectedImage.asStateFlow() + + private val _nicknameState = MutableStateFlow(NicknameValidationState()) + val nicknameState: StateFlow = _nicknameState + + private val MIN_LENGTH = 2 + private val MAX_LENGTH = 10 + + // 한글, 영문, 숫자, .(마침표), _(언더만)만 허용 + private val ALLOWED_CHARS_REGEX = Regex("^[가-힣a-zA-Z0-9._]*$") + + init { + loadMyData() + } + + private fun loadMyData() { + viewModelScope.launch { + // 로딩 시작 + _uiState.value = MypageUiState.Loading + + // 사용자 ID 가져오기 + val uid = try { + getCurrentUserIdUseCase() ?: throw Exception("사용자 인증 정보 없음") + } catch (e: Exception) { + _uiState.value = MypageUiState.Error("인증 정보 조회 실패: ${e.message}") + return@launch + } + + // 사용자 정보 가져오기 + try { + val user = getUserDataUseCase(uid) + Log.d("my", "loadMyData - user: $user") + val successState = MypageUiState.Success( + user = user + ) + _uiState.value = successState + } catch (e: Exception) { + Log.e("my", "사용자 정보 조회 실패", e) + } + } + } + + /** + * 프로필 이미지 선택 + */ + fun selectImage(image: ProfileImageType) { + // 선택된 이미지 다시 선택하면 해제 + _selectedImage.value = if (_selectedImage.value == image) null else image + } + + /** + * 프로필 이미지 변경 모달창 열기 + */ + fun openDialog() { + _showImageUpdateDialog.value = true + } + + /** + * 프로필 이미지 변경 모달창 닫기 + */ + fun closeDialog() { + _showImageUpdateDialog.value = false + } + + /** + * 프로필 이미지 변경 + */ + fun updateProfileImage() { + viewModelScope.launch { + selectedImage.value?.let { image -> + val result = updateProfileImageUseCase(image.key) + + result.onSuccess { + closeDialog() + loadMyData() + Log.d("my", "updateProfileImage 성공") + }.onFailure { exception -> + Log.e("my", "updateProfileImage 실패", exception) + } + } + } + } + + /** + * 사용자의 입력에 따라 닉네임 검사 및 업데이트 + */ + fun onNicknameChange(newInput: String) { + + // 10자 초과 입력 차단 + val finalInput = if (newInput.length > MAX_LENGTH) { + newInput.substring(0, MAX_LENGTH) + } else { + newInput + } + Log.d("my", "onNicknameChange finalInput: $finalInput") + + val (isValid, errorMessage) = validateNickname(finalInput) + + _nicknameState.update { + it.copy( + currentNickname = finalInput, + errorMessage = errorMessage, + isValid = isValid + ) + } + } + + private fun validateNickname(nickname: String): Pair { + + // 빈 문자열인 경우 + if (nickname.isEmpty()) { + return Pair(false, null) + } + + // 문자 제한 검사 + if (!nickname.matches(ALLOWED_CHARS_REGEX)) { + return Pair(false, "한글, 영문, 숫자, .(마침표), _(언더바)만 가능합니다") + } + + // 글자수 제한 검사 + if (nickname.length < MIN_LENGTH) { + return Pair(false, "2~10자로 입력해주세요") + } + + // 모든 조건 통과 + return Pair(true, null) + } + + /** + * 새로운 닉네임 저장 + */ + fun updateNickname(onSuccess: () -> Unit) { + viewModelScope.launch { + val nicknameState = _nicknameState.value + + if (!nicknameState.isValid || nicknameState.isSaving) { + return@launch + } + + // 저장 시작 + _nicknameState.update { it.copy(isSaving = true) } + + val newNickname = nicknameState.currentNickname + val result = saveNicknameUseCase(newNickname) + + result.onSuccess { + // 프로필 데이터 갱신 + loadMyData() + // Dialog 닫기 신호 전달 + onSuccess() + Log.d("my", "updateNickname 성공") + }.onFailure { exception -> + Log.e("my", "updateNickname 실패", exception) + } + + _nicknameState.update { it.copy(isSaving = false) } + } + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/mypage/NicknameValidationState.kt b/app/src/main/java/com/min/dnapp/presentation/mypage/NicknameValidationState.kt new file mode 100644 index 0000000..27cf7f9 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/mypage/NicknameValidationState.kt @@ -0,0 +1,8 @@ +package com.min.dnapp.presentation.mypage + +data class NicknameValidationState( + val currentNickname: String = "", + val errorMessage: String? = null, + val isValid: Boolean = false, + val isSaving: Boolean = false +) diff --git a/app/src/main/java/com/min/dnapp/presentation/mypage/ProfileImageType.kt b/app/src/main/java/com/min/dnapp/presentation/mypage/ProfileImageType.kt new file mode 100644 index 0000000..239cef3 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/mypage/ProfileImageType.kt @@ -0,0 +1,18 @@ +package com.min.dnapp.presentation.mypage + +import androidx.annotation.DrawableRes +import com.min.dnapp.R + +enum class ProfileImageType( + val key: String, + @DrawableRes val resId: Int +) { + BOAT("01_boat", R.drawable.logo_profile), + TENT("02_tent", R.drawable.logo_profile2), + SEA("03_sea", R.drawable.logo_profile3), + BAG("04_bag", R.drawable.logo_profile4), + PLANE("05_plane", R.drawable.logo_profile5), + TELESCOPE("06_telescope", R.drawable.logo_profile6), + MAP("07_map", R.drawable.logo_profile7), + MOUNT("08_mount", R.drawable.logo_profile8) +} diff --git a/app/src/main/java/com/min/dnapp/presentation/mypage/SettingScreen.kt b/app/src/main/java/com/min/dnapp/presentation/mypage/SettingScreen.kt new file mode 100644 index 0000000..1046a53 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/mypage/SettingScreen.kt @@ -0,0 +1,154 @@ +package com.min.dnapp.presentation.mypage + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.navigation.NavHostController +import com.min.dnapp.presentation.login.LoginViewModel +import com.min.dnapp.presentation.mypage.component.UserRemoveDialog +import com.min.dnapp.presentation.mypage.component.UserRemoveSuccessDialog +import com.min.dnapp.presentation.ui.icon.AppIcons +import com.min.dnapp.presentation.ui.icon.appicons.Back +import com.min.dnapp.presentation.ui.theme.DngoTheme +import com.min.dnapp.presentation.ui.theme.MomentoTheme + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SettingScreen( + navController: NavHostController, + loginViewModel: LoginViewModel = hiltViewModel() +) { + val showUserRemoveSuccessDialog by loginViewModel.showUserRemoveSuccessDialog.collectAsStateWithLifecycle() + + var showUserRemoveDialog by remember { mutableStateOf(false) } + + Scaffold( + containerColor = MomentoTheme.colors.brownBg, + topBar = { + CenterAlignedTopAppBar( + title = { + Text( + text = "설정", + style = MomentoTheme.typography.title02, + color = MomentoTheme.colors.grayW20 + ) + }, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + containerColor = MomentoTheme.colors.brownBg + ), + navigationIcon = { + Icon( + modifier = Modifier + .clickable { navController.popBackStack() } + .padding(16.dp), + imageVector = AppIcons.Back, + contentDescription = null + ) + } + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues) + .padding(horizontal = 20.dp) + .fillMaxSize() + ) { + Spacer(Modifier.height(20.dp)) + + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { + loginViewModel.onLogoutClicked( + onSuccess = { + // 로그아웃 성공 후 이동 + navController.navigate("login") { + // 최상위 그래프의 ID까지 스택 모두 제거 + popUpTo(navController.graph.id) { inclusive = true } + } + }, + onFailure = {} + ) + } + ) { + Text( + text = "로그아웃", + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.grayW20 + ) + } + + Spacer(Modifier.height(28.dp)) + + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { showUserRemoveDialog = true } + ) { + Text( + text = "탈퇴하기", + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.grayW20 + ) + } + } + } + + // 회원탈퇴 확인 모달창 + if (showUserRemoveDialog) { + UserRemoveDialog( + onDismiss = { showUserRemoveDialog = false }, + onCancel = { showUserRemoveDialog = false }, + onConfirm = { + showUserRemoveDialog = false + loginViewModel.onUnlinkClicked() + } + ) + } + + // 회원탈퇴 완료 모달창 + if (showUserRemoveSuccessDialog) { + UserRemoveSuccessDialog( + onDismiss = { + navController.navigate("login") { + popUpTo(navController.graph.id) { inclusive = true } + } + }, + onConfirm = { + navController.navigate("login") { + popUpTo(navController.graph.id) { inclusive = true } + } + } + ) + } +} + +@Preview +@Composable +fun SettingScreenPreview() { + DngoTheme { +// SettingScreen() + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/mypage/component/NicknameUpdateDialog.kt b/app/src/main/java/com/min/dnapp/presentation/mypage/component/NicknameUpdateDialog.kt new file mode 100644 index 0000000..0ef9266 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/mypage/component/NicknameUpdateDialog.kt @@ -0,0 +1,155 @@ +package com.min.dnapp.presentation.mypage.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.material3.CircularProgressIndicator +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.unit.dp +import androidx.compose.ui.window.Dialog +import com.min.dnapp.presentation.mypage.NicknameValidationState +import com.min.dnapp.presentation.ui.theme.ErrorRed +import com.min.dnapp.presentation.ui.theme.MomentoTheme + +@Composable +fun NicknameUpdateDialog( + state: NicknameValidationState, + onValueChange: (String) -> Unit, + onDismiss: () -> Unit, + onCancel: () -> Unit, + onConfirm: () -> Unit +) { + Dialog( + onDismissRequest = onDismiss + ) { + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(16.dp), + color = MomentoTheme.colors.brownBg + ) { + Box( + contentAlignment = Alignment.Center + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(20.dp), + ) { + Text( + text = "닉네임", + style = MomentoTheme.typography.title02, + color = MomentoTheme.colors.grayW20 + ) + + Spacer(Modifier.height(12.dp)) + + // 닉네임 입력 영역 + Box( + modifier = Modifier + .fillMaxWidth() + .height(40.dp) + .border(width = 1.dp, color = MomentoTheme.colors.grayW90, shape = RoundedCornerShape(5.dp)) + .background(color = MomentoTheme.colors.white) + .padding(start = 12.dp), + contentAlignment = Alignment.CenterStart + ) { + if (state.currentNickname.isEmpty()) { + Text( + text = "새 닉네임을 입력하세요.", + style = MomentoTheme.typography.body02, + color = MomentoTheme.colors.grayW60 + ) + } + BasicTextField( + modifier = Modifier.fillMaxWidth(), + value = state.currentNickname, + onValueChange = { newValue -> + onValueChange(newValue) + }, + textStyle = MomentoTheme.typography.body02, + singleLine = true + ) + } + + // 에러메시지 표시 + if (state.errorMessage != null) { + Spacer(Modifier.height(4.dp)) + Text( + text = state.errorMessage, + style = MomentoTheme.typography.body03, + color = ErrorRed + ) + } + + Spacer(Modifier.height(20.dp)) + + // 버튼 영역 + Row( + modifier = Modifier.fillMaxWidth() + ) { + Box( + modifier = Modifier + .clickable { onCancel() } + .weight(1f) + .background(color = MomentoTheme.colors.white) + .border(width = 1.dp, color = MomentoTheme.colors.grayW60, shape = RoundedCornerShape(5.dp)) + .padding(vertical = 16.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = "취소", + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.grayW20 + ) + } + + Spacer(Modifier.width(12.dp)) + + Box( + modifier = Modifier + .clickable { + if (state.isValid) { onConfirm() } + } + .weight(1f) + .background( + color = if (state.isValid) MomentoTheme.colors.pinkBase else MomentoTheme.colors.grayW80, + shape = RoundedCornerShape(5.dp) + ) + .padding(vertical = 16.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = "저장", + style = MomentoTheme.typography.body01, + color = if (state.isValid) MomentoTheme.colors.grayW20 else MomentoTheme.colors.white + ) + } + } + } + + if (state.isSaving) { + CircularProgressIndicator( + modifier = Modifier.size(40.dp), + color = MomentoTheme.colors.brownW20, + strokeWidth = 4.dp + ) + } + } + } + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/mypage/component/ProfileImageDialog.kt b/app/src/main/java/com/min/dnapp/presentation/mypage/component/ProfileImageDialog.kt new file mode 100644 index 0000000..47a9b24 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/mypage/component/ProfileImageDialog.kt @@ -0,0 +1,113 @@ +package com.min.dnapp.presentation.mypage.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.border +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.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +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.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import com.min.dnapp.presentation.mypage.ProfileImageType +import com.min.dnapp.presentation.ui.component.SelectButton +import com.min.dnapp.presentation.ui.theme.MomentoTheme + +@Composable +fun ProfileImageDialog( + selectedImage: ProfileImageType?, + onImageClick: (ProfileImageType) -> Unit, + onDismiss: () -> Unit, + onConfirm: () -> Unit +) { + // 프로필 이미지 목록 (8개) + val allImages = ProfileImageType.entries.toList() + + Dialog( + onDismissRequest = onDismiss + ) { + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(16.dp), + color = MomentoTheme.colors.white + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 20.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "프로필을 선택해주세요!", + style = MomentoTheme.typography.title01, + color = MomentoTheme.colors.grayW20 + ) + + Spacer(Modifier.height(20.dp)) + + for (idx in 0 until allImages.size step 2) { + + // 처음 Row가 아니면, Row 위에 간격 추가 + if (idx > 0) { + Spacer(Modifier.height(16.dp)) + } + + Row( + horizontalArrangement = Arrangement.spacedBy(20.dp) + ) { + ProfileImageItem( + profileImageType = allImages[idx], + isSelected = selectedImage == allImages[idx], + onClick = onImageClick + ) + ProfileImageItem( + profileImageType = allImages[idx+1], + isSelected = selectedImage == allImages[idx+1], + onClick = onImageClick + ) + } + } + + Spacer(Modifier.height(24.dp)) + + // 확인 버튼 + SelectButton( + enabled = selectedImage != null, + onConfirm = onConfirm + ) + } + } + } +} + +@Composable +fun ProfileImageItem( + profileImageType: ProfileImageType, + isSelected: Boolean, + onClick: (ProfileImageType) -> Unit +) { + val borderColor = if (isSelected) MomentoTheme.colors.pinkBase else Color.Transparent + + Box( + modifier = Modifier + .clickable { onClick(profileImageType) } + .border(width = 3.dp, color = borderColor, shape = RoundedCornerShape(10.dp)) + ) { + Image( + painter = painterResource(profileImageType.resId), + contentDescription = null + ) + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/mypage/component/UserRemoveDialog.kt b/app/src/main/java/com/min/dnapp/presentation/mypage/component/UserRemoveDialog.kt new file mode 100644 index 0000000..f6f24b7 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/mypage/component/UserRemoveDialog.kt @@ -0,0 +1,112 @@ +package com.min.dnapp.presentation.mypage.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +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.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import com.min.dnapp.presentation.ui.theme.DngoTheme +import com.min.dnapp.presentation.ui.theme.MomentoTheme + +@Composable +fun UserRemoveDialog( + onDismiss: () -> Unit, + onCancel: () -> Unit, + onConfirm: () -> Unit +) { + Dialog( + onDismissRequest = onDismiss + ) { + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(16.dp), + color = MomentoTheme.colors.brownBg + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(20.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "정말 탈퇴하시겠어요?", + style = MomentoTheme.typography.title01, + color = MomentoTheme.colors.grayW20 + ) + + Spacer(Modifier.height(8.dp)) + + Text( + text = "회원탈퇴 후 계정 복구가 불가능합니다. \n탈퇴하시겠습니까?", + style = MomentoTheme.typography.body02, + color = MomentoTheme.colors.grayW20, + textAlign = TextAlign.Center, + ) + + Spacer(Modifier.height(20.dp)) + + // 버튼 영역 + Row( + modifier = Modifier.fillMaxWidth() + ) { + Box( + modifier = Modifier + .clickable { onCancel() } + .weight(1f) + .background(color = MomentoTheme.colors.white) + .border(width = 1.dp, color = MomentoTheme.colors.grayW60, shape = RoundedCornerShape(5.dp)) + .padding(vertical = 16.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = "취소", + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.grayW20 + ) + } + + Spacer(Modifier.width(12.dp)) + + Box( + modifier = Modifier + .clickable { onConfirm() } + .weight(1f) + .background(color = MomentoTheme.colors.brownBase, shape = RoundedCornerShape(5.dp)) + .padding(vertical = 16.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = "탈퇴", + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.white + ) + } + } + } + } + } +} + +@Preview +@Composable +fun UserRemoveDialogPreview() { + DngoTheme { +// UserRemoveDialog() + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/mypage/component/UserRemoveSuccessDialog.kt b/app/src/main/java/com/min/dnapp/presentation/mypage/component/UserRemoveSuccessDialog.kt new file mode 100644 index 0000000..3e35271 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/mypage/component/UserRemoveSuccessDialog.kt @@ -0,0 +1,85 @@ +package com.min.dnapp.presentation.mypage.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +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.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import com.min.dnapp.presentation.ui.theme.DngoTheme +import com.min.dnapp.presentation.ui.theme.MomentoTheme + +@Composable +fun UserRemoveSuccessDialog( + onDismiss: () -> Unit, + onConfirm: () -> Unit +) { + Dialog( + onDismissRequest = onDismiss + ) { + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(16.dp), + color = MomentoTheme.colors.brownBg + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(20.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "탈퇴가 완료되었습니다.", + style = MomentoTheme.typography.title01, + color = MomentoTheme.colors.grayW20 + ) + + Spacer(Modifier.height(8.dp)) + + Text( + text = "그동안 모멘토를 이용해주셔서 감사합니다.", + style = MomentoTheme.typography.body02, + color = MomentoTheme.colors.grayW20, + textAlign = TextAlign.Center, + ) + + Spacer(Modifier.height(20.dp)) + + Box( + modifier = Modifier + .clickable { onConfirm() } + .fillMaxWidth() + .background(color = MomentoTheme.colors.brownBase, shape = RoundedCornerShape(5.dp)) + .padding(vertical = 16.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = "확인", + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.white + ) + } + } + } + } +} + +@Preview +@Composable +fun UserRemoveSuccessDialogPreview() { + DngoTheme { +// UserRemoveSuccessDialog() + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/navigation/AppInitHost.kt b/app/src/main/java/com/min/dnapp/presentation/navigation/AppInitHost.kt new file mode 100644 index 0000000..eaa6e44 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/navigation/AppInitHost.kt @@ -0,0 +1,93 @@ +package com.min.dnapp.presentation.navigation + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.navigation +import androidx.navigation.compose.rememberNavController +import com.min.dnapp.presentation.init.AppInitViewModel +import com.min.dnapp.presentation.init.InitRoute +import com.min.dnapp.presentation.init.LoadingScreen +import com.min.dnapp.presentation.init.OnboardingScreen +import com.min.dnapp.presentation.init.OnboardingScreen2 +import com.min.dnapp.presentation.init.OnboardingScreen3 +import com.min.dnapp.presentation.init.ProfileSetupScreen + +@Composable +fun AppInitHost( + appInitViewModel: AppInitViewModel = hiltViewModel(), + onInitComplete: () -> Unit +) { + val uiState by appInitViewModel.uiState.collectAsStateWithLifecycle() + + // nested navigation 담당 (인증 후) + val navController = rememberNavController() + + // 초기 상태에 따라 시작 경로 결정 + val startDestination = when (uiState.route) { + InitRoute.LOADING -> "loading" + InitRoute.ONBOARDING -> "onboarding_flow" + InitRoute.PROFILE_SETUP -> "profile_setup" + InitRoute.MAIN -> { + // 1번만 실행 + LaunchedEffect(Unit) { + onInitComplete() + } + // NavHost 렌더링 중단 + return + } + } + + NavHost( + navController = navController, + startDestination = startDestination + ) { + composable("loading") { + LoadingScreen() + } + + navigation( + startDestination = "onboarding", + route = "onboarding_flow" + ) { + composable("onboarding") { + OnboardingScreen( + onFinish = { + navController.navigate("onboarding2") + } + ) + } + composable("onboarding2") { + OnboardingScreen2( + navController = navController, + onFinish = { + navController.navigate("onboarding3") + } + ) + } + composable("onboarding3") { + OnboardingScreen3( + navController = navController, + onFinish = { + appInitViewModel.onOnboardingFinished() + navController.navigate("profile_setup") { + popUpTo("onboarding_flow") { inclusive = true } + } + } + ) + } + } + + composable("profile_setup") { + ProfileSetupScreen( + onFinish = { + onInitComplete() + } + ) + } + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/profile/ProfileSetupScreen.kt b/app/src/main/java/com/min/dnapp/presentation/profile/ProfileSetupScreen.kt new file mode 100644 index 0000000..cc89bdb --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/profile/ProfileSetupScreen.kt @@ -0,0 +1,171 @@ +package com.min.dnapp.presentation.profile + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +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.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +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.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.min.dnapp.R +import com.min.dnapp.presentation.ui.theme.DngoTheme +import com.min.dnapp.presentation.ui.theme.MomentoTheme + +@Composable +fun ProfileSetupScreen() { + Surface( + modifier = Modifier.fillMaxSize(), + color = MomentoTheme.colors.white + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 20.dp), + verticalArrangement = Arrangement.SpaceBetween + ) { + ProfileSelectSection() + ProfileButtonSection( + onClick = {} + ) + } + } +} + +@Composable +fun ProfileSelectSection() { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(Modifier.height(20.dp)) + + Text( + text = "프로필 설정", + style = MomentoTheme.typography.title01, + color = MomentoTheme.colors.black + ) + + Spacer(Modifier.height(34.dp)) + + // 기본 프로필이미지 목록 (8개) + Column( + modifier = Modifier.fillMaxWidth() + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + Image( + painter = painterResource(R.drawable.logo_profile), + contentDescription = null + ) + Spacer(Modifier.width(20.dp)) + Image( + painter = painterResource(R.drawable.logo_profile2), + contentDescription = null + ) + } + Spacer(Modifier.height(16.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + Image( + painter = painterResource(R.drawable.logo_profile3), + contentDescription = null + ) + Spacer(Modifier.width(20.dp)) + Image( + painter = painterResource(R.drawable.logo_profile4), + contentDescription = null + ) + } + Spacer(Modifier.height(16.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + Image( + painter = painterResource(R.drawable.logo_profile5), + contentDescription = null + ) + Spacer(Modifier.width(20.dp)) + Image( + painter = painterResource(R.drawable.logo_profile6), + contentDescription = null + ) + } + Spacer(Modifier.height(16.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + Image( + painter = painterResource(R.drawable.logo_profile7), + contentDescription = null + ) + Spacer(Modifier.width(20.dp)) + Image( + painter = painterResource(R.drawable.logo_profile8), + contentDescription = null + ) + } + } + + Spacer(Modifier.height(20.dp)) + + Text( + text = "프로필을 설정해주세요!", + style = MomentoTheme.typography.title01, + color = MomentoTheme.colors.black + ) + } +} + +@Composable +fun ProfileButtonSection( + onClick: () -> Unit +) { + Column( + modifier = Modifier.fillMaxWidth() + ) { + Box( + modifier = Modifier + .clickable { onClick() } + .fillMaxWidth() + .background(color = MomentoTheme.colors.brownBase, shape = RoundedCornerShape(10.dp)) + .padding(vertical = 16.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = "선택했어요", + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.white + ) + } + Spacer(Modifier.height(16.dp)) + } +} + +@Preview +@Composable +fun ProfileSetupScreenPreview() { + DngoTheme { + ProfileSetupScreen() + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/ui/component/CustomFloatingActionButton.kt b/app/src/main/java/com/min/dnapp/presentation/ui/component/CustomFloatingActionButton.kt new file mode 100644 index 0000000..fade4ca --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/ui/component/CustomFloatingActionButton.kt @@ -0,0 +1,45 @@ +package com.min.dnapp.presentation.ui.component + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +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.unit.dp +import com.min.dnapp.presentation.ui.icon.AppIcons +import com.min.dnapp.presentation.ui.icon.appicons.Pen +import com.min.dnapp.presentation.ui.theme.MomentoTheme + +@Composable +fun CustomFloatingActionButton( + onClick: () -> Unit +) { + Surface( + onClick = { onClick() }, + color = MomentoTheme.colors.pinkB20, + shape = RoundedCornerShape(50.dp) + ) { + Row( + modifier = Modifier.padding(horizontal = 12.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = AppIcons.Pen, + contentDescription = null, + tint = MomentoTheme.colors.white + ) + Spacer(Modifier.width(4.dp)) + Text( + text = "기록 남기기", + style = MomentoTheme.typography.label, + color = MomentoTheme.colors.white + ) + } + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/ui/component/CustomSnackbar.kt b/app/src/main/java/com/min/dnapp/presentation/ui/component/CustomSnackbar.kt new file mode 100644 index 0000000..323ed7c --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/ui/component/CustomSnackbar.kt @@ -0,0 +1,54 @@ +package com.min.dnapp.presentation.ui.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.SnackbarData +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.min.dnapp.R +import com.min.dnapp.presentation.ui.theme.DngoTheme +import com.min.dnapp.presentation.ui.theme.MomentoTheme + +@Composable +fun CustomSnackbar( + snackbarData: SnackbarData +) { + Row( + modifier = Modifier + .clip(RoundedCornerShape(100.dp)) + .background(color = MomentoTheme.colors.grayW20) + .padding(horizontal = 12.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Image( + painter = painterResource(R.drawable.logo_snackbar), + contentDescription = null + ) + Spacer(Modifier.width(10.dp)) + Text( + text = snackbarData.visuals.message, +// style = MomentoTheme.typography.label, + style = MomentoTheme.typography.body02, + color = MomentoTheme.colors.white + ) + } +} + +@Preview +@Composable +fun CustomSnackbarPreview() { + DngoTheme { +// CustomSnackbar() + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/ui/component/MomentoBottomNav.kt b/app/src/main/java/com/min/dnapp/presentation/ui/component/MomentoBottomNav.kt new file mode 100644 index 0000000..57e75e5 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/ui/component/MomentoBottomNav.kt @@ -0,0 +1,119 @@ +package com.min.dnapp.presentation.ui.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.min.dnapp.R +import com.min.dnapp.presentation.ui.icon.AppIcons +import com.min.dnapp.presentation.ui.icon.appicons.Explore +import com.min.dnapp.presentation.ui.icon.appicons.Home +import com.min.dnapp.presentation.ui.icon.appicons.My +import com.min.dnapp.presentation.ui.theme.DngoTheme +import com.min.dnapp.presentation.ui.theme.MomentoTheme + +data class BottonNavItem( + val icon: ImageVector, + val selectedIcon: Int, + val label: String, + val route: String +) + +@Composable +fun MomentoBottomNav( + currentRoute: String, + onNavItemClick: (String) -> Unit +) { + val items = listOf( + BottonNavItem(AppIcons.Home, R.drawable.ic_home, "Home", "home"), + BottonNavItem(AppIcons.Explore, R.drawable.ic_explore, "Discover", "find"), + BottonNavItem(AppIcons.My, R.drawable.ic_my, "My", "my") + ) + + Column( + modifier = Modifier + .fillMaxWidth() + // bottomNav가 navigationBar 위에 올라가도록 padding 처리 + .navigationBarsPadding() + ) { + HorizontalDivider(thickness = 1.dp, color = MomentoTheme.colors.grayW90) + + Row( + modifier = Modifier + .fillMaxWidth() +// .height(56.dp) + .height(58.dp) + .background(color = MomentoTheme.colors.white), + horizontalArrangement = Arrangement.SpaceAround + ) { + items.forEach { item -> + val selected = currentRoute == item.route + + Column( + modifier = Modifier + .weight(1f) + .fillMaxHeight() + .clickable( + // 리플효과 제거 + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) { + onNavItemClick(item.route) + }, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(Modifier.height(6.dp)) + + // bottomBar 아이콘 + if (selected) { + Image( + painter = painterResource(item.selectedIcon), + contentDescription = null + ) + } else { + Icon( + imageVector = item.icon, + contentDescription = null, + tint = if (selected) MomentoTheme.colors.brownBase else MomentoTheme.colors.grayW60 + ) + } + + Text( + text = item.label, + style = MomentoTheme.typography.label, + color = if (selected) MomentoTheme.colors.brownB40 else MomentoTheme.colors.grayW60 + ) + } + } + } + } +} + +@Preview +@Composable +fun MomentoBottomNavPreview() { + DngoTheme { + MomentoBottomNav( + currentRoute = "home" + ) { } + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/ui/component/SelectButton.kt b/app/src/main/java/com/min/dnapp/presentation/ui/component/SelectButton.kt new file mode 100644 index 0000000..de34924 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/ui/component/SelectButton.kt @@ -0,0 +1,41 @@ +package com.min.dnapp.presentation.ui.component + +import androidx.compose.foundation.background +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.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.min.dnapp.presentation.ui.theme.MomentoTheme + +@Composable +fun SelectButton( + enabled: Boolean, + onConfirm: () -> Unit +) { + val buttonColor = if (enabled) { + MomentoTheme.colors.brownBase + } else { + MomentoTheme.colors.grayW80 + } + + Box( + modifier = Modifier + .clickable(enabled = enabled, onClick = onConfirm) + .fillMaxWidth() + .background(color = buttonColor, shape = RoundedCornerShape(10.dp)) + .padding(vertical = 16.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = "선택했어요", + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.white + ) + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/ui/component/UserBadge.kt b/app/src/main/java/com/min/dnapp/presentation/ui/component/UserBadge.kt new file mode 100644 index 0000000..d6d5d7a --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/ui/component/UserBadge.kt @@ -0,0 +1,47 @@ +package com.min.dnapp.presentation.ui.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +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.unit.dp +import com.min.dnapp.presentation.common.BadgeMapper +import com.min.dnapp.presentation.ui.theme.MomentoTheme + +@Composable +fun UserBadge( + badgeLv: Int, + badgeName: String +) { + // badgeLv을 통해 매핑된 이미지 리소스 ID + val badgeImageResId = BadgeMapper.getBadgeImageResId(badgeLv) + + Row( + modifier = Modifier + .background(color = MomentoTheme.colors.white, shape = RoundedCornerShape(20.dp)) + .padding(horizontal = 12.dp, vertical = 6.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Image( + modifier = Modifier.size(24.dp), + painter = painterResource(badgeImageResId), + contentDescription = null + ) + Spacer(Modifier.width(4.dp)) + Text( + text = badgeName, +// style = MomentoTheme.typography.caption, + style = MomentoTheme.typography.body03, + color = MomentoTheme.colors.grayW20 + ) + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/ui/icon/__AppIcons.kt b/app/src/main/java/com/min/dnapp/presentation/ui/icon/__AppIcons.kt new file mode 100644 index 0000000..5767381 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/ui/icon/__AppIcons.kt @@ -0,0 +1,40 @@ +package com.min.dnapp.presentation.ui.icon + +import androidx.compose.ui.graphics.vector.ImageVector +import com.min.dnapp.presentation.ui.icon.appicons.ArrowRight +import com.min.dnapp.presentation.ui.icon.appicons.Back +import com.min.dnapp.presentation.ui.icon.appicons.Bell +import com.min.dnapp.presentation.ui.icon.appicons.Calendar +import com.min.dnapp.presentation.ui.icon.appicons.Delete +import com.min.dnapp.presentation.ui.icon.appicons.DeleteLine +import com.min.dnapp.presentation.ui.icon.appicons.Explore +import com.min.dnapp.presentation.ui.icon.appicons.Gallery +import com.min.dnapp.presentation.ui.icon.appicons.Home +import com.min.dnapp.presentation.ui.icon.appicons.Kakao +import com.min.dnapp.presentation.ui.icon.appicons.More +import com.min.dnapp.presentation.ui.icon.appicons.My +import com.min.dnapp.presentation.ui.icon.appicons.Pen +import com.min.dnapp.presentation.ui.icon.appicons.PenSmall +import com.min.dnapp.presentation.ui.icon.appicons.RecordBest +import com.min.dnapp.presentation.ui.icon.appicons.RecordBookmark +import com.min.dnapp.presentation.ui.icon.appicons.RecordComment +import com.min.dnapp.presentation.ui.icon.appicons.RecordLike +import com.min.dnapp.presentation.ui.icon.appicons.RecordSurprise +import com.min.dnapp.presentation.ui.icon.appicons.ShareTriangle +import com.min.dnapp.presentation.ui.icon.appicons.Year +import kotlin.collections.List as ____KtList + +public object AppIcons + +private var __AllIcons: ____KtList? = null + +public val AppIcons.AllIcons: ____KtList + get() { + if (__AllIcons != null) { + return __AllIcons!! + } + __AllIcons= listOf(ArrowRight, Back, Bell, Calendar, Delete, DeleteLine, Explore, Gallery, Home, + Kakao, More, My, Pen, PenSmall, RecordBest, RecordBookmark, RecordComment, RecordLike, + RecordSurprise, ShareTriangle, Year) + return __AllIcons!! + } diff --git a/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/ArrowRight.kt b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/ArrowRight.kt new file mode 100644 index 0000000..ee0192f --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/ArrowRight.kt @@ -0,0 +1,114 @@ +package com.min.dnapp.presentation.ui.icon.appicons + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType +import androidx.compose.ui.graphics.PathFillType.Companion.NonZero +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.StrokeCap.Companion.Butt +import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.ImageVector.Builder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.min.dnapp.presentation.ui.icon.AppIcons +import kotlin.Unit + +public val AppIcons.ArrowRight: ImageVector + get() { + if (_arrowRight != null) { + return _arrowRight!! + } + _arrowRight = Builder(name = "ArrowRight", defaultWidth = 25.0.dp, defaultHeight = 24.0.dp, + viewportWidth = 25.0f, viewportHeight = 24.0f).apply { + path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(18.546f, 12.0f) + lineTo(18.9f, 11.646f) + lineTo(19.253f, 12.0f) + lineTo(18.9f, 12.354f) + lineTo(18.546f, 12.0f) + close() + moveTo(6.547f, 12.5f) + curveTo(6.414f, 12.5f, 6.287f, 12.447f, 6.193f, 12.354f) + curveTo(6.099f, 12.26f, 6.047f, 12.133f, 6.047f, 12.0f) + curveTo(6.047f, 11.867f, 6.099f, 11.74f, 6.193f, 11.646f) + curveTo(6.287f, 11.553f, 6.414f, 11.5f, 6.547f, 11.5f) + verticalLineTo(12.5f) + close() + moveTo(14.901f, 7.646f) + lineTo(18.9f, 11.646f) + lineTo(18.192f, 12.354f) + lineTo(14.193f, 8.354f) + lineTo(14.901f, 7.646f) + close() + moveTo(18.9f, 12.354f) + lineTo(14.901f, 16.354f) + lineTo(14.193f, 15.646f) + lineTo(18.192f, 11.646f) + lineTo(18.9f, 12.354f) + close() + moveTo(18.546f, 12.5f) + horizontalLineTo(6.547f) + verticalLineTo(11.5f) + horizontalLineTo(18.546f) + verticalLineTo(12.5f) + close() + } + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, fillAlpha = 0.2f, + strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, + strokeLineMiter = 4.0f, pathFillType = NonZero) { + moveTo(18.546f, 12.0f) + lineTo(18.9f, 11.646f) + lineTo(19.253f, 12.0f) + lineTo(18.9f, 12.354f) + lineTo(18.546f, 12.0f) + close() + moveTo(6.547f, 12.5f) + curveTo(6.414f, 12.5f, 6.287f, 12.447f, 6.193f, 12.354f) + curveTo(6.099f, 12.26f, 6.047f, 12.133f, 6.047f, 12.0f) + curveTo(6.047f, 11.867f, 6.099f, 11.74f, 6.193f, 11.646f) + curveTo(6.287f, 11.553f, 6.414f, 11.5f, 6.547f, 11.5f) + verticalLineTo(12.5f) + close() + moveTo(14.901f, 7.646f) + lineTo(18.9f, 11.646f) + lineTo(18.192f, 12.354f) + lineTo(14.193f, 8.354f) + lineTo(14.901f, 7.646f) + close() + moveTo(18.9f, 12.354f) + lineTo(14.901f, 16.354f) + lineTo(14.193f, 15.646f) + lineTo(18.192f, 11.646f) + lineTo(18.9f, 12.354f) + close() + moveTo(18.546f, 12.5f) + horizontalLineTo(6.547f) + verticalLineTo(11.5f) + horizontalLineTo(18.546f) + verticalLineTo(12.5f) + close() + } + } + .build() + return _arrowRight!! + } + +private var _arrowRight: ImageVector? = null + +@Preview +@Composable +private fun Preview(): Unit { + Box(modifier = Modifier.padding(12.dp)) { + Image(imageVector = AppIcons.ArrowRight, contentDescription = "") + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/Back.kt b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/Back.kt new file mode 100644 index 0000000..6aa3841 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/Back.kt @@ -0,0 +1,114 @@ +package com.min.dnapp.presentation.ui.icon.appicons + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType +import androidx.compose.ui.graphics.PathFillType.Companion.NonZero +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.StrokeCap.Companion.Butt +import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.ImageVector.Builder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.min.dnapp.presentation.ui.icon.AppIcons +import kotlin.Unit + +public val AppIcons.Back: ImageVector + get() { + if (_back != null) { + return _back!! + } + _back = Builder(name = "Back", defaultWidth = 16.0.dp, defaultHeight = 12.0.dp, + viewportWidth = 16.0f, viewportHeight = 12.0f).apply { + path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(1.121f, 6.0f) + lineTo(0.708f, 5.587f) + lineTo(0.296f, 6.0f) + lineTo(0.708f, 6.413f) + lineTo(1.121f, 6.0f) + close() + moveTo(15.121f, 6.583f) + curveTo(15.276f, 6.583f, 15.424f, 6.522f, 15.533f, 6.413f) + curveTo(15.643f, 6.303f, 15.704f, 6.155f, 15.704f, 6.0f) + curveTo(15.704f, 5.845f, 15.643f, 5.697f, 15.533f, 5.588f) + curveTo(15.424f, 5.478f, 15.276f, 5.417f, 15.121f, 5.417f) + verticalLineTo(6.583f) + close() + moveTo(5.374f, 0.92f) + lineTo(0.708f, 5.587f) + lineTo(1.534f, 6.413f) + lineTo(6.2f, 1.746f) + lineTo(5.374f, 0.92f) + close() + moveTo(0.708f, 6.413f) + lineTo(5.374f, 11.08f) + lineTo(6.2f, 10.254f) + lineTo(1.534f, 5.587f) + lineTo(0.708f, 6.413f) + close() + moveTo(1.121f, 6.583f) + horizontalLineTo(15.121f) + verticalLineTo(5.417f) + horizontalLineTo(1.121f) + verticalLineTo(6.583f) + close() + } + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, fillAlpha = 0.2f, + strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, + strokeLineMiter = 4.0f, pathFillType = NonZero) { + moveTo(1.121f, 6.0f) + lineTo(0.708f, 5.587f) + lineTo(0.296f, 6.0f) + lineTo(0.708f, 6.413f) + lineTo(1.121f, 6.0f) + close() + moveTo(15.121f, 6.583f) + curveTo(15.276f, 6.583f, 15.424f, 6.522f, 15.533f, 6.413f) + curveTo(15.643f, 6.303f, 15.704f, 6.155f, 15.704f, 6.0f) + curveTo(15.704f, 5.845f, 15.643f, 5.697f, 15.533f, 5.588f) + curveTo(15.424f, 5.478f, 15.276f, 5.417f, 15.121f, 5.417f) + verticalLineTo(6.583f) + close() + moveTo(5.374f, 0.92f) + lineTo(0.708f, 5.587f) + lineTo(1.534f, 6.413f) + lineTo(6.2f, 1.746f) + lineTo(5.374f, 0.92f) + close() + moveTo(0.708f, 6.413f) + lineTo(5.374f, 11.08f) + lineTo(6.2f, 10.254f) + lineTo(1.534f, 5.587f) + lineTo(0.708f, 6.413f) + close() + moveTo(1.121f, 6.583f) + horizontalLineTo(15.121f) + verticalLineTo(5.417f) + horizontalLineTo(1.121f) + verticalLineTo(6.583f) + close() + } + } + .build() + return _back!! + } + +private var _back: ImageVector? = null + +@Preview +@Composable +private fun Preview(): Unit { + Box(modifier = Modifier.padding(12.dp)) { + Image(imageVector = AppIcons.Back, contentDescription = "") + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/Bell.kt b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/Bell.kt new file mode 100644 index 0000000..222a2e0 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/Bell.kt @@ -0,0 +1,218 @@ +package com.min.dnapp.presentation.ui.icon.appicons + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType +import androidx.compose.ui.graphics.PathFillType.Companion.NonZero +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.StrokeCap.Companion.Butt +import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.ImageVector.Builder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.min.dnapp.presentation.ui.icon.AppIcons +import kotlin.Unit + +public val AppIcons.Bell: ImageVector + get() { + if (_bell != null) { + return _bell!! + } + _bell = Builder(name = "Bell", defaultWidth = 16.0.dp, defaultHeight = 20.0.dp, + viewportWidth = 16.0f, viewportHeight = 20.0f).apply { + path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(10.546f, 16.734f) + curveTo(10.812f, 16.806f, 10.971f, 17.083f, 10.899f, 17.352f) + curveTo(10.729f, 17.997f, 10.352f, 18.567f, 9.828f, 18.974f) + curveTo(9.304f, 19.38f, 8.661f, 19.6f, 8.001f, 19.6f) + curveTo(7.341f, 19.6f, 6.699f, 19.38f, 6.175f, 18.974f) + curveTo(5.651f, 18.567f, 5.274f, 17.997f, 5.103f, 17.352f) + curveTo(5.032f, 17.083f, 5.19f, 16.806f, 5.457f, 16.734f) + curveTo(5.724f, 16.662f, 5.998f, 16.821f, 6.069f, 17.091f) + curveTo(6.183f, 17.521f, 6.434f, 17.9f, 6.783f, 18.171f) + curveTo(7.132f, 18.442f, 7.561f, 18.59f, 8.001f, 18.59f) + curveTo(8.441f, 18.59f, 8.869f, 18.442f, 9.219f, 18.171f) + curveTo(9.568f, 17.9f, 9.82f, 17.521f, 9.934f, 17.091f) + curveTo(10.005f, 16.821f, 10.279f, 16.661f, 10.546f, 16.734f) + close() + moveTo(13.001f, 6.463f) + curveTo(13.001f, 5.123f, 12.474f, 3.838f, 11.537f, 2.891f) + curveTo(10.599f, 1.943f, 9.327f, 1.41f, 8.001f, 1.41f) + curveTo(6.675f, 1.41f, 5.403f, 1.943f, 4.466f, 2.891f) + curveTo(3.528f, 3.838f, 3.001f, 5.123f, 3.001f, 6.463f) + verticalLineTo(8.484f) + curveTo(3.001f, 10.233f, 2.44f, 11.935f, 1.401f, 13.334f) + lineTo(1.4f, 13.335f) + lineTo(1.188f, 13.617f) + curveTo(1.15f, 13.67f, 1.127f, 13.704f, 1.106f, 13.733f) + curveTo(1.097f, 13.746f, 1.09f, 13.755f, 1.085f, 13.762f) + curveTo(1.081f, 13.769f, 1.078f, 13.772f, 1.078f, 13.772f) + lineTo(1.077f, 13.771f) + curveTo(1.031f, 13.844f, 1.006f, 13.929f, 1.002f, 14.016f) + curveTo(0.998f, 14.103f, 1.015f, 14.19f, 1.054f, 14.267f) + curveTo(1.092f, 14.345f, 1.15f, 14.412f, 1.222f, 14.461f) + curveTo(1.293f, 14.509f, 1.375f, 14.538f, 1.46f, 14.545f) + curveTo(1.46f, 14.545f, 1.461f, 14.546f, 1.463f, 14.546f) + curveTo(1.465f, 14.546f, 1.469f, 14.546f, 1.473f, 14.546f) + curveTo(1.481f, 14.546f, 1.492f, 14.547f, 1.508f, 14.547f) + horizontalLineTo(14.493f) + curveTo(14.518f, 14.546f, 14.528f, 14.545f, 14.53f, 14.545f) + lineTo(14.54f, 14.544f) + curveTo(14.625f, 14.537f, 14.708f, 14.509f, 14.779f, 14.46f) + curveTo(14.851f, 14.411f, 14.908f, 14.344f, 14.946f, 14.267f) + curveTo(14.985f, 14.189f, 15.003f, 14.103f, 14.999f, 14.016f) + curveTo(14.995f, 13.931f, 14.968f, 13.848f, 14.924f, 13.775f) + curveTo(14.89f, 13.725f, 14.855f, 13.675f, 14.818f, 13.627f) + lineTo(14.814f, 13.623f) + lineTo(14.601f, 13.334f) + verticalLineTo(13.333f) + curveTo(13.563f, 11.934f, 13.001f, 10.233f, 13.001f, 8.484f) + verticalLineTo(6.463f) + close() + moveTo(14.001f, 8.484f) + curveTo(14.001f, 9.919f, 14.433f, 11.317f, 15.235f, 12.495f) + lineTo(15.401f, 12.728f) + lineTo(15.612f, 13.013f) + lineTo(15.759f, 13.218f) + lineTo(15.764f, 13.226f) + lineTo(15.769f, 13.232f) + curveTo(15.906f, 13.453f, 15.984f, 13.705f, 15.997f, 13.966f) + curveTo(16.01f, 14.226f, 15.956f, 14.485f, 15.841f, 14.719f) + curveTo(15.725f, 14.952f, 15.553f, 15.151f, 15.339f, 15.297f) + curveTo(15.127f, 15.441f, 14.883f, 15.527f, 14.629f, 15.549f) + lineTo(14.63f, 15.55f) + horizontalLineTo(14.627f) + curveTo(14.625f, 15.55f, 14.623f, 15.551f, 14.621f, 15.551f) + lineTo(14.62f, 15.55f) + curveTo(14.55f, 15.557f, 14.46f, 15.557f, 14.358f, 15.557f) + horizontalLineTo(1.644f) + curveTo(1.535f, 15.557f, 1.447f, 15.557f, 1.384f, 15.552f) + horizontalLineTo(1.383f) + curveTo(1.125f, 15.532f, 0.877f, 15.445f, 0.663f, 15.299f) + curveTo(0.449f, 15.153f, 0.275f, 14.953f, 0.159f, 14.719f) + curveTo(0.044f, 14.486f, -0.01f, 14.226f, 0.003f, 13.966f) + curveTo(0.016f, 13.705f, 0.095f, 13.451f, 0.233f, 13.23f) + lineTo(0.234f, 13.229f) + curveTo(0.267f, 13.177f, 0.321f, 13.104f, 0.383f, 13.02f) + lineTo(0.387f, 13.014f) + lineTo(0.602f, 12.727f) + curveTo(1.51f, 11.503f, 2.001f, 10.014f, 2.001f, 8.484f) + verticalLineTo(6.463f) + curveTo(2.001f, 4.855f, 2.634f, 3.313f, 3.759f, 2.176f) + curveTo(4.884f, 1.039f, 6.41f, 0.4f, 8.001f, 0.4f) + curveTo(9.592f, 0.4f, 11.119f, 1.039f, 12.244f, 2.176f) + curveTo(13.369f, 3.313f, 14.001f, 4.855f, 14.001f, 6.463f) + verticalLineTo(8.484f) + close() + } + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, fillAlpha = 0.6f, + strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, + strokeLineMiter = 4.0f, pathFillType = NonZero) { + moveTo(10.546f, 16.734f) + curveTo(10.812f, 16.806f, 10.971f, 17.083f, 10.899f, 17.352f) + curveTo(10.729f, 17.997f, 10.352f, 18.567f, 9.828f, 18.974f) + curveTo(9.304f, 19.38f, 8.661f, 19.6f, 8.001f, 19.6f) + curveTo(7.341f, 19.6f, 6.699f, 19.38f, 6.175f, 18.974f) + curveTo(5.651f, 18.567f, 5.274f, 17.997f, 5.103f, 17.352f) + curveTo(5.032f, 17.083f, 5.19f, 16.806f, 5.457f, 16.734f) + curveTo(5.724f, 16.662f, 5.998f, 16.821f, 6.069f, 17.091f) + curveTo(6.183f, 17.521f, 6.434f, 17.9f, 6.783f, 18.171f) + curveTo(7.132f, 18.442f, 7.561f, 18.59f, 8.001f, 18.59f) + curveTo(8.441f, 18.59f, 8.869f, 18.442f, 9.219f, 18.171f) + curveTo(9.568f, 17.9f, 9.82f, 17.521f, 9.934f, 17.091f) + curveTo(10.005f, 16.821f, 10.279f, 16.661f, 10.546f, 16.734f) + close() + moveTo(13.001f, 6.463f) + curveTo(13.001f, 5.123f, 12.474f, 3.838f, 11.537f, 2.891f) + curveTo(10.599f, 1.943f, 9.327f, 1.41f, 8.001f, 1.41f) + curveTo(6.675f, 1.41f, 5.403f, 1.943f, 4.466f, 2.891f) + curveTo(3.528f, 3.838f, 3.001f, 5.123f, 3.001f, 6.463f) + verticalLineTo(8.484f) + curveTo(3.001f, 10.233f, 2.44f, 11.935f, 1.401f, 13.334f) + lineTo(1.4f, 13.335f) + lineTo(1.188f, 13.617f) + curveTo(1.15f, 13.67f, 1.127f, 13.704f, 1.106f, 13.733f) + curveTo(1.097f, 13.746f, 1.09f, 13.755f, 1.085f, 13.762f) + curveTo(1.081f, 13.769f, 1.078f, 13.772f, 1.078f, 13.772f) + lineTo(1.077f, 13.771f) + curveTo(1.031f, 13.844f, 1.006f, 13.929f, 1.002f, 14.016f) + curveTo(0.998f, 14.103f, 1.015f, 14.19f, 1.054f, 14.267f) + curveTo(1.092f, 14.345f, 1.15f, 14.412f, 1.222f, 14.461f) + curveTo(1.293f, 14.509f, 1.375f, 14.538f, 1.46f, 14.545f) + curveTo(1.46f, 14.545f, 1.461f, 14.546f, 1.463f, 14.546f) + curveTo(1.465f, 14.546f, 1.469f, 14.546f, 1.473f, 14.546f) + curveTo(1.481f, 14.546f, 1.492f, 14.547f, 1.508f, 14.547f) + horizontalLineTo(14.493f) + curveTo(14.518f, 14.546f, 14.528f, 14.545f, 14.53f, 14.545f) + lineTo(14.54f, 14.544f) + curveTo(14.625f, 14.537f, 14.708f, 14.509f, 14.779f, 14.46f) + curveTo(14.851f, 14.411f, 14.908f, 14.344f, 14.946f, 14.267f) + curveTo(14.985f, 14.189f, 15.003f, 14.103f, 14.999f, 14.016f) + curveTo(14.995f, 13.931f, 14.968f, 13.848f, 14.924f, 13.775f) + curveTo(14.89f, 13.725f, 14.855f, 13.675f, 14.818f, 13.627f) + lineTo(14.814f, 13.623f) + lineTo(14.601f, 13.334f) + verticalLineTo(13.333f) + curveTo(13.563f, 11.934f, 13.001f, 10.233f, 13.001f, 8.484f) + verticalLineTo(6.463f) + close() + moveTo(14.001f, 8.484f) + curveTo(14.001f, 9.919f, 14.433f, 11.317f, 15.235f, 12.495f) + lineTo(15.401f, 12.728f) + lineTo(15.612f, 13.013f) + lineTo(15.759f, 13.218f) + lineTo(15.764f, 13.226f) + lineTo(15.769f, 13.232f) + curveTo(15.906f, 13.453f, 15.984f, 13.705f, 15.997f, 13.966f) + curveTo(16.01f, 14.226f, 15.956f, 14.485f, 15.841f, 14.719f) + curveTo(15.725f, 14.952f, 15.553f, 15.151f, 15.339f, 15.297f) + curveTo(15.127f, 15.441f, 14.883f, 15.527f, 14.629f, 15.549f) + lineTo(14.63f, 15.55f) + horizontalLineTo(14.627f) + curveTo(14.625f, 15.55f, 14.623f, 15.551f, 14.621f, 15.551f) + lineTo(14.62f, 15.55f) + curveTo(14.55f, 15.557f, 14.46f, 15.557f, 14.358f, 15.557f) + horizontalLineTo(1.644f) + curveTo(1.535f, 15.557f, 1.447f, 15.557f, 1.384f, 15.552f) + horizontalLineTo(1.383f) + curveTo(1.125f, 15.532f, 0.877f, 15.445f, 0.663f, 15.299f) + curveTo(0.449f, 15.153f, 0.275f, 14.953f, 0.159f, 14.719f) + curveTo(0.044f, 14.486f, -0.01f, 14.226f, 0.003f, 13.966f) + curveTo(0.016f, 13.705f, 0.095f, 13.451f, 0.233f, 13.23f) + lineTo(0.234f, 13.229f) + curveTo(0.267f, 13.177f, 0.321f, 13.104f, 0.383f, 13.02f) + lineTo(0.387f, 13.014f) + lineTo(0.602f, 12.727f) + curveTo(1.51f, 11.503f, 2.001f, 10.014f, 2.001f, 8.484f) + verticalLineTo(6.463f) + curveTo(2.001f, 4.855f, 2.634f, 3.313f, 3.759f, 2.176f) + curveTo(4.884f, 1.039f, 6.41f, 0.4f, 8.001f, 0.4f) + curveTo(9.592f, 0.4f, 11.119f, 1.039f, 12.244f, 2.176f) + curveTo(13.369f, 3.313f, 14.001f, 4.855f, 14.001f, 6.463f) + verticalLineTo(8.484f) + close() + } + } + .build() + return _bell!! + } + +private var _bell: ImageVector? = null + +@Preview +@Composable +private fun Preview(): Unit { + Box(modifier = Modifier.padding(12.dp)) { + Image(imageVector = AppIcons.Bell, contentDescription = "") + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/Calendar.kt b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/Calendar.kt new file mode 100644 index 0000000..9a59e01 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/Calendar.kt @@ -0,0 +1,100 @@ +package com.min.dnapp.presentation.ui.icon.appicons + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType +import androidx.compose.ui.graphics.PathFillType.Companion.NonZero +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.StrokeCap.Companion.Butt +import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.ImageVector.Builder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.min.dnapp.presentation.ui.icon.AppIcons +import kotlin.Unit + +public val AppIcons.Calendar: ImageVector + get() { + if (_calendar != null) { + return _calendar!! + } + _calendar = Builder(name = "Calendar", defaultWidth = 20.0.dp, defaultHeight = 20.0.dp, + viewportWidth = 20.0f, viewportHeight = 20.0f).apply { + path(fill = SolidColor(Color(0xFFA56C48)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(18.0f, 6.0f) + curveTo(18.0f, 5.448f, 17.552f, 5.0f, 17.0f, 5.0f) + horizontalLineTo(3.0f) + curveTo(2.448f, 5.0f, 2.0f, 5.448f, 2.0f, 6.0f) + verticalLineTo(17.0f) + curveTo(2.0f, 17.552f, 2.448f, 18.0f, 3.0f, 18.0f) + horizontalLineTo(17.0f) + curveTo(17.552f, 18.0f, 18.0f, 17.552f, 18.0f, 17.0f) + verticalLineTo(6.0f) + close() + moveTo(20.0f, 17.0f) + curveTo(20.0f, 18.657f, 18.657f, 20.0f, 17.0f, 20.0f) + horizontalLineTo(3.0f) + curveTo(1.343f, 20.0f, 0.0f, 18.657f, 0.0f, 17.0f) + verticalLineTo(6.0f) + curveTo(0.0f, 4.343f, 1.343f, 3.0f, 3.0f, 3.0f) + horizontalLineTo(17.0f) + curveTo(18.657f, 3.0f, 20.0f, 4.343f, 20.0f, 6.0f) + verticalLineTo(17.0f) + close() + } + path(fill = SolidColor(Color(0xFFA56C48)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(1.0f, 8.0f) + curveTo(1.0f, 6.114f, 1.0f, 5.172f, 1.586f, 4.586f) + curveTo(2.172f, 4.0f, 3.114f, 4.0f, 5.0f, 4.0f) + horizontalLineTo(15.0f) + curveTo(16.886f, 4.0f, 17.828f, 4.0f, 18.414f, 4.586f) + curveTo(19.0f, 5.172f, 19.0f, 6.114f, 19.0f, 8.0f) + horizontalLineTo(1.0f) + close() + } + path(fill = SolidColor(Color(0xFFA56C48)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(4.0f, 4.0f) + verticalLineTo(1.0f) + curveTo(4.0f, 0.448f, 4.448f, 0.0f, 5.0f, 0.0f) + curveTo(5.552f, 0.0f, 6.0f, 0.448f, 6.0f, 1.0f) + verticalLineTo(4.0f) + curveTo(6.0f, 4.552f, 5.552f, 5.0f, 5.0f, 5.0f) + curveTo(4.448f, 5.0f, 4.0f, 4.552f, 4.0f, 4.0f) + close() + moveTo(14.0f, 4.0f) + verticalLineTo(1.0f) + curveTo(14.0f, 0.448f, 14.448f, 0.0f, 15.0f, 0.0f) + curveTo(15.552f, 0.0f, 16.0f, 0.448f, 16.0f, 1.0f) + verticalLineTo(4.0f) + curveTo(16.0f, 4.552f, 15.552f, 5.0f, 15.0f, 5.0f) + curveTo(14.448f, 5.0f, 14.0f, 4.552f, 14.0f, 4.0f) + close() + } + } + .build() + return _calendar!! + } + +private var _calendar: ImageVector? = null + +@Preview +@Composable +private fun Preview(): Unit { + Box(modifier = Modifier.padding(12.dp)) { + Image(imageVector = AppIcons.Calendar, contentDescription = "") + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/Delete.kt b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/Delete.kt new file mode 100644 index 0000000..1835935 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/Delete.kt @@ -0,0 +1,92 @@ +package com.min.dnapp.presentation.ui.icon.appicons + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType +import androidx.compose.ui.graphics.PathFillType.Companion.EvenOdd +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.StrokeCap.Companion.Butt +import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.ImageVector.Builder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.min.dnapp.presentation.ui.icon.AppIcons +import kotlin.Unit + +public val AppIcons.Delete: ImageVector + get() { + if (_delete != null) { + return _delete!! + } + _delete = Builder(name = "Delete", defaultWidth = 18.0.dp, defaultHeight = 18.0.dp, + viewportWidth = 18.0f, viewportHeight = 18.0f).apply { + path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = EvenOdd) { + moveTo(9.0f, 0.0f) + curveTo(13.971f, 0.0f, 18.0f, 4.029f, 18.0f, 9.0f) + curveTo(18.0f, 13.971f, 13.971f, 18.0f, 9.0f, 18.0f) + curveTo(4.029f, 18.0f, 0.0f, 13.971f, 0.0f, 9.0f) + curveTo(0.0f, 4.029f, 4.029f, 0.0f, 9.0f, 0.0f) + close() + moveTo(9.0f, 8.293f) + lineTo(6.0f, 5.293f) + lineTo(5.293f, 6.0f) + lineTo(8.293f, 9.0f) + lineTo(5.293f, 12.0f) + lineTo(6.0f, 12.707f) + lineTo(9.0f, 9.707f) + lineTo(12.0f, 12.707f) + lineTo(12.707f, 12.0f) + lineTo(9.707f, 9.0f) + lineTo(12.707f, 6.0f) + lineTo(12.0f, 5.293f) + lineTo(9.0f, 8.293f) + close() + } + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, fillAlpha = 0.6f, + strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, + strokeLineMiter = 4.0f, pathFillType = EvenOdd) { + moveTo(9.0f, 0.0f) + curveTo(13.971f, 0.0f, 18.0f, 4.029f, 18.0f, 9.0f) + curveTo(18.0f, 13.971f, 13.971f, 18.0f, 9.0f, 18.0f) + curveTo(4.029f, 18.0f, 0.0f, 13.971f, 0.0f, 9.0f) + curveTo(0.0f, 4.029f, 4.029f, 0.0f, 9.0f, 0.0f) + close() + moveTo(9.0f, 8.293f) + lineTo(6.0f, 5.293f) + lineTo(5.293f, 6.0f) + lineTo(8.293f, 9.0f) + lineTo(5.293f, 12.0f) + lineTo(6.0f, 12.707f) + lineTo(9.0f, 9.707f) + lineTo(12.0f, 12.707f) + lineTo(12.707f, 12.0f) + lineTo(9.707f, 9.0f) + lineTo(12.707f, 6.0f) + lineTo(12.0f, 5.293f) + lineTo(9.0f, 8.293f) + close() + } + } + .build() + return _delete!! + } + +private var _delete: ImageVector? = null + +@Preview +@Composable +private fun Preview(): Unit { + Box(modifier = Modifier.padding(12.dp)) { + Image(imageVector = AppIcons.Delete, contentDescription = "") + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/DeleteLine.kt b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/DeleteLine.kt new file mode 100644 index 0000000..5a27d43 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/DeleteLine.kt @@ -0,0 +1,88 @@ +package com.min.dnapp.presentation.ui.icon.appicons + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType +import androidx.compose.ui.graphics.PathFillType.Companion.NonZero +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.StrokeCap.Companion.Butt +import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.ImageVector.Builder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.min.dnapp.presentation.ui.icon.AppIcons +import kotlin.Unit + +public val AppIcons.DeleteLine: ImageVector + get() { + if (_deleteLine != null) { + return _deleteLine!! + } + _deleteLine = Builder(name = "DeleteLine", defaultWidth = 16.0.dp, defaultHeight = 16.0.dp, + viewportWidth = 16.0f, viewportHeight = 16.0f).apply { + path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(11.764f, 3.764f) + curveTo(11.895f, 3.634f, 12.106f, 3.634f, 12.236f, 3.764f) + curveTo(12.366f, 3.894f, 12.366f, 4.105f, 12.236f, 4.235f) + lineTo(8.471f, 8.0f) + lineTo(12.236f, 11.764f) + curveTo(12.366f, 11.894f, 12.366f, 12.105f, 12.236f, 12.236f) + curveTo(12.106f, 12.366f, 11.895f, 12.366f, 11.764f, 12.236f) + lineTo(8.0f, 8.471f) + lineTo(4.236f, 12.236f) + curveTo(4.106f, 12.366f, 3.895f, 12.366f, 3.764f, 12.236f) + curveTo(3.634f, 12.105f, 3.634f, 11.894f, 3.764f, 11.764f) + lineTo(7.529f, 8.0f) + lineTo(3.764f, 4.235f) + curveTo(3.634f, 4.105f, 3.634f, 3.894f, 3.764f, 3.764f) + curveTo(3.895f, 3.634f, 4.106f, 3.634f, 4.236f, 3.764f) + lineTo(8.0f, 7.528f) + lineTo(11.764f, 3.764f) + close() + } + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, fillAlpha = 0.2f, + strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, + strokeLineMiter = 4.0f, pathFillType = NonZero) { + moveTo(11.764f, 3.764f) + curveTo(11.895f, 3.634f, 12.106f, 3.634f, 12.236f, 3.764f) + curveTo(12.366f, 3.894f, 12.366f, 4.105f, 12.236f, 4.235f) + lineTo(8.471f, 8.0f) + lineTo(12.236f, 11.764f) + curveTo(12.366f, 11.894f, 12.366f, 12.105f, 12.236f, 12.236f) + curveTo(12.106f, 12.366f, 11.895f, 12.366f, 11.764f, 12.236f) + lineTo(8.0f, 8.471f) + lineTo(4.236f, 12.236f) + curveTo(4.106f, 12.366f, 3.895f, 12.366f, 3.764f, 12.236f) + curveTo(3.634f, 12.105f, 3.634f, 11.894f, 3.764f, 11.764f) + lineTo(7.529f, 8.0f) + lineTo(3.764f, 4.235f) + curveTo(3.634f, 4.105f, 3.634f, 3.894f, 3.764f, 3.764f) + curveTo(3.895f, 3.634f, 4.106f, 3.634f, 4.236f, 3.764f) + lineTo(8.0f, 7.528f) + lineTo(11.764f, 3.764f) + close() + } + } + .build() + return _deleteLine!! + } + +private var _deleteLine: ImageVector? = null + +@Preview +@Composable +private fun Preview(): Unit { + Box(modifier = Modifier.padding(12.dp)) { + Image(imageVector = AppIcons.DeleteLine, contentDescription = "") + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/Explore.kt b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/Explore.kt new file mode 100644 index 0000000..1f15b2a --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/Explore.kt @@ -0,0 +1,350 @@ +package com.min.dnapp.presentation.ui.icon.appicons + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType +import androidx.compose.ui.graphics.PathFillType.Companion.NonZero +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.StrokeCap.Companion.Butt +import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.ImageVector.Builder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.min.dnapp.presentation.ui.icon.AppIcons +import kotlin.Unit + +public val AppIcons.Explore: ImageVector + get() { + if (_explore != null) { + return _explore!! + } + _explore = Builder(name = "Explore", defaultWidth = 28.0.dp, defaultHeight = 29.0.dp, + viewportWidth = 28.0f, viewportHeight = 29.0f).apply { + path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(16.941f, 9.833f) + curveTo(16.941f, 8.222f, 15.635f, 6.917f, 14.024f, 6.917f) + curveTo(12.413f, 6.917f, 11.107f, 8.222f, 11.107f, 9.833f) + curveTo(11.107f, 11.444f, 12.413f, 12.75f, 14.024f, 12.75f) + curveTo(15.635f, 12.75f, 16.941f, 11.444f, 16.941f, 9.833f) + close() + moveTo(18.107f, 9.833f) + curveTo(18.107f, 12.089f, 16.279f, 13.917f, 14.024f, 13.917f) + curveTo(11.769f, 13.917f, 9.941f, 12.089f, 9.941f, 9.833f) + curveTo(9.941f, 7.578f, 11.769f, 5.75f, 14.024f, 5.75f) + curveTo(16.279f, 5.75f, 18.107f, 7.578f, 18.107f, 9.833f) + close() + } + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, fillAlpha = 0.6f, + strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, + strokeLineMiter = 4.0f, pathFillType = NonZero) { + moveTo(16.941f, 9.833f) + curveTo(16.941f, 8.222f, 15.635f, 6.917f, 14.024f, 6.917f) + curveTo(12.413f, 6.917f, 11.107f, 8.222f, 11.107f, 9.833f) + curveTo(11.107f, 11.444f, 12.413f, 12.75f, 14.024f, 12.75f) + curveTo(15.635f, 12.75f, 16.941f, 11.444f, 16.941f, 9.833f) + close() + moveTo(18.107f, 9.833f) + curveTo(18.107f, 12.089f, 16.279f, 13.917f, 14.024f, 13.917f) + curveTo(11.769f, 13.917f, 9.941f, 12.089f, 9.941f, 9.833f) + curveTo(9.941f, 7.578f, 11.769f, 5.75f, 14.024f, 5.75f) + curveTo(16.279f, 5.75f, 18.107f, 7.578f, 18.107f, 9.833f) + close() + } + path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(8.573f, 8.094f) + curveTo(8.955f, 8.143f, 9.322f, 8.268f, 9.656f, 8.461f) + curveTo(9.989f, 8.653f, 10.281f, 8.909f, 10.514f, 9.215f) + curveTo(10.748f, 9.52f, 10.919f, 9.87f, 11.017f, 10.241f) + curveTo(11.115f, 10.613f, 11.139f, 11.001f, 11.087f, 11.382f) + curveTo(11.034f, 11.763f, 10.907f, 12.13f, 10.713f, 12.462f) + curveTo(10.323f, 13.126f, 9.687f, 13.609f, 8.942f, 13.806f) + curveTo(8.198f, 14.003f, 7.406f, 13.897f, 6.739f, 13.512f) + curveTo(6.072f, 13.127f, 5.584f, 12.494f, 5.382f, 11.751f) + curveTo(5.18f, 11.008f, 5.282f, 10.215f, 5.662f, 9.545f) + curveTo(5.852f, 9.211f, 6.107f, 8.917f, 6.411f, 8.682f) + curveTo(6.715f, 8.446f, 7.062f, 8.272f, 7.433f, 8.171f) + curveTo(7.804f, 8.07f, 8.192f, 8.044f, 8.573f, 8.094f) + close() + moveTo(20.24f, 8.094f) + curveTo(20.621f, 8.143f, 20.989f, 8.268f, 21.322f, 8.461f) + curveTo(21.655f, 8.653f, 21.948f, 8.909f, 22.181f, 9.215f) + curveTo(22.415f, 9.52f, 22.585f, 9.87f, 22.684f, 10.241f) + curveTo(22.782f, 10.613f, 22.805f, 11.001f, 22.753f, 11.382f) + curveTo(22.701f, 11.763f, 22.574f, 12.13f, 22.379f, 12.462f) + curveTo(21.99f, 13.126f, 21.353f, 13.609f, 20.609f, 13.806f) + curveTo(19.865f, 14.003f, 19.072f, 13.897f, 18.406f, 13.512f) + curveTo(17.739f, 13.127f, 17.25f, 12.494f, 17.049f, 11.751f) + curveTo(16.847f, 11.008f, 16.948f, 10.215f, 17.329f, 9.545f) + curveTo(17.519f, 9.211f, 17.773f, 8.917f, 18.077f, 8.682f) + curveTo(18.381f, 8.446f, 18.728f, 8.272f, 19.099f, 8.171f) + curveTo(19.471f, 8.07f, 19.858f, 8.044f, 20.24f, 8.094f) + close() + moveTo(8.423f, 9.251f) + curveTo(8.194f, 9.222f, 7.962f, 9.238f, 7.739f, 9.298f) + curveTo(7.516f, 9.358f, 7.307f, 9.462f, 7.125f, 9.603f) + curveTo(6.943f, 9.745f, 6.79f, 9.921f, 6.676f, 10.122f) + curveTo(6.448f, 10.523f, 6.388f, 10.999f, 6.509f, 11.444f) + curveTo(6.63f, 11.89f, 6.922f, 12.271f, 7.322f, 12.502f) + curveTo(7.722f, 12.733f, 8.198f, 12.796f, 8.645f, 12.678f) + curveTo(9.091f, 12.56f, 9.473f, 12.27f, 9.707f, 11.872f) + curveTo(9.823f, 11.673f, 9.9f, 11.452f, 9.931f, 11.223f) + curveTo(9.962f, 10.995f, 9.948f, 10.762f, 9.889f, 10.539f) + curveTo(9.83f, 10.316f, 9.727f, 10.107f, 9.587f, 9.923f) + curveTo(9.447f, 9.74f, 9.272f, 9.587f, 9.072f, 9.471f) + curveTo(8.872f, 9.356f, 8.652f, 9.281f, 8.423f, 9.251f) + close() + moveTo(20.089f, 9.251f) + curveTo(19.861f, 9.222f, 19.628f, 9.238f, 19.406f, 9.298f) + curveTo(19.183f, 9.358f, 18.974f, 9.462f, 18.792f, 9.603f) + curveTo(18.609f, 9.745f, 18.457f, 9.921f, 18.343f, 10.122f) + curveTo(18.115f, 10.523f, 18.054f, 10.999f, 18.175f, 11.444f) + curveTo(18.296f, 11.89f, 18.589f, 12.271f, 18.989f, 12.502f) + curveTo(19.389f, 12.733f, 19.865f, 12.796f, 20.312f, 12.678f) + curveTo(20.758f, 12.56f, 21.14f, 12.27f, 21.373f, 11.872f) + curveTo(21.49f, 11.673f, 21.567f, 11.452f, 21.598f, 11.223f) + curveTo(21.629f, 10.995f, 21.615f, 10.762f, 21.556f, 10.539f) + curveTo(21.497f, 10.316f, 21.394f, 10.107f, 21.254f, 9.923f) + curveTo(21.114f, 9.74f, 20.939f, 9.587f, 20.739f, 9.471f) + curveTo(20.539f, 9.356f, 20.318f, 9.281f, 20.089f, 9.251f) + close() + } + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, fillAlpha = 0.6f, + strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, + strokeLineMiter = 4.0f, pathFillType = NonZero) { + moveTo(8.573f, 8.094f) + curveTo(8.955f, 8.143f, 9.322f, 8.268f, 9.656f, 8.461f) + curveTo(9.989f, 8.653f, 10.281f, 8.909f, 10.514f, 9.215f) + curveTo(10.748f, 9.52f, 10.919f, 9.87f, 11.017f, 10.241f) + curveTo(11.115f, 10.613f, 11.139f, 11.001f, 11.087f, 11.382f) + curveTo(11.034f, 11.763f, 10.907f, 12.13f, 10.713f, 12.462f) + curveTo(10.323f, 13.126f, 9.687f, 13.609f, 8.942f, 13.806f) + curveTo(8.198f, 14.003f, 7.406f, 13.897f, 6.739f, 13.512f) + curveTo(6.072f, 13.127f, 5.584f, 12.494f, 5.382f, 11.751f) + curveTo(5.18f, 11.008f, 5.282f, 10.215f, 5.662f, 9.545f) + curveTo(5.852f, 9.211f, 6.107f, 8.917f, 6.411f, 8.682f) + curveTo(6.715f, 8.446f, 7.062f, 8.272f, 7.433f, 8.171f) + curveTo(7.804f, 8.07f, 8.192f, 8.044f, 8.573f, 8.094f) + close() + moveTo(20.24f, 8.094f) + curveTo(20.621f, 8.143f, 20.989f, 8.268f, 21.322f, 8.461f) + curveTo(21.655f, 8.653f, 21.948f, 8.909f, 22.181f, 9.215f) + curveTo(22.415f, 9.52f, 22.585f, 9.87f, 22.684f, 10.241f) + curveTo(22.782f, 10.613f, 22.805f, 11.001f, 22.753f, 11.382f) + curveTo(22.701f, 11.763f, 22.574f, 12.13f, 22.379f, 12.462f) + curveTo(21.99f, 13.126f, 21.353f, 13.609f, 20.609f, 13.806f) + curveTo(19.865f, 14.003f, 19.072f, 13.897f, 18.406f, 13.512f) + curveTo(17.739f, 13.127f, 17.25f, 12.494f, 17.049f, 11.751f) + curveTo(16.847f, 11.008f, 16.948f, 10.215f, 17.329f, 9.545f) + curveTo(17.519f, 9.211f, 17.773f, 8.917f, 18.077f, 8.682f) + curveTo(18.381f, 8.446f, 18.728f, 8.272f, 19.099f, 8.171f) + curveTo(19.471f, 8.07f, 19.858f, 8.044f, 20.24f, 8.094f) + close() + moveTo(8.423f, 9.251f) + curveTo(8.194f, 9.222f, 7.962f, 9.238f, 7.739f, 9.298f) + curveTo(7.516f, 9.358f, 7.307f, 9.462f, 7.125f, 9.603f) + curveTo(6.943f, 9.745f, 6.79f, 9.921f, 6.676f, 10.122f) + curveTo(6.448f, 10.523f, 6.388f, 10.999f, 6.509f, 11.444f) + curveTo(6.63f, 11.89f, 6.922f, 12.271f, 7.322f, 12.502f) + curveTo(7.722f, 12.733f, 8.198f, 12.796f, 8.645f, 12.678f) + curveTo(9.091f, 12.56f, 9.473f, 12.27f, 9.707f, 11.872f) + curveTo(9.823f, 11.673f, 9.9f, 11.452f, 9.931f, 11.223f) + curveTo(9.962f, 10.995f, 9.948f, 10.762f, 9.889f, 10.539f) + curveTo(9.83f, 10.316f, 9.727f, 10.107f, 9.587f, 9.923f) + curveTo(9.447f, 9.74f, 9.272f, 9.587f, 9.072f, 9.471f) + curveTo(8.872f, 9.356f, 8.652f, 9.281f, 8.423f, 9.251f) + close() + moveTo(20.089f, 9.251f) + curveTo(19.861f, 9.222f, 19.628f, 9.238f, 19.406f, 9.298f) + curveTo(19.183f, 9.358f, 18.974f, 9.462f, 18.792f, 9.603f) + curveTo(18.609f, 9.745f, 18.457f, 9.921f, 18.343f, 10.122f) + curveTo(18.115f, 10.523f, 18.054f, 10.999f, 18.175f, 11.444f) + curveTo(18.296f, 11.89f, 18.589f, 12.271f, 18.989f, 12.502f) + curveTo(19.389f, 12.733f, 19.865f, 12.796f, 20.312f, 12.678f) + curveTo(20.758f, 12.56f, 21.14f, 12.27f, 21.373f, 11.872f) + curveTo(21.49f, 11.673f, 21.567f, 11.452f, 21.598f, 11.223f) + curveTo(21.629f, 10.995f, 21.615f, 10.762f, 21.556f, 10.539f) + curveTo(21.497f, 10.316f, 21.394f, 10.107f, 21.254f, 9.923f) + curveTo(21.114f, 9.74f, 20.939f, 9.587f, 20.739f, 9.471f) + curveTo(20.539f, 9.356f, 20.318f, 9.281f, 20.089f, 9.251f) + close() + } + path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(14.024f, 16.25f) + curveTo(16.265f, 16.25f, 17.765f, 17.058f, 18.742f, 18.144f) + curveTo(19.7f, 19.208f, 20.113f, 20.492f, 20.293f, 21.396f) + horizontalLineTo(20.292f) + curveTo(20.505f, 22.455f, 19.633f, 23.25f, 18.69f, 23.25f) + horizontalLineTo(9.357f) + curveTo(8.416f, 23.25f, 7.542f, 22.454f, 7.755f, 21.395f) + curveTo(7.936f, 20.49f, 8.349f, 19.208f, 9.307f, 18.144f) + curveTo(10.283f, 17.059f, 11.783f, 16.25f, 14.024f, 16.25f) + close() + moveTo(14.024f, 17.417f) + curveTo(12.098f, 17.417f, 10.919f, 18.096f, 10.174f, 18.924f) + curveTo(9.41f, 19.772f, 9.058f, 20.827f, 8.899f, 21.624f) + verticalLineTo(21.625f) + curveTo(8.874f, 21.748f, 8.909f, 21.85f, 8.983f, 21.931f) + curveTo(9.062f, 22.017f, 9.193f, 22.083f, 9.357f, 22.083f) + horizontalLineTo(18.69f) + curveTo(18.855f, 22.083f, 18.986f, 22.016f, 19.065f, 21.931f) + curveTo(19.139f, 21.85f, 19.174f, 21.748f, 19.149f, 21.625f) + lineTo(19.148f, 21.624f) + curveTo(18.989f, 20.828f, 18.638f, 19.772f, 17.875f, 18.924f) + curveTo(17.129f, 18.096f, 15.95f, 17.417f, 14.024f, 17.417f) + close() + } + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, fillAlpha = 0.6f, + strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, + strokeLineMiter = 4.0f, pathFillType = NonZero) { + moveTo(14.024f, 16.25f) + curveTo(16.265f, 16.25f, 17.765f, 17.058f, 18.742f, 18.144f) + curveTo(19.7f, 19.208f, 20.113f, 20.492f, 20.293f, 21.396f) + horizontalLineTo(20.292f) + curveTo(20.505f, 22.455f, 19.633f, 23.25f, 18.69f, 23.25f) + horizontalLineTo(9.357f) + curveTo(8.416f, 23.25f, 7.542f, 22.454f, 7.755f, 21.395f) + curveTo(7.936f, 20.49f, 8.349f, 19.208f, 9.307f, 18.144f) + curveTo(10.283f, 17.059f, 11.783f, 16.25f, 14.024f, 16.25f) + close() + moveTo(14.024f, 17.417f) + curveTo(12.098f, 17.417f, 10.919f, 18.096f, 10.174f, 18.924f) + curveTo(9.41f, 19.772f, 9.058f, 20.827f, 8.899f, 21.624f) + verticalLineTo(21.625f) + curveTo(8.874f, 21.748f, 8.909f, 21.85f, 8.983f, 21.931f) + curveTo(9.062f, 22.017f, 9.193f, 22.083f, 9.357f, 22.083f) + horizontalLineTo(18.69f) + curveTo(18.855f, 22.083f, 18.986f, 22.016f, 19.065f, 21.931f) + curveTo(19.139f, 21.85f, 19.174f, 21.748f, 19.149f, 21.625f) + lineTo(19.148f, 21.624f) + curveTo(18.989f, 20.828f, 18.638f, 19.772f, 17.875f, 18.924f) + curveTo(17.129f, 18.096f, 15.95f, 17.417f, 14.024f, 17.417f) + close() + } + path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(8.166f, 15.551f) + curveTo(9.224f, 15.538f, 10.249f, 15.925f, 11.034f, 16.634f) + lineTo(11.597f, 17.144f) + lineTo(10.96f, 17.556f) + curveTo(9.382f, 18.579f, 8.524f, 20.406f, 8.067f, 22.117f) + lineTo(7.952f, 22.55f) + horizontalLineTo(4.789f) + curveTo(3.772f, 22.55f, 2.9f, 21.649f, 3.17f, 20.562f) + lineTo(3.172f, 20.56f) + curveTo(3.395f, 19.67f, 3.812f, 18.441f, 4.576f, 17.424f) + curveTo(5.351f, 16.392f, 6.506f, 15.554f, 8.158f, 15.551f) + curveTo(8.16f, 15.551f, 8.162f, 15.551f, 8.164f, 15.551f) + lineTo(8.166f, 15.55f) + verticalLineTo(15.551f) + close() + moveTo(19.833f, 15.55f) + curveTo(21.49f, 15.55f, 22.648f, 16.39f, 23.424f, 17.424f) + curveTo(24.093f, 18.314f, 24.496f, 19.367f, 24.736f, 20.212f) + lineTo(24.829f, 20.56f) + verticalLineTo(20.562f) + curveTo(25.1f, 21.649f, 24.228f, 22.55f, 23.211f, 22.55f) + horizontalLineTo(20.049f) + lineTo(19.933f, 22.117f) + curveTo(19.477f, 20.407f, 18.619f, 18.579f, 17.041f, 17.556f) + lineTo(16.397f, 17.139f) + lineTo(16.971f, 16.63f) + curveTo(17.7f, 15.985f, 18.644f, 15.55f, 19.833f, 15.55f) + close() + moveTo(8.166f, 16.716f) + curveTo(6.967f, 16.717f, 6.125f, 17.303f, 5.508f, 18.125f) + curveTo(4.879f, 18.963f, 4.51f, 20.019f, 4.303f, 20.844f) + curveTo(4.241f, 21.096f, 4.426f, 21.383f, 4.789f, 21.383f) + horizontalLineTo(7.065f) + curveTo(7.513f, 19.898f, 8.29f, 18.249f, 9.652f, 17.081f) + curveTo(9.201f, 16.839f, 8.694f, 16.709f, 8.174f, 16.716f) + horizontalLineTo(8.166f) + close() + moveTo(19.833f, 16.716f) + curveTo(19.256f, 16.716f, 18.767f, 16.854f, 18.347f, 17.081f) + curveTo(19.71f, 18.249f, 20.488f, 19.897f, 20.937f, 21.383f) + horizontalLineTo(23.211f) + curveTo(23.575f, 21.383f, 23.76f, 21.096f, 23.698f, 20.844f) + lineTo(23.612f, 20.525f) + curveTo(23.394f, 19.759f, 23.042f, 18.858f, 22.491f, 18.125f) + curveTo(21.874f, 17.303f, 21.032f, 16.716f, 19.833f, 16.716f) + close() + } + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, fillAlpha = 0.6f, + strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, + strokeLineMiter = 4.0f, pathFillType = NonZero) { + moveTo(8.166f, 15.551f) + curveTo(9.224f, 15.538f, 10.249f, 15.925f, 11.034f, 16.634f) + lineTo(11.597f, 17.144f) + lineTo(10.96f, 17.556f) + curveTo(9.382f, 18.579f, 8.524f, 20.406f, 8.067f, 22.117f) + lineTo(7.952f, 22.55f) + horizontalLineTo(4.789f) + curveTo(3.772f, 22.55f, 2.9f, 21.649f, 3.17f, 20.562f) + lineTo(3.172f, 20.56f) + curveTo(3.395f, 19.67f, 3.812f, 18.441f, 4.576f, 17.424f) + curveTo(5.351f, 16.392f, 6.506f, 15.554f, 8.158f, 15.551f) + curveTo(8.16f, 15.551f, 8.162f, 15.551f, 8.164f, 15.551f) + lineTo(8.166f, 15.55f) + verticalLineTo(15.551f) + close() + moveTo(19.833f, 15.55f) + curveTo(21.49f, 15.55f, 22.648f, 16.39f, 23.424f, 17.424f) + curveTo(24.093f, 18.314f, 24.496f, 19.367f, 24.736f, 20.212f) + lineTo(24.829f, 20.56f) + verticalLineTo(20.562f) + curveTo(25.1f, 21.649f, 24.228f, 22.55f, 23.211f, 22.55f) + horizontalLineTo(20.049f) + lineTo(19.933f, 22.117f) + curveTo(19.477f, 20.407f, 18.619f, 18.579f, 17.041f, 17.556f) + lineTo(16.397f, 17.139f) + lineTo(16.971f, 16.63f) + curveTo(17.7f, 15.985f, 18.644f, 15.55f, 19.833f, 15.55f) + close() + moveTo(8.166f, 16.716f) + curveTo(6.967f, 16.717f, 6.125f, 17.303f, 5.508f, 18.125f) + curveTo(4.879f, 18.963f, 4.51f, 20.019f, 4.303f, 20.844f) + curveTo(4.241f, 21.096f, 4.426f, 21.383f, 4.789f, 21.383f) + horizontalLineTo(7.065f) + curveTo(7.513f, 19.898f, 8.29f, 18.249f, 9.652f, 17.081f) + curveTo(9.201f, 16.839f, 8.694f, 16.709f, 8.174f, 16.716f) + horizontalLineTo(8.166f) + close() + moveTo(19.833f, 16.716f) + curveTo(19.256f, 16.716f, 18.767f, 16.854f, 18.347f, 17.081f) + curveTo(19.71f, 18.249f, 20.488f, 19.897f, 20.937f, 21.383f) + horizontalLineTo(23.211f) + curveTo(23.575f, 21.383f, 23.76f, 21.096f, 23.698f, 20.844f) + lineTo(23.612f, 20.525f) + curveTo(23.394f, 19.759f, 23.042f, 18.858f, 22.491f, 18.125f) + curveTo(21.874f, 17.303f, 21.032f, 16.716f, 19.833f, 16.716f) + close() + } + } + .build() + return _explore!! + } + +private var _explore: ImageVector? = null + +@Preview +@Composable +private fun Preview(): Unit { + Box(modifier = Modifier.padding(12.dp)) { + Image(imageVector = AppIcons.Explore, contentDescription = "") + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/Gallery.kt b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/Gallery.kt new file mode 100644 index 0000000..c11e911 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/Gallery.kt @@ -0,0 +1,305 @@ +package com.min.dnapp.presentation.ui.icon.appicons + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType +import androidx.compose.ui.graphics.PathFillType.Companion.EvenOdd +import androidx.compose.ui.graphics.PathFillType.Companion.NonZero +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.StrokeCap.Companion.Butt +import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.ImageVector.Builder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.min.dnapp.presentation.ui.icon.AppIcons +import kotlin.Unit + +public val AppIcons.Gallery: ImageVector + get() { + if (_gallery != null) { + return _gallery!! + } + _gallery = Builder(name = "Gallery", defaultWidth = 22.0.dp, defaultHeight = 22.0.dp, + viewportWidth = 22.0f, viewportHeight = 22.0f).apply { + path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(11.25f, 0.75f) + curveTo(13.121f, 0.75f, 14.576f, 0.749f, 15.706f, 0.901f) + curveTo(16.851f, 1.055f, 17.738f, 1.374f, 18.432f, 2.068f) + curveTo(19.125f, 2.763f, 19.444f, 3.65f, 19.599f, 4.795f) + curveTo(19.751f, 5.925f, 19.75f, 7.379f, 19.75f, 9.25f) + verticalLineTo(11.25f) + curveTo(19.75f, 11.293f, 19.749f, 11.336f, 19.749f, 11.379f) + curveTo(19.434f, 11.153f, 19.1f, 10.958f, 18.75f, 10.797f) + verticalLineTo(9.25f) + curveTo(18.75f, 7.35f, 18.749f, 5.976f, 18.607f, 4.928f) + curveTo(18.468f, 3.894f, 18.202f, 3.253f, 17.725f, 2.775f) + curveTo(17.247f, 2.298f, 16.606f, 2.031f, 15.572f, 1.892f) + curveTo(14.524f, 1.751f, 13.15f, 1.75f, 11.25f, 1.75f) + horizontalLineTo(9.25f) + curveTo(7.35f, 1.75f, 5.976f, 1.751f, 4.928f, 1.893f) + curveTo(3.894f, 2.032f, 3.253f, 2.298f, 2.775f, 2.775f) + curveTo(2.298f, 3.252f, 2.031f, 3.894f, 1.892f, 4.928f) + curveTo(1.751f, 5.976f, 1.75f, 7.35f, 1.75f, 9.25f) + verticalLineTo(11.25f) + curveTo(1.75f, 13.15f, 1.751f, 14.524f, 1.893f, 15.572f) + curveTo(2.032f, 16.606f, 2.298f, 17.247f, 2.775f, 17.725f) + curveTo(3.252f, 18.202f, 3.894f, 18.469f, 4.928f, 18.608f) + curveTo(5.976f, 18.749f, 7.35f, 18.75f, 9.25f, 18.75f) + horizontalLineTo(10.797f) + curveTo(10.958f, 19.1f, 11.153f, 19.434f, 11.379f, 19.749f) + curveTo(11.336f, 19.749f, 11.293f, 19.75f, 11.25f, 19.75f) + horizontalLineTo(9.25f) + curveTo(7.379f, 19.75f, 5.924f, 19.751f, 4.794f, 19.599f) + curveTo(3.65f, 19.445f, 2.762f, 19.126f, 2.068f, 18.432f) + curveTo(1.375f, 17.737f, 1.055f, 16.85f, 0.901f, 15.705f) + curveTo(0.749f, 14.575f, 0.75f, 13.121f, 0.75f, 11.25f) + verticalLineTo(9.25f) + curveTo(0.75f, 7.379f, 0.749f, 5.924f, 0.901f, 4.794f) + curveTo(1.055f, 3.65f, 1.374f, 2.762f, 2.068f, 2.068f) + curveTo(2.763f, 1.375f, 3.65f, 1.055f, 4.795f, 0.901f) + curveTo(5.925f, 0.749f, 7.379f, 0.75f, 9.25f, 0.75f) + horizontalLineTo(11.25f) + close() + } + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, fillAlpha = 0.6f, + strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, + strokeLineMiter = 4.0f, pathFillType = NonZero) { + moveTo(11.25f, 0.75f) + curveTo(13.121f, 0.75f, 14.576f, 0.749f, 15.706f, 0.901f) + curveTo(16.851f, 1.055f, 17.738f, 1.374f, 18.432f, 2.068f) + curveTo(19.125f, 2.763f, 19.444f, 3.65f, 19.599f, 4.795f) + curveTo(19.751f, 5.925f, 19.75f, 7.379f, 19.75f, 9.25f) + verticalLineTo(11.25f) + curveTo(19.75f, 11.293f, 19.749f, 11.336f, 19.749f, 11.379f) + curveTo(19.434f, 11.153f, 19.1f, 10.958f, 18.75f, 10.797f) + verticalLineTo(9.25f) + curveTo(18.75f, 7.35f, 18.749f, 5.976f, 18.607f, 4.928f) + curveTo(18.468f, 3.894f, 18.202f, 3.253f, 17.725f, 2.775f) + curveTo(17.247f, 2.298f, 16.606f, 2.031f, 15.572f, 1.892f) + curveTo(14.524f, 1.751f, 13.15f, 1.75f, 11.25f, 1.75f) + horizontalLineTo(9.25f) + curveTo(7.35f, 1.75f, 5.976f, 1.751f, 4.928f, 1.893f) + curveTo(3.894f, 2.032f, 3.253f, 2.298f, 2.775f, 2.775f) + curveTo(2.298f, 3.252f, 2.031f, 3.894f, 1.892f, 4.928f) + curveTo(1.751f, 5.976f, 1.75f, 7.35f, 1.75f, 9.25f) + verticalLineTo(11.25f) + curveTo(1.75f, 13.15f, 1.751f, 14.524f, 1.893f, 15.572f) + curveTo(2.032f, 16.606f, 2.298f, 17.247f, 2.775f, 17.725f) + curveTo(3.252f, 18.202f, 3.894f, 18.469f, 4.928f, 18.608f) + curveTo(5.976f, 18.749f, 7.35f, 18.75f, 9.25f, 18.75f) + horizontalLineTo(10.797f) + curveTo(10.958f, 19.1f, 11.153f, 19.434f, 11.379f, 19.749f) + curveTo(11.336f, 19.749f, 11.293f, 19.75f, 11.25f, 19.75f) + horizontalLineTo(9.25f) + curveTo(7.379f, 19.75f, 5.924f, 19.751f, 4.794f, 19.599f) + curveTo(3.65f, 19.445f, 2.762f, 19.126f, 2.068f, 18.432f) + curveTo(1.375f, 17.737f, 1.055f, 16.85f, 0.901f, 15.705f) + curveTo(0.749f, 14.575f, 0.75f, 13.121f, 0.75f, 11.25f) + verticalLineTo(9.25f) + curveTo(0.75f, 7.379f, 0.749f, 5.924f, 0.901f, 4.794f) + curveTo(1.055f, 3.65f, 1.374f, 2.762f, 2.068f, 2.068f) + curveTo(2.763f, 1.375f, 3.65f, 1.055f, 4.795f, 0.901f) + curveTo(5.925f, 0.749f, 7.379f, 0.75f, 9.25f, 0.75f) + horizontalLineTo(11.25f) + close() + } + path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(6.366f, 8.262f) + curveTo(6.825f, 8.223f, 7.285f, 8.312f, 7.696f, 8.518f) + curveTo(8.155f, 8.748f, 8.474f, 9.153f, 8.761f, 9.663f) + curveTo(9.043f, 10.164f, 9.333f, 10.841f, 9.697f, 11.691f) + lineTo(9.71f, 11.72f) + lineTo(9.763f, 11.844f) + curveTo(9.878f, 12.127f, 10.004f, 12.406f, 10.143f, 12.679f) + curveTo(10.243f, 12.862f, 10.3f, 12.908f, 10.324f, 12.924f) + curveTo(10.428f, 12.988f, 10.552f, 13.012f, 10.673f, 12.991f) + curveTo(10.702f, 12.986f, 10.772f, 12.964f, 10.933f, 12.831f) + curveTo(11.096f, 12.697f, 11.296f, 12.497f, 11.596f, 12.197f) + lineTo(11.611f, 12.181f) + curveTo(12.012f, 11.781f, 12.335f, 11.458f, 12.618f, 11.219f) + curveTo(12.911f, 10.973f, 13.195f, 10.785f, 13.528f, 10.685f) + curveTo(13.778f, 10.609f, 14.035f, 10.575f, 14.293f, 10.579f) + curveTo(13.441f, 10.873f, 12.658f, 11.358f, 12.008f, 12.008f) + curveTo(11.435f, 12.58f, 10.99f, 13.257f, 10.691f, 13.993f) + curveTo(10.379f, 14.015f, 10.066f, 13.941f, 9.798f, 13.774f) + curveTo(9.554f, 13.623f, 9.394f, 13.396f, 9.264f, 13.157f) + curveTo(9.138f, 12.925f, 9.008f, 12.621f, 8.854f, 12.261f) + lineTo(8.844f, 12.238f) + lineTo(8.79f, 12.113f) + curveTo(8.411f, 11.228f, 8.142f, 10.603f, 7.889f, 10.153f) + curveTo(7.638f, 9.706f, 7.442f, 9.509f, 7.248f, 9.412f) + curveTo(7.001f, 9.288f, 6.725f, 9.235f, 6.449f, 9.258f) + curveTo(6.233f, 9.277f, 5.978f, 9.387f, 5.578f, 9.708f) + curveTo(5.177f, 10.031f, 4.694f, 10.513f, 4.013f, 11.193f) + lineTo(3.25f, 11.957f) + verticalLineTo(10.543f) + lineTo(3.306f, 10.486f) + lineTo(3.328f, 10.465f) + curveTo(3.982f, 9.81f, 4.503f, 9.29f, 4.951f, 8.929f) + curveTo(5.407f, 8.562f, 5.853f, 8.305f, 6.366f, 8.262f) + close() + } + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, fillAlpha = 0.6f, + strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, + strokeLineMiter = 4.0f, pathFillType = NonZero) { + moveTo(6.366f, 8.262f) + curveTo(6.825f, 8.223f, 7.285f, 8.312f, 7.696f, 8.518f) + curveTo(8.155f, 8.748f, 8.474f, 9.153f, 8.761f, 9.663f) + curveTo(9.043f, 10.164f, 9.333f, 10.841f, 9.697f, 11.691f) + lineTo(9.71f, 11.72f) + lineTo(9.763f, 11.844f) + curveTo(9.878f, 12.127f, 10.004f, 12.406f, 10.143f, 12.679f) + curveTo(10.243f, 12.862f, 10.3f, 12.908f, 10.324f, 12.924f) + curveTo(10.428f, 12.988f, 10.552f, 13.012f, 10.673f, 12.991f) + curveTo(10.702f, 12.986f, 10.772f, 12.964f, 10.933f, 12.831f) + curveTo(11.096f, 12.697f, 11.296f, 12.497f, 11.596f, 12.197f) + lineTo(11.611f, 12.181f) + curveTo(12.012f, 11.781f, 12.335f, 11.458f, 12.618f, 11.219f) + curveTo(12.911f, 10.973f, 13.195f, 10.785f, 13.528f, 10.685f) + curveTo(13.778f, 10.609f, 14.035f, 10.575f, 14.293f, 10.579f) + curveTo(13.441f, 10.873f, 12.658f, 11.358f, 12.008f, 12.008f) + curveTo(11.435f, 12.58f, 10.99f, 13.257f, 10.691f, 13.993f) + curveTo(10.379f, 14.015f, 10.066f, 13.941f, 9.798f, 13.774f) + curveTo(9.554f, 13.623f, 9.394f, 13.396f, 9.264f, 13.157f) + curveTo(9.138f, 12.925f, 9.008f, 12.621f, 8.854f, 12.261f) + lineTo(8.844f, 12.238f) + lineTo(8.79f, 12.113f) + curveTo(8.411f, 11.228f, 8.142f, 10.603f, 7.889f, 10.153f) + curveTo(7.638f, 9.706f, 7.442f, 9.509f, 7.248f, 9.412f) + curveTo(7.001f, 9.288f, 6.725f, 9.235f, 6.449f, 9.258f) + curveTo(6.233f, 9.277f, 5.978f, 9.387f, 5.578f, 9.708f) + curveTo(5.177f, 10.031f, 4.694f, 10.513f, 4.013f, 11.193f) + lineTo(3.25f, 11.957f) + verticalLineTo(10.543f) + lineTo(3.306f, 10.486f) + lineTo(3.328f, 10.465f) + curveTo(3.982f, 9.81f, 4.503f, 9.29f, 4.951f, 8.929f) + curveTo(5.407f, 8.562f, 5.853f, 8.305f, 6.366f, 8.262f) + close() + } + path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(14.75f, 4.25f) + curveTo(15.578f, 4.25f, 16.25f, 4.922f, 16.25f, 5.75f) + curveTo(16.25f, 6.578f, 15.578f, 7.25f, 14.75f, 7.25f) + curveTo(13.922f, 7.25f, 13.25f, 6.578f, 13.25f, 5.75f) + curveTo(13.25f, 4.922f, 13.922f, 4.25f, 14.75f, 4.25f) + close() + } + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, fillAlpha = 0.6f, + strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, + strokeLineMiter = 4.0f, pathFillType = NonZero) { + moveTo(14.75f, 4.25f) + curveTo(15.578f, 4.25f, 16.25f, 4.922f, 16.25f, 5.75f) + curveTo(16.25f, 6.578f, 15.578f, 7.25f, 14.75f, 7.25f) + curveTo(13.922f, 7.25f, 13.25f, 6.578f, 13.25f, 5.75f) + curveTo(13.25f, 4.922f, 13.922f, 4.25f, 14.75f, 4.25f) + close() + } + path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = EvenOdd) { + moveTo(16.25f, 21.25f) + curveTo(17.576f, 21.25f, 18.848f, 20.723f, 19.785f, 19.785f) + curveTo(20.723f, 18.848f, 21.25f, 17.576f, 21.25f, 16.25f) + curveTo(21.25f, 14.924f, 20.723f, 13.652f, 19.785f, 12.715f) + curveTo(18.848f, 11.777f, 17.576f, 11.25f, 16.25f, 11.25f) + curveTo(14.924f, 11.25f, 13.652f, 11.777f, 12.715f, 12.715f) + curveTo(11.777f, 13.652f, 11.25f, 14.924f, 11.25f, 16.25f) + curveTo(11.25f, 17.576f, 11.777f, 18.848f, 12.715f, 19.785f) + curveTo(13.652f, 20.723f, 14.924f, 21.25f, 16.25f, 21.25f) + close() + moveTo(16.25f, 13.075f) + curveTo(16.371f, 13.075f, 16.486f, 13.122f, 16.571f, 13.208f) + curveTo(16.657f, 13.293f, 16.705f, 13.408f, 16.705f, 13.529f) + verticalLineTo(15.795f) + horizontalLineTo(18.971f) + curveTo(19.091f, 15.795f, 19.207f, 15.843f, 19.292f, 15.929f) + curveTo(19.378f, 16.014f, 19.426f, 16.129f, 19.426f, 16.25f) + curveTo(19.426f, 16.371f, 19.378f, 16.486f, 19.292f, 16.571f) + curveTo(19.207f, 16.657f, 19.091f, 16.705f, 18.971f, 16.705f) + horizontalLineTo(16.705f) + verticalLineTo(18.972f) + curveTo(16.705f, 19.092f, 16.657f, 19.208f, 16.571f, 19.293f) + curveTo(16.486f, 19.378f, 16.371f, 19.426f, 16.25f, 19.426f) + curveTo(16.129f, 19.426f, 16.014f, 19.378f, 15.929f, 19.293f) + curveTo(15.843f, 19.208f, 15.795f, 19.092f, 15.795f, 18.972f) + verticalLineTo(16.705f) + horizontalLineTo(13.529f) + curveTo(13.408f, 16.705f, 13.293f, 16.657f, 13.208f, 16.571f) + curveTo(13.122f, 16.486f, 13.075f, 16.371f, 13.075f, 16.25f) + curveTo(13.075f, 16.129f, 13.122f, 16.014f, 13.208f, 15.929f) + curveTo(13.293f, 15.843f, 13.408f, 15.795f, 13.529f, 15.795f) + horizontalLineTo(15.795f) + verticalLineTo(13.529f) + curveTo(15.795f, 13.408f, 15.843f, 13.293f, 15.929f, 13.208f) + curveTo(16.014f, 13.122f, 16.129f, 13.075f, 16.25f, 13.075f) + close() + } + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, fillAlpha = 0.6f, + strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, + strokeLineMiter = 4.0f, pathFillType = EvenOdd) { + moveTo(16.25f, 21.25f) + curveTo(17.576f, 21.25f, 18.848f, 20.723f, 19.785f, 19.785f) + curveTo(20.723f, 18.848f, 21.25f, 17.576f, 21.25f, 16.25f) + curveTo(21.25f, 14.924f, 20.723f, 13.652f, 19.785f, 12.715f) + curveTo(18.848f, 11.777f, 17.576f, 11.25f, 16.25f, 11.25f) + curveTo(14.924f, 11.25f, 13.652f, 11.777f, 12.715f, 12.715f) + curveTo(11.777f, 13.652f, 11.25f, 14.924f, 11.25f, 16.25f) + curveTo(11.25f, 17.576f, 11.777f, 18.848f, 12.715f, 19.785f) + curveTo(13.652f, 20.723f, 14.924f, 21.25f, 16.25f, 21.25f) + close() + moveTo(16.25f, 13.075f) + curveTo(16.371f, 13.075f, 16.486f, 13.122f, 16.571f, 13.208f) + curveTo(16.657f, 13.293f, 16.705f, 13.408f, 16.705f, 13.529f) + verticalLineTo(15.795f) + horizontalLineTo(18.971f) + curveTo(19.091f, 15.795f, 19.207f, 15.843f, 19.292f, 15.929f) + curveTo(19.378f, 16.014f, 19.426f, 16.129f, 19.426f, 16.25f) + curveTo(19.426f, 16.371f, 19.378f, 16.486f, 19.292f, 16.571f) + curveTo(19.207f, 16.657f, 19.091f, 16.705f, 18.971f, 16.705f) + horizontalLineTo(16.705f) + verticalLineTo(18.972f) + curveTo(16.705f, 19.092f, 16.657f, 19.208f, 16.571f, 19.293f) + curveTo(16.486f, 19.378f, 16.371f, 19.426f, 16.25f, 19.426f) + curveTo(16.129f, 19.426f, 16.014f, 19.378f, 15.929f, 19.293f) + curveTo(15.843f, 19.208f, 15.795f, 19.092f, 15.795f, 18.972f) + verticalLineTo(16.705f) + horizontalLineTo(13.529f) + curveTo(13.408f, 16.705f, 13.293f, 16.657f, 13.208f, 16.571f) + curveTo(13.122f, 16.486f, 13.075f, 16.371f, 13.075f, 16.25f) + curveTo(13.075f, 16.129f, 13.122f, 16.014f, 13.208f, 15.929f) + curveTo(13.293f, 15.843f, 13.408f, 15.795f, 13.529f, 15.795f) + horizontalLineTo(15.795f) + verticalLineTo(13.529f) + curveTo(15.795f, 13.408f, 15.843f, 13.293f, 15.929f, 13.208f) + curveTo(16.014f, 13.122f, 16.129f, 13.075f, 16.25f, 13.075f) + close() + } + } + .build() + return _gallery!! + } + +private var _gallery: ImageVector? = null + +@Preview +@Composable +private fun Preview(): Unit { + Box(modifier = Modifier.padding(12.dp)) { + Image(imageVector = AppIcons.Gallery, contentDescription = "") + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/Home.kt b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/Home.kt new file mode 100644 index 0000000..c4779e5 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/Home.kt @@ -0,0 +1,216 @@ +package com.min.dnapp.presentation.ui.icon.appicons + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType +import androidx.compose.ui.graphics.PathFillType.Companion.NonZero +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.StrokeCap.Companion.Butt +import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.ImageVector.Builder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.min.dnapp.presentation.ui.icon.AppIcons +import kotlin.Unit + +public val AppIcons.Home: ImageVector + get() { + if (_home != null) { + return _home!! + } + _home = Builder(name = "Home", defaultWidth = 29.0.dp, defaultHeight = 29.0.dp, + viewportWidth = 29.0f, viewportHeight = 29.0f).apply { + path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(23.25f, 12.167f) + curveTo(23.25f, 12.012f, 23.189f, 11.864f, 23.079f, 11.755f) + curveTo(22.97f, 11.645f, 22.822f, 11.584f, 22.667f, 11.584f) + curveTo(22.512f, 11.584f, 22.364f, 11.645f, 22.254f, 11.755f) + curveTo(22.145f, 11.864f, 22.083f, 12.012f, 22.083f, 12.167f) + horizontalLineTo(23.25f) + close() + moveTo(6.917f, 12.167f) + curveTo(6.917f, 12.012f, 6.855f, 11.864f, 6.746f, 11.755f) + curveTo(6.636f, 11.645f, 6.488f, 11.584f, 6.333f, 11.584f) + curveTo(6.179f, 11.584f, 6.03f, 11.645f, 5.921f, 11.755f) + curveTo(5.812f, 11.864f, 5.75f, 12.012f, 5.75f, 12.167f) + horizontalLineTo(6.917f) + close() + moveTo(24.587f, 14.913f) + curveTo(24.697f, 15.023f, 24.845f, 15.085f, 25.0f, 15.085f) + curveTo(25.155f, 15.085f, 25.304f, 15.023f, 25.413f, 14.913f) + curveTo(25.523f, 14.804f, 25.584f, 14.655f, 25.584f, 14.5f) + curveTo(25.584f, 14.345f, 25.523f, 14.197f, 25.413f, 14.087f) + lineTo(24.587f, 14.913f) + close() + moveTo(14.5f, 4.0f) + lineTo(14.913f, 3.587f) + curveTo(14.859f, 3.533f, 14.795f, 3.49f, 14.724f, 3.461f) + curveTo(14.653f, 3.431f, 14.577f, 3.416f, 14.5f, 3.416f) + curveTo(14.423f, 3.416f, 14.347f, 3.431f, 14.276f, 3.461f) + curveTo(14.206f, 3.49f, 14.141f, 3.533f, 14.087f, 3.587f) + lineTo(14.5f, 4.0f) + close() + moveTo(3.587f, 14.087f) + curveTo(3.478f, 14.197f, 3.416f, 14.345f, 3.416f, 14.5f) + curveTo(3.416f, 14.655f, 3.478f, 14.804f, 3.587f, 14.913f) + curveTo(3.697f, 15.023f, 3.845f, 15.085f, 4.0f, 15.085f) + curveTo(4.155f, 15.085f, 4.304f, 15.023f, 4.413f, 14.913f) + lineTo(3.587f, 14.087f) + close() + moveTo(8.667f, 25.584f) + horizontalLineTo(20.333f) + verticalLineTo(24.417f) + horizontalLineTo(8.667f) + verticalLineTo(25.584f) + close() + moveTo(23.25f, 22.667f) + verticalLineTo(12.167f) + horizontalLineTo(22.083f) + verticalLineTo(22.667f) + horizontalLineTo(23.25f) + close() + moveTo(6.917f, 22.667f) + verticalLineTo(12.167f) + horizontalLineTo(5.75f) + verticalLineTo(22.667f) + horizontalLineTo(6.917f) + close() + moveTo(25.413f, 14.087f) + lineTo(14.913f, 3.587f) + lineTo(14.087f, 4.413f) + lineTo(24.587f, 14.913f) + lineTo(25.413f, 14.087f) + close() + moveTo(14.087f, 3.587f) + lineTo(3.587f, 14.087f) + lineTo(4.413f, 14.913f) + lineTo(14.913f, 4.413f) + lineTo(14.087f, 3.587f) + close() + moveTo(20.333f, 25.584f) + curveTo(21.107f, 25.584f, 21.849f, 25.276f, 22.396f, 24.729f) + curveTo(22.943f, 24.183f, 23.25f, 23.441f, 23.25f, 22.667f) + horizontalLineTo(22.083f) + curveTo(22.083f, 23.131f, 21.899f, 23.576f, 21.571f, 23.904f) + curveTo(21.243f, 24.233f, 20.798f, 24.417f, 20.333f, 24.417f) + verticalLineTo(25.584f) + close() + moveTo(8.667f, 24.417f) + curveTo(8.203f, 24.417f, 7.758f, 24.233f, 7.429f, 23.904f) + curveTo(7.101f, 23.576f, 6.917f, 23.131f, 6.917f, 22.667f) + horizontalLineTo(5.75f) + curveTo(5.75f, 23.441f, 6.057f, 24.183f, 6.604f, 24.729f) + curveTo(7.151f, 25.276f, 7.893f, 25.584f, 8.667f, 25.584f) + verticalLineTo(24.417f) + close() + } + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, fillAlpha = 0.6f, + strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, + strokeLineMiter = 4.0f, pathFillType = NonZero) { + moveTo(23.25f, 12.167f) + curveTo(23.25f, 12.012f, 23.189f, 11.864f, 23.079f, 11.755f) + curveTo(22.97f, 11.645f, 22.822f, 11.584f, 22.667f, 11.584f) + curveTo(22.512f, 11.584f, 22.364f, 11.645f, 22.254f, 11.755f) + curveTo(22.145f, 11.864f, 22.083f, 12.012f, 22.083f, 12.167f) + horizontalLineTo(23.25f) + close() + moveTo(6.917f, 12.167f) + curveTo(6.917f, 12.012f, 6.855f, 11.864f, 6.746f, 11.755f) + curveTo(6.636f, 11.645f, 6.488f, 11.584f, 6.333f, 11.584f) + curveTo(6.179f, 11.584f, 6.03f, 11.645f, 5.921f, 11.755f) + curveTo(5.812f, 11.864f, 5.75f, 12.012f, 5.75f, 12.167f) + horizontalLineTo(6.917f) + close() + moveTo(24.587f, 14.913f) + curveTo(24.697f, 15.023f, 24.845f, 15.085f, 25.0f, 15.085f) + curveTo(25.155f, 15.085f, 25.304f, 15.023f, 25.413f, 14.913f) + curveTo(25.523f, 14.804f, 25.584f, 14.655f, 25.584f, 14.5f) + curveTo(25.584f, 14.345f, 25.523f, 14.197f, 25.413f, 14.087f) + lineTo(24.587f, 14.913f) + close() + moveTo(14.5f, 4.0f) + lineTo(14.913f, 3.587f) + curveTo(14.859f, 3.533f, 14.795f, 3.49f, 14.724f, 3.461f) + curveTo(14.653f, 3.431f, 14.577f, 3.416f, 14.5f, 3.416f) + curveTo(14.423f, 3.416f, 14.347f, 3.431f, 14.276f, 3.461f) + curveTo(14.206f, 3.49f, 14.141f, 3.533f, 14.087f, 3.587f) + lineTo(14.5f, 4.0f) + close() + moveTo(3.587f, 14.087f) + curveTo(3.478f, 14.197f, 3.416f, 14.345f, 3.416f, 14.5f) + curveTo(3.416f, 14.655f, 3.478f, 14.804f, 3.587f, 14.913f) + curveTo(3.697f, 15.023f, 3.845f, 15.085f, 4.0f, 15.085f) + curveTo(4.155f, 15.085f, 4.304f, 15.023f, 4.413f, 14.913f) + lineTo(3.587f, 14.087f) + close() + moveTo(8.667f, 25.584f) + horizontalLineTo(20.333f) + verticalLineTo(24.417f) + horizontalLineTo(8.667f) + verticalLineTo(25.584f) + close() + moveTo(23.25f, 22.667f) + verticalLineTo(12.167f) + horizontalLineTo(22.083f) + verticalLineTo(22.667f) + horizontalLineTo(23.25f) + close() + moveTo(6.917f, 22.667f) + verticalLineTo(12.167f) + horizontalLineTo(5.75f) + verticalLineTo(22.667f) + horizontalLineTo(6.917f) + close() + moveTo(25.413f, 14.087f) + lineTo(14.913f, 3.587f) + lineTo(14.087f, 4.413f) + lineTo(24.587f, 14.913f) + lineTo(25.413f, 14.087f) + close() + moveTo(14.087f, 3.587f) + lineTo(3.587f, 14.087f) + lineTo(4.413f, 14.913f) + lineTo(14.913f, 4.413f) + lineTo(14.087f, 3.587f) + close() + moveTo(20.333f, 25.584f) + curveTo(21.107f, 25.584f, 21.849f, 25.276f, 22.396f, 24.729f) + curveTo(22.943f, 24.183f, 23.25f, 23.441f, 23.25f, 22.667f) + horizontalLineTo(22.083f) + curveTo(22.083f, 23.131f, 21.899f, 23.576f, 21.571f, 23.904f) + curveTo(21.243f, 24.233f, 20.798f, 24.417f, 20.333f, 24.417f) + verticalLineTo(25.584f) + close() + moveTo(8.667f, 24.417f) + curveTo(8.203f, 24.417f, 7.758f, 24.233f, 7.429f, 23.904f) + curveTo(7.101f, 23.576f, 6.917f, 23.131f, 6.917f, 22.667f) + horizontalLineTo(5.75f) + curveTo(5.75f, 23.441f, 6.057f, 24.183f, 6.604f, 24.729f) + curveTo(7.151f, 25.276f, 7.893f, 25.584f, 8.667f, 25.584f) + verticalLineTo(24.417f) + close() + } + } + .build() + return _home!! + } + +private var _home: ImageVector? = null + +@Preview +@Composable +private fun Preview(): Unit { + Box(modifier = Modifier.padding(12.dp)) { + Image(imageVector = AppIcons.Home, contentDescription = "") + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/Kakao.kt b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/Kakao.kt new file mode 100644 index 0000000..93ab59c --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/Kakao.kt @@ -0,0 +1,55 @@ +package com.min.dnapp.presentation.ui.icon.appicons + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType.Companion.EvenOdd +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap.Companion.Butt +import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.ImageVector.Builder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.min.dnapp.presentation.ui.icon.AppIcons +import kotlin.Unit + +public val AppIcons.Kakao: ImageVector + get() { + if (_kakao != null) { + return _kakao!! + } + _kakao = Builder(name = "Kakao", defaultWidth = 20.0.dp, defaultHeight = 20.0.dp, + viewportWidth = 20.0f, viewportHeight = 20.0f).apply { + path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = EvenOdd) { + moveTo(10.0f, 0.667f) + curveTo(4.477f, 0.667f, 0.0f, 4.125f, 0.0f, 8.391f) + curveTo(0.0f, 11.044f, 1.732f, 13.383f, 4.369f, 14.774f) + lineTo(3.259f, 18.827f) + curveTo(3.161f, 19.185f, 3.571f, 19.471f, 3.885f, 19.263f) + lineTo(8.749f, 16.053f) + curveTo(9.159f, 16.093f, 9.576f, 16.116f, 10.0f, 16.116f) + curveTo(15.523f, 16.116f, 20.0f, 12.658f, 20.0f, 8.391f) + curveTo(20.0f, 4.125f, 15.523f, 0.667f, 10.0f, 0.667f) + close() + } + } + .build() + return _kakao!! + } + +private var _kakao: ImageVector? = null + +@Preview +@Composable +private fun Preview(): Unit { + Box(modifier = Modifier.padding(12.dp)) { + Image(imageVector = AppIcons.Kakao, contentDescription = "") + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/More.kt b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/More.kt new file mode 100644 index 0000000..3af04d5 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/More.kt @@ -0,0 +1,104 @@ +package com.min.dnapp.presentation.ui.icon.appicons + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType +import androidx.compose.ui.graphics.PathFillType.Companion.NonZero +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.StrokeCap.Companion.Butt +import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.ImageVector.Builder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.min.dnapp.presentation.ui.icon.AppIcons +import kotlin.Unit + +public val AppIcons.More: ImageVector + get() { + if (_more != null) { + return _more!! + } + _more = Builder(name = "More", defaultWidth = 21.0.dp, defaultHeight = 20.0.dp, + viewportWidth = 21.0f, viewportHeight = 20.0f).apply { + path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(5.11f, 10.0f) + curveTo(5.11f, 10.506f, 4.7f, 10.917f, 4.193f, 10.917f) + curveTo(3.687f, 10.917f, 3.277f, 10.506f, 3.277f, 10.0f) + curveTo(3.277f, 9.494f, 3.687f, 9.083f, 4.193f, 9.083f) + curveTo(4.7f, 9.083f, 5.11f, 9.494f, 5.11f, 10.0f) + close() + } + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, fillAlpha = 0.2f, + strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, + strokeLineMiter = 4.0f, pathFillType = NonZero) { + moveTo(5.11f, 10.0f) + curveTo(5.11f, 10.506f, 4.7f, 10.917f, 4.193f, 10.917f) + curveTo(3.687f, 10.917f, 3.277f, 10.506f, 3.277f, 10.0f) + curveTo(3.277f, 9.494f, 3.687f, 9.083f, 4.193f, 9.083f) + curveTo(4.7f, 9.083f, 5.11f, 9.494f, 5.11f, 10.0f) + close() + } + path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(11.693f, 10.0f) + curveTo(11.693f, 10.506f, 11.283f, 10.917f, 10.777f, 10.917f) + curveTo(10.27f, 10.917f, 9.86f, 10.506f, 9.86f, 10.0f) + curveTo(9.86f, 9.494f, 10.27f, 9.083f, 10.777f, 9.083f) + curveTo(11.283f, 9.083f, 11.693f, 9.494f, 11.693f, 10.0f) + close() + } + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, fillAlpha = 0.2f, + strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, + strokeLineMiter = 4.0f, pathFillType = NonZero) { + moveTo(11.693f, 10.0f) + curveTo(11.693f, 10.506f, 11.283f, 10.917f, 10.777f, 10.917f) + curveTo(10.27f, 10.917f, 9.86f, 10.506f, 9.86f, 10.0f) + curveTo(9.86f, 9.494f, 10.27f, 9.083f, 10.777f, 9.083f) + curveTo(11.283f, 9.083f, 11.693f, 9.494f, 11.693f, 10.0f) + close() + } + path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(18.277f, 10.0f) + curveTo(18.277f, 10.506f, 17.866f, 10.917f, 17.36f, 10.917f) + curveTo(16.854f, 10.917f, 16.443f, 10.506f, 16.443f, 10.0f) + curveTo(16.443f, 9.494f, 16.854f, 9.083f, 17.36f, 9.083f) + curveTo(17.866f, 9.083f, 18.277f, 9.494f, 18.277f, 10.0f) + close() + } + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, fillAlpha = 0.2f, + strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, + strokeLineMiter = 4.0f, pathFillType = NonZero) { + moveTo(18.277f, 10.0f) + curveTo(18.277f, 10.506f, 17.866f, 10.917f, 17.36f, 10.917f) + curveTo(16.854f, 10.917f, 16.443f, 10.506f, 16.443f, 10.0f) + curveTo(16.443f, 9.494f, 16.854f, 9.083f, 17.36f, 9.083f) + curveTo(17.866f, 9.083f, 18.277f, 9.494f, 18.277f, 10.0f) + close() + } + } + .build() + return _more!! + } + +private var _more: ImageVector? = null + +@Preview +@Composable +private fun Preview(): Unit { + Box(modifier = Modifier.padding(12.dp)) { + Image(imageVector = AppIcons.More, contentDescription = "") + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/My.kt b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/My.kt new file mode 100644 index 0000000..f5fb11f --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/My.kt @@ -0,0 +1,144 @@ +package com.min.dnapp.presentation.ui.icon.appicons + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType +import androidx.compose.ui.graphics.PathFillType.Companion.NonZero +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.StrokeCap.Companion.Butt +import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.ImageVector.Builder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.min.dnapp.presentation.ui.icon.AppIcons +import kotlin.Unit + +public val AppIcons.My: ImageVector + get() { + if (_my != null) { + return _my!! + } + _my = Builder(name = "My", defaultWidth = 29.0.dp, defaultHeight = 29.0.dp, viewportWidth = + 29.0f, viewportHeight = 29.0f).apply { + path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(17.417f, 12.166f) + curveTo(17.417f, 10.556f, 16.111f, 9.25f, 14.5f, 9.25f) + curveTo(12.889f, 9.25f, 11.583f, 10.556f, 11.583f, 12.166f) + curveTo(11.583f, 13.777f, 12.889f, 15.083f, 14.5f, 15.083f) + curveTo(16.111f, 15.083f, 17.417f, 13.777f, 17.417f, 12.166f) + close() + moveTo(18.583f, 12.166f) + curveTo(18.583f, 14.422f, 16.755f, 16.25f, 14.5f, 16.25f) + curveTo(12.245f, 16.25f, 10.417f, 14.422f, 10.417f, 12.166f) + curveTo(10.417f, 9.911f, 12.245f, 8.083f, 14.5f, 8.083f) + curveTo(16.755f, 8.083f, 18.583f, 9.911f, 18.583f, 12.166f) + close() + } + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, fillAlpha = 0.6f, + strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, + strokeLineMiter = 4.0f, pathFillType = NonZero) { + moveTo(17.417f, 12.166f) + curveTo(17.417f, 10.556f, 16.111f, 9.25f, 14.5f, 9.25f) + curveTo(12.889f, 9.25f, 11.583f, 10.556f, 11.583f, 12.166f) + curveTo(11.583f, 13.777f, 12.889f, 15.083f, 14.5f, 15.083f) + curveTo(16.111f, 15.083f, 17.417f, 13.777f, 17.417f, 12.166f) + close() + moveTo(18.583f, 12.166f) + curveTo(18.583f, 14.422f, 16.755f, 16.25f, 14.5f, 16.25f) + curveTo(12.245f, 16.25f, 10.417f, 14.422f, 10.417f, 12.166f) + curveTo(10.417f, 9.911f, 12.245f, 8.083f, 14.5f, 8.083f) + curveTo(16.755f, 8.083f, 18.583f, 9.911f, 18.583f, 12.166f) + close() + } + path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(24.417f, 14.5f) + curveTo(24.417f, 9.023f, 19.977f, 4.583f, 14.5f, 4.583f) + curveTo(9.023f, 4.583f, 4.583f, 9.023f, 4.583f, 14.5f) + curveTo(4.583f, 19.977f, 9.023f, 24.417f, 14.5f, 24.417f) + curveTo(19.977f, 24.417f, 24.417f, 19.977f, 24.417f, 14.5f) + close() + moveTo(25.583f, 14.5f) + curveTo(25.583f, 20.621f, 20.621f, 25.583f, 14.5f, 25.583f) + curveTo(8.379f, 25.583f, 3.417f, 20.621f, 3.417f, 14.5f) + curveTo(3.417f, 8.379f, 8.379f, 3.417f, 14.5f, 3.417f) + curveTo(20.621f, 3.417f, 25.583f, 8.379f, 25.583f, 14.5f) + close() + } + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, fillAlpha = 0.6f, + strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, + strokeLineMiter = 4.0f, pathFillType = NonZero) { + moveTo(24.417f, 14.5f) + curveTo(24.417f, 9.023f, 19.977f, 4.583f, 14.5f, 4.583f) + curveTo(9.023f, 4.583f, 4.583f, 9.023f, 4.583f, 14.5f) + curveTo(4.583f, 19.977f, 9.023f, 24.417f, 14.5f, 24.417f) + curveTo(19.977f, 24.417f, 24.417f, 19.977f, 24.417f, 14.5f) + close() + moveTo(25.583f, 14.5f) + curveTo(25.583f, 20.621f, 20.621f, 25.583f, 14.5f, 25.583f) + curveTo(8.379f, 25.583f, 3.417f, 20.621f, 3.417f, 14.5f) + curveTo(3.417f, 8.379f, 8.379f, 3.417f, 14.5f, 3.417f) + curveTo(20.621f, 3.417f, 25.583f, 8.379f, 25.583f, 14.5f) + close() + } + path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(14.5f, 17.417f) + curveTo(16.195f, 17.417f, 17.853f, 17.866f, 19.218f, 18.708f) + curveTo(20.583f, 19.552f, 21.592f, 20.751f, 22.054f, 22.139f) + curveTo(22.156f, 22.445f, 21.99f, 22.775f, 21.685f, 22.877f) + curveTo(21.379f, 22.979f, 21.048f, 22.814f, 20.946f, 22.508f) + curveTo(20.582f, 21.415f, 19.771f, 20.421f, 18.605f, 19.701f) + curveTo(17.438f, 18.98f, 15.995f, 18.583f, 14.5f, 18.583f) + curveTo(13.005f, 18.583f, 11.561f, 18.98f, 10.395f, 19.701f) + curveTo(9.23f, 20.421f, 8.418f, 21.416f, 8.054f, 22.508f) + curveTo(7.952f, 22.814f, 7.621f, 22.979f, 7.315f, 22.877f) + curveTo(7.01f, 22.775f, 6.845f, 22.445f, 6.946f, 22.139f) + curveTo(7.408f, 20.751f, 8.418f, 19.551f, 9.782f, 18.708f) + curveTo(11.146f, 17.866f, 12.805f, 17.417f, 14.5f, 17.417f) + close() + } + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, fillAlpha = 0.6f, + strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, + strokeLineMiter = 4.0f, pathFillType = NonZero) { + moveTo(14.5f, 17.417f) + curveTo(16.195f, 17.417f, 17.853f, 17.866f, 19.218f, 18.708f) + curveTo(20.583f, 19.552f, 21.592f, 20.751f, 22.054f, 22.139f) + curveTo(22.156f, 22.445f, 21.99f, 22.775f, 21.685f, 22.877f) + curveTo(21.379f, 22.979f, 21.048f, 22.814f, 20.946f, 22.508f) + curveTo(20.582f, 21.415f, 19.771f, 20.421f, 18.605f, 19.701f) + curveTo(17.438f, 18.98f, 15.995f, 18.583f, 14.5f, 18.583f) + curveTo(13.005f, 18.583f, 11.561f, 18.98f, 10.395f, 19.701f) + curveTo(9.23f, 20.421f, 8.418f, 21.416f, 8.054f, 22.508f) + curveTo(7.952f, 22.814f, 7.621f, 22.979f, 7.315f, 22.877f) + curveTo(7.01f, 22.775f, 6.845f, 22.445f, 6.946f, 22.139f) + curveTo(7.408f, 20.751f, 8.418f, 19.551f, 9.782f, 18.708f) + curveTo(11.146f, 17.866f, 12.805f, 17.417f, 14.5f, 17.417f) + close() + } + } + .build() + return _my!! + } + +private var _my: ImageVector? = null + +@Preview +@Composable +private fun Preview(): Unit { + Box(modifier = Modifier.padding(12.dp)) { + Image(imageVector = AppIcons.My, contentDescription = "") + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/Pen.kt b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/Pen.kt new file mode 100644 index 0000000..8299864 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/Pen.kt @@ -0,0 +1,132 @@ +package com.min.dnapp.presentation.ui.icon.appicons + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType +import androidx.compose.ui.graphics.PathFillType.Companion.NonZero +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.StrokeCap.Companion.Butt +import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.ImageVector.Builder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.min.dnapp.presentation.ui.icon.AppIcons +import kotlin.Unit + +public val AppIcons.Pen: ImageVector + get() { + if (_pen != null) { + return _pen!! + } + _pen = Builder(name = "Pen", defaultWidth = 20.0.dp, defaultHeight = 20.0.dp, viewportWidth + = 20.0f, viewportHeight = 20.0f).apply { + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(15.026f, 7.164f) + curveTo(15.026f, 6.992f, 14.956f, 6.835f, 14.817f, 6.653f) + curveTo(14.682f, 6.476f, 14.48f, 6.273f, 14.212f, 6.005f) + lineTo(14.192f, 5.985f) + lineTo(14.029f, 5.822f) + curveTo(13.762f, 5.555f, 13.559f, 5.353f, 13.382f, 5.218f) + lineTo(13.249f, 5.127f) + curveTo(13.12f, 5.048f, 13.0f, 5.009f, 12.871f, 5.009f) + curveTo(12.699f, 5.009f, 12.543f, 5.078f, 12.361f, 5.217f) + curveTo(12.184f, 5.352f, 11.98f, 5.555f, 11.712f, 5.822f) + lineTo(5.693f, 11.842f) + lineTo(5.665f, 11.869f) + curveTo(5.524f, 12.011f, 5.45f, 12.089f, 5.399f, 12.178f) + lineTo(5.398f, 12.179f) + curveTo(5.346f, 12.271f, 5.318f, 12.376f, 5.269f, 12.575f) + lineTo(5.259f, 12.611f) + lineTo(4.698f, 14.852f) + curveTo(4.67f, 14.959f, 4.647f, 15.068f, 4.631f, 15.177f) + curveTo(4.627f, 15.215f, 4.629f, 15.251f, 4.636f, 15.282f) + curveTo(4.642f, 15.311f, 4.654f, 15.335f, 4.677f, 15.359f) + curveTo(4.7f, 15.382f, 4.724f, 15.393f, 4.751f, 15.399f) + curveTo(4.782f, 15.406f, 4.818f, 15.406f, 4.857f, 15.403f) + curveTo(4.972f, 15.387f, 5.087f, 15.363f, 5.199f, 15.332f) + lineTo(5.216f, 15.327f) + lineTo(7.423f, 14.775f) + lineTo(7.434f, 14.773f) + lineTo(7.459f, 14.767f) + curveTo(7.657f, 14.717f, 7.764f, 14.688f, 7.855f, 14.637f) + curveTo(7.947f, 14.584f, 8.027f, 14.507f, 8.17f, 14.365f) + lineTo(8.197f, 14.337f) + lineTo(14.192f, 8.342f) + lineTo(14.212f, 8.322f) + curveTo(14.48f, 8.055f, 14.682f, 7.851f, 14.817f, 7.674f) + curveTo(14.956f, 7.491f, 15.026f, 7.335f, 15.026f, 7.164f) + close() + moveTo(15.859f, 7.164f) + curveTo(15.859f, 7.592f, 15.674f, 7.924f, 15.48f, 8.179f) + lineTo(15.479f, 8.18f) + curveTo(15.303f, 8.411f, 15.055f, 8.658f, 14.802f, 8.912f) + lineTo(8.759f, 14.954f) + curveTo(8.632f, 15.081f, 8.474f, 15.243f, 8.268f, 15.361f) + curveTo(8.06f, 15.479f, 7.837f, 15.531f, 7.663f, 15.575f) + lineTo(7.652f, 15.578f) + lineTo(7.625f, 15.583f) + lineTo(5.444f, 16.128f) + lineTo(5.427f, 16.133f) + curveTo(5.273f, 16.177f, 5.115f, 16.21f, 4.956f, 16.23f) + lineTo(4.946f, 16.232f) + curveTo(4.774f, 16.25f, 4.395f, 16.257f, 4.086f, 15.947f) + curveTo(3.779f, 15.639f, 3.786f, 15.262f, 3.802f, 15.091f) + lineTo(3.803f, 15.081f) + lineTo(3.805f, 15.072f) + curveTo(3.826f, 14.925f, 3.855f, 14.78f, 3.893f, 14.637f) + lineTo(4.451f, 12.409f) + lineTo(4.46f, 12.373f) + curveTo(4.503f, 12.198f, 4.555f, 11.976f, 4.673f, 11.768f) + curveTo(4.79f, 11.561f, 4.955f, 11.402f, 5.08f, 11.276f) + lineTo(5.085f, 11.27f) + lineTo(5.108f, 11.248f) + lineTo(11.103f, 5.253f) + lineTo(11.123f, 5.233f) + curveTo(11.377f, 4.98f, 11.624f, 4.731f, 11.855f, 4.555f) + curveTo(12.111f, 4.359f, 12.443f, 4.175f, 12.871f, 4.175f) + curveTo(13.246f, 4.175f, 13.547f, 4.317f, 13.788f, 4.483f) + lineTo(13.887f, 4.555f) + lineTo(14.064f, 4.699f) + curveTo(14.242f, 4.855f, 14.428f, 5.043f, 14.618f, 5.233f) + lineTo(14.639f, 5.253f) + lineTo(14.781f, 5.396f) + lineTo(14.802f, 5.416f) + curveTo(15.055f, 5.669f, 15.303f, 5.916f, 15.479f, 6.147f) + lineTo(15.552f, 6.247f) + curveTo(15.718f, 6.488f, 15.859f, 6.789f, 15.859f, 7.164f) + close() + } + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(10.788f, 5.914f) + lineTo(13.288f, 4.247f) + lineTo(15.788f, 6.747f) + lineTo(14.121f, 9.247f) + lineTo(10.788f, 5.914f) + close() + } + } + .build() + return _pen!! + } + +private var _pen: ImageVector? = null + +@Preview +@Composable +private fun Preview(): Unit { + Box(modifier = Modifier.padding(12.dp)) { + Image(imageVector = AppIcons.Pen, contentDescription = "") + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/PenSmall.kt b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/PenSmall.kt new file mode 100644 index 0000000..b7acab6 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/PenSmall.kt @@ -0,0 +1,220 @@ +package com.min.dnapp.presentation.ui.icon.appicons + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType +import androidx.compose.ui.graphics.PathFillType.Companion.NonZero +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.StrokeCap.Companion.Butt +import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.ImageVector.Builder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.min.dnapp.presentation.ui.icon.AppIcons +import kotlin.Unit + +public val AppIcons.PenSmall: ImageVector + get() { + if (_penSmall != null) { + return _penSmall!! + } + _penSmall = Builder(name = "PenSmall", defaultWidth = 10.0.dp, defaultHeight = 11.0.dp, + viewportWidth = 10.0f, viewportHeight = 11.0f).apply { + path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(9.021f, 2.772f) + curveTo(9.021f, 2.635f, 8.965f, 2.51f, 8.853f, 2.364f) + curveTo(8.745f, 2.223f, 8.584f, 2.06f, 8.37f, 1.845f) + lineTo(8.353f, 1.83f) + lineTo(8.223f, 1.7f) + curveTo(8.009f, 1.486f, 7.847f, 1.324f, 7.706f, 1.216f) + lineTo(7.599f, 1.143f) + curveTo(7.496f, 1.08f, 7.4f, 1.049f, 7.297f, 1.048f) + curveTo(7.159f, 1.048f, 7.034f, 1.104f, 6.889f, 1.215f) + curveTo(6.747f, 1.323f, 6.584f, 1.485f, 6.37f, 1.7f) + lineTo(1.554f, 6.515f) + lineTo(1.532f, 6.537f) + curveTo(1.419f, 6.65f, 1.359f, 6.713f, 1.319f, 6.784f) + lineTo(1.318f, 6.785f) + curveTo(1.277f, 6.858f, 1.255f, 6.942f, 1.215f, 7.101f) + lineTo(1.207f, 7.131f) + lineTo(0.758f, 8.924f) + curveTo(0.736f, 9.009f, 0.717f, 9.096f, 0.704f, 9.183f) + curveTo(0.702f, 9.214f, 0.703f, 9.242f, 0.708f, 9.267f) + curveTo(0.713f, 9.29f, 0.723f, 9.31f, 0.741f, 9.328f) + curveTo(0.76f, 9.347f, 0.779f, 9.356f, 0.801f, 9.361f) + curveTo(0.826f, 9.367f, 0.854f, 9.367f, 0.885f, 9.364f) + curveTo(0.978f, 9.351f, 1.069f, 9.332f, 1.159f, 9.307f) + lineTo(1.172f, 9.303f) + lineTo(2.938f, 8.862f) + lineTo(2.947f, 8.86f) + lineTo(2.967f, 8.855f) + curveTo(3.125f, 8.815f, 3.211f, 8.792f, 3.284f, 8.751f) + curveTo(3.357f, 8.709f, 3.422f, 8.648f, 3.536f, 8.534f) + lineTo(3.557f, 8.511f) + lineTo(8.353f, 3.715f) + lineTo(8.37f, 3.7f) + curveTo(8.584f, 3.485f, 8.745f, 3.322f, 8.853f, 3.181f) + curveTo(8.965f, 3.034f, 9.021f, 2.91f, 9.021f, 2.772f) + close() + moveTo(9.687f, 2.772f) + curveTo(9.687f, 3.115f, 9.539f, 3.381f, 9.384f, 3.585f) + lineTo(9.383f, 3.586f) + curveTo(9.242f, 3.771f, 9.044f, 3.968f, 8.841f, 4.171f) + lineTo(4.007f, 9.005f) + curveTo(3.905f, 9.107f, 3.779f, 9.236f, 3.614f, 9.33f) + curveTo(3.448f, 9.425f, 3.269f, 9.467f, 3.13f, 9.502f) + lineTo(3.122f, 9.504f) + lineTo(3.1f, 9.508f) + lineTo(1.355f, 9.944f) + lineTo(1.342f, 9.948f) + curveTo(1.218f, 9.983f, 1.092f, 10.009f, 0.965f, 10.026f) + lineTo(0.956f, 10.027f) + curveTo(0.819f, 10.041f, 0.516f, 10.047f, 0.269f, 9.799f) + curveTo(0.023f, 9.553f, 0.028f, 9.251f, 0.042f, 9.114f) + lineTo(0.042f, 9.106f) + lineTo(0.044f, 9.099f) + curveTo(0.06f, 8.982f, 0.084f, 8.865f, 0.115f, 8.751f) + lineTo(0.561f, 6.969f) + lineTo(0.568f, 6.94f) + curveTo(0.603f, 6.8f, 0.644f, 6.623f, 0.738f, 6.456f) + curveTo(0.832f, 6.29f, 0.964f, 6.163f, 1.064f, 6.062f) + lineTo(1.068f, 6.058f) + lineTo(1.086f, 6.04f) + lineTo(5.882f, 1.244f) + lineTo(5.898f, 1.228f) + curveTo(6.101f, 1.025f, 6.299f, 0.826f, 6.484f, 0.685f) + curveTo(6.689f, 0.529f, 6.955f, 0.382f, 7.297f, 0.382f) + curveTo(7.597f, 0.382f, 7.838f, 0.496f, 8.03f, 0.628f) + lineTo(8.109f, 0.685f) + lineTo(8.251f, 0.801f) + curveTo(8.394f, 0.926f, 8.542f, 1.076f, 8.695f, 1.228f) + lineTo(8.711f, 1.244f) + lineTo(8.825f, 1.358f) + lineTo(8.841f, 1.374f) + curveTo(9.044f, 1.577f, 9.242f, 1.774f, 9.383f, 1.959f) + lineTo(9.441f, 2.039f) + curveTo(9.574f, 2.232f, 9.687f, 2.473f, 9.687f, 2.772f) + close() + } + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, fillAlpha = 0.6f, + strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, + strokeLineMiter = 4.0f, pathFillType = NonZero) { + moveTo(9.021f, 2.772f) + curveTo(9.021f, 2.635f, 8.965f, 2.51f, 8.853f, 2.364f) + curveTo(8.745f, 2.223f, 8.584f, 2.06f, 8.37f, 1.845f) + lineTo(8.353f, 1.83f) + lineTo(8.223f, 1.7f) + curveTo(8.009f, 1.486f, 7.847f, 1.324f, 7.706f, 1.216f) + lineTo(7.599f, 1.143f) + curveTo(7.496f, 1.08f, 7.4f, 1.049f, 7.297f, 1.048f) + curveTo(7.159f, 1.048f, 7.034f, 1.104f, 6.889f, 1.215f) + curveTo(6.747f, 1.323f, 6.584f, 1.485f, 6.37f, 1.7f) + lineTo(1.554f, 6.515f) + lineTo(1.532f, 6.537f) + curveTo(1.419f, 6.65f, 1.359f, 6.713f, 1.319f, 6.784f) + lineTo(1.318f, 6.785f) + curveTo(1.277f, 6.858f, 1.255f, 6.942f, 1.215f, 7.101f) + lineTo(1.207f, 7.131f) + lineTo(0.758f, 8.924f) + curveTo(0.736f, 9.009f, 0.717f, 9.096f, 0.704f, 9.183f) + curveTo(0.702f, 9.214f, 0.703f, 9.242f, 0.708f, 9.267f) + curveTo(0.713f, 9.29f, 0.723f, 9.31f, 0.741f, 9.328f) + curveTo(0.76f, 9.347f, 0.779f, 9.356f, 0.801f, 9.361f) + curveTo(0.826f, 9.367f, 0.854f, 9.367f, 0.885f, 9.364f) + curveTo(0.978f, 9.351f, 1.069f, 9.332f, 1.159f, 9.307f) + lineTo(1.172f, 9.303f) + lineTo(2.938f, 8.862f) + lineTo(2.947f, 8.86f) + lineTo(2.967f, 8.855f) + curveTo(3.125f, 8.815f, 3.211f, 8.792f, 3.284f, 8.751f) + curveTo(3.357f, 8.709f, 3.422f, 8.648f, 3.536f, 8.534f) + lineTo(3.557f, 8.511f) + lineTo(8.353f, 3.715f) + lineTo(8.37f, 3.7f) + curveTo(8.584f, 3.485f, 8.745f, 3.322f, 8.853f, 3.181f) + curveTo(8.965f, 3.034f, 9.021f, 2.91f, 9.021f, 2.772f) + close() + moveTo(9.687f, 2.772f) + curveTo(9.687f, 3.115f, 9.539f, 3.381f, 9.384f, 3.585f) + lineTo(9.383f, 3.586f) + curveTo(9.242f, 3.771f, 9.044f, 3.968f, 8.841f, 4.171f) + lineTo(4.007f, 9.005f) + curveTo(3.905f, 9.107f, 3.779f, 9.236f, 3.614f, 9.33f) + curveTo(3.448f, 9.425f, 3.269f, 9.467f, 3.13f, 9.502f) + lineTo(3.122f, 9.504f) + lineTo(3.1f, 9.508f) + lineTo(1.355f, 9.944f) + lineTo(1.342f, 9.948f) + curveTo(1.218f, 9.983f, 1.092f, 10.009f, 0.965f, 10.026f) + lineTo(0.956f, 10.027f) + curveTo(0.819f, 10.041f, 0.516f, 10.047f, 0.269f, 9.799f) + curveTo(0.023f, 9.553f, 0.028f, 9.251f, 0.042f, 9.114f) + lineTo(0.042f, 9.106f) + lineTo(0.044f, 9.099f) + curveTo(0.06f, 8.982f, 0.084f, 8.865f, 0.115f, 8.751f) + lineTo(0.561f, 6.969f) + lineTo(0.568f, 6.94f) + curveTo(0.603f, 6.8f, 0.644f, 6.623f, 0.738f, 6.456f) + curveTo(0.832f, 6.29f, 0.964f, 6.163f, 1.064f, 6.062f) + lineTo(1.068f, 6.058f) + lineTo(1.086f, 6.04f) + lineTo(5.882f, 1.244f) + lineTo(5.898f, 1.228f) + curveTo(6.101f, 1.025f, 6.299f, 0.826f, 6.484f, 0.685f) + curveTo(6.689f, 0.529f, 6.955f, 0.382f, 7.297f, 0.382f) + curveTo(7.597f, 0.382f, 7.838f, 0.496f, 8.03f, 0.628f) + lineTo(8.109f, 0.685f) + lineTo(8.251f, 0.801f) + curveTo(8.394f, 0.926f, 8.542f, 1.076f, 8.695f, 1.228f) + lineTo(8.711f, 1.244f) + lineTo(8.825f, 1.358f) + lineTo(8.841f, 1.374f) + curveTo(9.044f, 1.577f, 9.242f, 1.774f, 9.383f, 1.959f) + lineTo(9.441f, 2.039f) + curveTo(9.574f, 2.232f, 9.687f, 2.473f, 9.687f, 2.772f) + close() + } + path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(5.63f, 1.773f) + lineTo(7.63f, 0.439f) + lineTo(9.63f, 2.439f) + lineTo(8.297f, 4.439f) + lineTo(5.63f, 1.773f) + close() + } + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, fillAlpha = 0.6f, + strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, + strokeLineMiter = 4.0f, pathFillType = NonZero) { + moveTo(5.63f, 1.773f) + lineTo(7.63f, 0.439f) + lineTo(9.63f, 2.439f) + lineTo(8.297f, 4.439f) + lineTo(5.63f, 1.773f) + close() + } + } + .build() + return _penSmall!! + } + +private var _penSmall: ImageVector? = null + +@Preview +@Composable +private fun Preview(): Unit { + Box(modifier = Modifier.padding(12.dp)) { + Image(imageVector = AppIcons.PenSmall, contentDescription = "") + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/RecordBest.kt b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/RecordBest.kt new file mode 100644 index 0000000..5cf69f7 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/RecordBest.kt @@ -0,0 +1,184 @@ +package com.min.dnapp.presentation.ui.icon.appicons + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType +import androidx.compose.ui.graphics.PathFillType.Companion.NonZero +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.StrokeCap.Companion.Butt +import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.ImageVector.Builder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.min.dnapp.presentation.ui.icon.AppIcons +import kotlin.Unit + +public val AppIcons.RecordBest: ImageVector + get() { + if (_recordBest != null) { + return _recordBest!! + } + _recordBest = Builder(name = "RecordBest", defaultWidth = 20.0.dp, defaultHeight = 20.0.dp, + viewportWidth = 20.0f, viewportHeight = 20.0f).apply { + path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(13.661f, 17.5f) + horizontalLineTo(4.128f) + curveTo(3.702f, 17.499f, 3.293f, 17.33f, 2.992f, 17.029f) + curveTo(2.69f, 16.728f, 2.521f, 16.319f, 2.52f, 15.893f) + verticalLineTo(10.536f) + curveTo(2.52f, 9.65f, 3.241f, 8.929f, 4.127f, 8.929f) + horizontalLineTo(6.0f) + curveTo(6.959f, 7.607f, 7.728f, 6.157f, 8.283f, 4.621f) + lineTo(8.78f, 3.242f) + curveTo(8.9f, 2.819f, 9.324f, 2.5f, 9.799f, 2.5f) + lineTo(9.935f, 2.504f) + curveTo(10.605f, 2.541f, 11.227f, 2.856f, 11.656f, 3.379f) + curveTo(11.853f, 3.619f, 12.001f, 3.894f, 12.093f, 4.19f) + lineTo(12.129f, 4.319f) + curveTo(12.215f, 4.663f, 12.223f, 5.022f, 12.153f, 5.37f) + lineTo(11.656f, 7.856f) + horizontalLineTo(15.336f) + lineTo(15.465f, 7.861f) + curveTo(16.101f, 7.898f, 16.68f, 8.209f, 17.061f, 8.73f) + curveTo(17.237f, 8.966f, 17.361f, 9.236f, 17.427f, 9.521f) + lineTo(17.451f, 9.645f) + curveTo(17.507f, 9.977f, 17.483f, 10.317f, 17.381f, 10.638f) + lineTo(15.708f, 15.995f) + curveTo(15.443f, 16.839f, 14.705f, 17.423f, 13.837f, 17.493f) + lineTo(13.661f, 17.5f) + close() + moveTo(13.664f, 16.428f) + curveTo(13.892f, 16.428f, 14.115f, 16.355f, 14.298f, 16.22f) + lineTo(14.365f, 16.166f) + curveTo(14.515f, 16.036f, 14.626f, 15.867f, 14.686f, 15.676f) + lineTo(16.36f, 10.319f) + curveTo(16.41f, 10.159f, 16.421f, 9.988f, 16.394f, 9.823f) + curveTo(16.366f, 9.657f, 16.3f, 9.5f, 16.201f, 9.364f) + curveTo(16.102f, 9.228f, 15.972f, 9.117f, 15.822f, 9.041f) + curveTo(15.672f, 8.965f, 15.506f, 8.926f, 15.337f, 8.927f) + horizontalLineTo(11.003f) + lineTo(10.944f, 8.923f) + curveTo(10.885f, 8.917f, 10.827f, 8.901f, 10.774f, 8.875f) + curveTo(10.702f, 8.841f, 10.638f, 8.793f, 10.588f, 8.731f) + curveTo(10.538f, 8.67f, 10.502f, 8.598f, 10.482f, 8.521f) + curveTo(10.463f, 8.444f, 10.461f, 8.364f, 10.477f, 8.286f) + lineTo(11.102f, 5.16f) + lineTo(11.124f, 5.014f) + curveTo(11.137f, 4.868f, 11.126f, 4.72f, 11.09f, 4.577f) + curveTo(11.043f, 4.386f, 10.953f, 4.207f, 10.828f, 4.055f) + curveTo(10.703f, 3.903f, 10.546f, 3.781f, 10.368f, 3.697f) + curveTo(10.19f, 3.613f, 9.996f, 3.57f, 9.799f, 3.57f) + lineTo(9.292f, 4.983f) + horizontalLineTo(9.296f) + curveTo(8.695f, 6.646f, 7.858f, 8.215f, 6.81f, 9.639f) + verticalLineTo(16.428f) + horizontalLineTo(13.664f) + close() + moveTo(5.735f, 16.428f) + verticalLineTo(9.999f) + horizontalLineTo(4.128f) + lineTo(4.023f, 10.01f) + curveTo(3.954f, 10.024f, 3.889f, 10.051f, 3.831f, 10.09f) + lineTo(3.75f, 10.156f) + curveTo(3.649f, 10.257f, 3.592f, 10.393f, 3.592f, 10.535f) + verticalLineTo(15.893f) + curveTo(3.592f, 16.035f, 3.649f, 16.171f, 3.75f, 16.272f) + curveTo(3.825f, 16.347f, 3.92f, 16.398f, 4.023f, 16.419f) + lineTo(4.128f, 16.428f) + horizontalLineTo(5.735f) + close() + } + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, fillAlpha = 0.6f, + strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, + strokeLineMiter = 4.0f, pathFillType = NonZero) { + moveTo(13.661f, 17.5f) + horizontalLineTo(4.128f) + curveTo(3.702f, 17.499f, 3.293f, 17.33f, 2.992f, 17.029f) + curveTo(2.69f, 16.728f, 2.521f, 16.319f, 2.52f, 15.893f) + verticalLineTo(10.536f) + curveTo(2.52f, 9.65f, 3.241f, 8.929f, 4.127f, 8.929f) + horizontalLineTo(6.0f) + curveTo(6.959f, 7.607f, 7.728f, 6.157f, 8.283f, 4.621f) + lineTo(8.78f, 3.242f) + curveTo(8.9f, 2.819f, 9.324f, 2.5f, 9.799f, 2.5f) + lineTo(9.935f, 2.504f) + curveTo(10.605f, 2.541f, 11.227f, 2.856f, 11.656f, 3.379f) + curveTo(11.853f, 3.619f, 12.001f, 3.894f, 12.093f, 4.19f) + lineTo(12.129f, 4.319f) + curveTo(12.215f, 4.663f, 12.223f, 5.022f, 12.153f, 5.37f) + lineTo(11.656f, 7.856f) + horizontalLineTo(15.336f) + lineTo(15.465f, 7.861f) + curveTo(16.101f, 7.898f, 16.68f, 8.209f, 17.061f, 8.73f) + curveTo(17.237f, 8.966f, 17.361f, 9.236f, 17.427f, 9.521f) + lineTo(17.451f, 9.645f) + curveTo(17.507f, 9.977f, 17.483f, 10.317f, 17.381f, 10.638f) + lineTo(15.708f, 15.995f) + curveTo(15.443f, 16.839f, 14.705f, 17.423f, 13.837f, 17.493f) + lineTo(13.661f, 17.5f) + close() + moveTo(13.664f, 16.428f) + curveTo(13.892f, 16.428f, 14.115f, 16.355f, 14.298f, 16.22f) + lineTo(14.365f, 16.166f) + curveTo(14.515f, 16.036f, 14.626f, 15.867f, 14.686f, 15.676f) + lineTo(16.36f, 10.319f) + curveTo(16.41f, 10.159f, 16.421f, 9.988f, 16.394f, 9.823f) + curveTo(16.366f, 9.657f, 16.3f, 9.5f, 16.201f, 9.364f) + curveTo(16.102f, 9.228f, 15.972f, 9.117f, 15.822f, 9.041f) + curveTo(15.672f, 8.965f, 15.506f, 8.926f, 15.337f, 8.927f) + horizontalLineTo(11.003f) + lineTo(10.944f, 8.923f) + curveTo(10.885f, 8.917f, 10.827f, 8.901f, 10.774f, 8.875f) + curveTo(10.702f, 8.841f, 10.638f, 8.793f, 10.588f, 8.731f) + curveTo(10.538f, 8.67f, 10.502f, 8.598f, 10.482f, 8.521f) + curveTo(10.463f, 8.444f, 10.461f, 8.364f, 10.477f, 8.286f) + lineTo(11.102f, 5.16f) + lineTo(11.124f, 5.014f) + curveTo(11.137f, 4.868f, 11.126f, 4.72f, 11.09f, 4.577f) + curveTo(11.043f, 4.386f, 10.953f, 4.207f, 10.828f, 4.055f) + curveTo(10.703f, 3.903f, 10.546f, 3.781f, 10.368f, 3.697f) + curveTo(10.19f, 3.613f, 9.996f, 3.57f, 9.799f, 3.57f) + lineTo(9.292f, 4.983f) + horizontalLineTo(9.296f) + curveTo(8.695f, 6.646f, 7.858f, 8.215f, 6.81f, 9.639f) + verticalLineTo(16.428f) + horizontalLineTo(13.664f) + close() + moveTo(5.735f, 16.428f) + verticalLineTo(9.999f) + horizontalLineTo(4.128f) + lineTo(4.023f, 10.01f) + curveTo(3.954f, 10.024f, 3.889f, 10.051f, 3.831f, 10.09f) + lineTo(3.75f, 10.156f) + curveTo(3.649f, 10.257f, 3.592f, 10.393f, 3.592f, 10.535f) + verticalLineTo(15.893f) + curveTo(3.592f, 16.035f, 3.649f, 16.171f, 3.75f, 16.272f) + curveTo(3.825f, 16.347f, 3.92f, 16.398f, 4.023f, 16.419f) + lineTo(4.128f, 16.428f) + horizontalLineTo(5.735f) + close() + } + } + .build() + return _recordBest!! + } + +private var _recordBest: ImageVector? = null + +@Preview +@Composable +private fun Preview(): Unit { + Box(modifier = Modifier.padding(12.dp)) { + Image(imageVector = AppIcons.RecordBest, contentDescription = "") + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/RecordBookmark.kt b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/RecordBookmark.kt new file mode 100644 index 0000000..aafb923 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/RecordBookmark.kt @@ -0,0 +1,178 @@ +package com.min.dnapp.presentation.ui.icon.appicons + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType +import androidx.compose.ui.graphics.PathFillType.Companion.NonZero +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.StrokeCap.Companion.Butt +import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.ImageVector.Builder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.min.dnapp.presentation.ui.icon.AppIcons +import kotlin.Unit + +public val AppIcons.RecordBookmark: ImageVector + get() { + if (_recordBookmark != null) { + return _recordBookmark!! + } + _recordBookmark = Builder(name = "RecordBookmark", defaultWidth = 20.0.dp, defaultHeight = + 20.0.dp, viewportWidth = 20.0f, viewportHeight = 20.0f).apply { + path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(10.0f, 13.395f) + curveTo(10.391f, 13.395f, 10.733f, 13.532f, 11.086f, 13.748f) + curveTo(11.43f, 13.958f, 11.822f, 14.267f, 12.306f, 14.649f) + lineTo(12.866f, 15.092f) + lineTo(13.484f, 15.578f) + curveTo(14.057f, 16.025f, 14.503f, 16.356f, 14.872f, 16.569f) + curveTo(15.369f, 16.855f, 15.598f, 16.851f, 15.755f, 16.775f) + lineTo(15.812f, 16.741f) + curveTo(15.945f, 16.648f, 16.065f, 16.449f, 16.138f, 15.951f) + curveTo(16.22f, 15.387f, 16.221f, 14.586f, 16.221f, 13.454f) + verticalLineTo(7.764f) + curveTo(16.221f, 6.574f, 16.22f, 5.722f, 16.134f, 5.076f) + curveTo(16.059f, 4.521f, 15.927f, 4.164f, 15.712f, 3.899f) + lineTo(15.614f, 3.791f) + curveTo(15.34f, 3.515f, 14.966f, 3.354f, 14.335f, 3.268f) + curveTo(13.691f, 3.181f, 12.844f, 3.181f, 11.659f, 3.181f) + horizontalLineTo(8.341f) + curveTo(7.156f, 3.181f, 6.309f, 3.181f, 5.665f, 3.268f) + curveTo(5.113f, 3.343f, 4.758f, 3.476f, 4.494f, 3.692f) + lineTo(4.386f, 3.791f) + curveTo(4.112f, 4.066f, 3.951f, 4.441f, 3.866f, 5.076f) + curveTo(3.78f, 5.722f, 3.779f, 6.574f, 3.779f, 7.764f) + verticalLineTo(13.454f) + curveTo(3.779f, 14.585f, 3.78f, 15.387f, 3.862f, 15.951f) + curveTo(3.945f, 16.521f, 4.09f, 16.699f, 4.245f, 16.775f) + curveTo(4.401f, 16.851f, 4.631f, 16.855f, 5.127f, 16.569f) + curveTo(5.619f, 16.285f, 6.248f, 15.791f, 7.134f, 15.091f) + lineTo(7.694f, 14.649f) + curveTo(8.178f, 14.267f, 8.57f, 13.958f, 8.914f, 13.748f) + curveTo(9.267f, 13.532f, 9.609f, 13.395f, 10.0f, 13.395f) + close() + moveTo(17.051f, 13.454f) + curveTo(17.051f, 14.558f, 17.052f, 15.433f, 16.959f, 16.072f) + curveTo(16.867f, 16.706f, 16.662f, 17.258f, 16.118f, 17.525f) + horizontalLineTo(16.117f) + curveTo(15.572f, 17.79f, 15.014f, 17.611f, 14.46f, 17.292f) + curveTo(14.042f, 17.051f, 13.552f, 16.686f, 12.969f, 16.231f) + lineTo(12.353f, 15.747f) + lineTo(11.793f, 15.304f) + curveTo(11.294f, 14.91f, 10.947f, 14.638f, 10.655f, 14.46f) + curveTo(10.373f, 14.287f, 10.182f, 14.229f, 10.0f, 14.229f) + curveTo(9.818f, 14.229f, 9.627f, 14.287f, 9.345f, 14.46f) + curveTo(9.053f, 14.638f, 8.706f, 14.91f, 8.206f, 15.304f) + verticalLineTo(15.305f) + lineTo(7.646f, 15.746f) + curveTo(6.782f, 16.428f, 6.097f, 16.97f, 5.54f, 17.292f) + curveTo(4.986f, 17.611f, 4.428f, 17.79f, 3.883f, 17.525f) + curveTo(3.339f, 17.259f, 3.133f, 16.706f, 3.041f, 16.072f) + curveTo(2.948f, 15.433f, 2.949f, 14.557f, 2.949f, 13.454f) + verticalLineTo(7.764f) + curveTo(2.949f, 6.597f, 2.948f, 5.68f, 3.044f, 4.965f) + curveTo(3.141f, 4.238f, 3.345f, 3.658f, 3.8f, 3.202f) + lineTo(3.976f, 3.042f) + curveTo(4.4f, 2.696f, 4.922f, 2.528f, 5.555f, 2.442f) + curveTo(6.267f, 2.346f, 7.18f, 2.347f, 8.341f, 2.347f) + horizontalLineTo(11.659f) + curveTo(12.82f, 2.347f, 13.733f, 2.346f, 14.445f, 2.442f) + curveTo(15.168f, 2.54f, 15.746f, 2.745f, 16.2f, 3.202f) + lineTo(16.359f, 3.378f) + curveTo(16.704f, 3.805f, 16.871f, 4.329f, 16.956f, 4.965f) + curveTo(17.052f, 5.68f, 17.051f, 6.597f, 17.051f, 7.764f) + verticalLineTo(13.454f) + close() + } + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, fillAlpha = 0.6f, + strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, + strokeLineMiter = 4.0f, pathFillType = NonZero) { + moveTo(10.0f, 13.395f) + curveTo(10.391f, 13.395f, 10.733f, 13.532f, 11.086f, 13.748f) + curveTo(11.43f, 13.958f, 11.822f, 14.267f, 12.306f, 14.649f) + lineTo(12.866f, 15.092f) + lineTo(13.484f, 15.578f) + curveTo(14.057f, 16.025f, 14.503f, 16.356f, 14.872f, 16.569f) + curveTo(15.369f, 16.855f, 15.598f, 16.851f, 15.755f, 16.775f) + lineTo(15.812f, 16.741f) + curveTo(15.945f, 16.648f, 16.065f, 16.449f, 16.138f, 15.951f) + curveTo(16.22f, 15.387f, 16.221f, 14.586f, 16.221f, 13.454f) + verticalLineTo(7.764f) + curveTo(16.221f, 6.574f, 16.22f, 5.722f, 16.134f, 5.076f) + curveTo(16.059f, 4.521f, 15.927f, 4.164f, 15.712f, 3.899f) + lineTo(15.614f, 3.791f) + curveTo(15.34f, 3.515f, 14.966f, 3.354f, 14.335f, 3.268f) + curveTo(13.691f, 3.181f, 12.844f, 3.181f, 11.659f, 3.181f) + horizontalLineTo(8.341f) + curveTo(7.156f, 3.181f, 6.309f, 3.181f, 5.665f, 3.268f) + curveTo(5.113f, 3.343f, 4.758f, 3.476f, 4.494f, 3.692f) + lineTo(4.386f, 3.791f) + curveTo(4.112f, 4.066f, 3.951f, 4.441f, 3.866f, 5.076f) + curveTo(3.78f, 5.722f, 3.779f, 6.574f, 3.779f, 7.764f) + verticalLineTo(13.454f) + curveTo(3.779f, 14.585f, 3.78f, 15.387f, 3.862f, 15.951f) + curveTo(3.945f, 16.521f, 4.09f, 16.699f, 4.245f, 16.775f) + curveTo(4.401f, 16.851f, 4.631f, 16.855f, 5.127f, 16.569f) + curveTo(5.619f, 16.285f, 6.248f, 15.791f, 7.134f, 15.091f) + lineTo(7.694f, 14.649f) + curveTo(8.178f, 14.267f, 8.57f, 13.958f, 8.914f, 13.748f) + curveTo(9.267f, 13.532f, 9.609f, 13.395f, 10.0f, 13.395f) + close() + moveTo(17.051f, 13.454f) + curveTo(17.051f, 14.558f, 17.052f, 15.433f, 16.959f, 16.072f) + curveTo(16.867f, 16.706f, 16.662f, 17.258f, 16.118f, 17.525f) + horizontalLineTo(16.117f) + curveTo(15.572f, 17.79f, 15.014f, 17.611f, 14.46f, 17.292f) + curveTo(14.042f, 17.051f, 13.552f, 16.686f, 12.969f, 16.231f) + lineTo(12.353f, 15.747f) + lineTo(11.793f, 15.304f) + curveTo(11.294f, 14.91f, 10.947f, 14.638f, 10.655f, 14.46f) + curveTo(10.373f, 14.287f, 10.182f, 14.229f, 10.0f, 14.229f) + curveTo(9.818f, 14.229f, 9.627f, 14.287f, 9.345f, 14.46f) + curveTo(9.053f, 14.638f, 8.706f, 14.91f, 8.206f, 15.304f) + verticalLineTo(15.305f) + lineTo(7.646f, 15.746f) + curveTo(6.782f, 16.428f, 6.097f, 16.97f, 5.54f, 17.292f) + curveTo(4.986f, 17.611f, 4.428f, 17.79f, 3.883f, 17.525f) + curveTo(3.339f, 17.259f, 3.133f, 16.706f, 3.041f, 16.072f) + curveTo(2.948f, 15.433f, 2.949f, 14.557f, 2.949f, 13.454f) + verticalLineTo(7.764f) + curveTo(2.949f, 6.597f, 2.948f, 5.68f, 3.044f, 4.965f) + curveTo(3.141f, 4.238f, 3.345f, 3.658f, 3.8f, 3.202f) + lineTo(3.976f, 3.042f) + curveTo(4.4f, 2.696f, 4.922f, 2.528f, 5.555f, 2.442f) + curveTo(6.267f, 2.346f, 7.18f, 2.347f, 8.341f, 2.347f) + horizontalLineTo(11.659f) + curveTo(12.82f, 2.347f, 13.733f, 2.346f, 14.445f, 2.442f) + curveTo(15.168f, 2.54f, 15.746f, 2.745f, 16.2f, 3.202f) + lineTo(16.359f, 3.378f) + curveTo(16.704f, 3.805f, 16.871f, 4.329f, 16.956f, 4.965f) + curveTo(17.052f, 5.68f, 17.051f, 6.597f, 17.051f, 7.764f) + verticalLineTo(13.454f) + close() + } + } + .build() + return _recordBookmark!! + } + +private var _recordBookmark: ImageVector? = null + +@Preview +@Composable +private fun Preview(): Unit { + Box(modifier = Modifier.padding(12.dp)) { + Image(imageVector = AppIcons.RecordBookmark, contentDescription = "") + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/RecordComment.kt b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/RecordComment.kt new file mode 100644 index 0000000..bbf3388 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/RecordComment.kt @@ -0,0 +1,210 @@ +package com.min.dnapp.presentation.ui.icon.appicons + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType +import androidx.compose.ui.graphics.PathFillType.Companion.NonZero +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.StrokeCap.Companion.Butt +import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.ImageVector.Builder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.min.dnapp.presentation.ui.icon.AppIcons +import kotlin.Unit + +public val AppIcons.RecordComment: ImageVector + get() { + if (_recordComment != null) { + return _recordComment!! + } + _recordComment = Builder(name = "RecordComment", defaultWidth = 20.0.dp, defaultHeight = + 20.0.dp, viewportWidth = 20.0f, viewportHeight = 20.0f).apply { + path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(16.25f, 9.23f) + curveTo(16.25f, 8.051f, 16.249f, 7.198f, 16.182f, 6.534f) + curveTo(16.115f, 5.877f, 15.986f, 5.45f, 15.759f, 5.11f) + curveTo(15.546f, 4.791f, 15.272f, 4.518f, 14.954f, 4.305f) + curveTo(14.613f, 4.077f, 14.187f, 3.949f, 13.53f, 3.882f) + curveTo(12.865f, 3.814f, 12.012f, 3.813f, 10.833f, 3.813f) + horizontalLineTo(9.167f) + curveTo(7.987f, 3.813f, 7.135f, 3.814f, 6.471f, 3.882f) + curveTo(5.814f, 3.949f, 5.387f, 4.077f, 5.046f, 4.305f) + curveTo(4.728f, 4.518f, 4.454f, 4.791f, 4.242f, 5.11f) + curveTo(4.014f, 5.45f, 3.885f, 5.877f, 3.818f, 6.534f) + curveTo(3.751f, 7.198f, 3.75f, 8.051f, 3.75f, 9.23f) + curveTo(3.75f, 10.409f, 3.751f, 11.262f, 3.818f, 11.926f) + curveTo(3.885f, 12.583f, 4.014f, 13.01f, 4.242f, 13.35f) + curveTo(4.454f, 13.669f, 4.728f, 13.942f, 5.046f, 14.155f) + curveTo(5.344f, 14.354f, 5.708f, 14.478f, 6.233f, 14.551f) + curveTo(6.765f, 14.624f, 7.434f, 14.642f, 8.335f, 14.647f) + curveTo(8.492f, 14.647f, 8.636f, 14.736f, 8.706f, 14.877f) + lineTo(9.627f, 16.719f) + lineTo(9.657f, 16.769f) + curveTo(9.689f, 16.816f, 9.732f, 16.857f, 9.781f, 16.887f) + curveTo(9.847f, 16.928f, 9.923f, 16.949f, 10.0f, 16.949f) + curveTo(10.077f, 16.949f, 10.153f, 16.928f, 10.219f, 16.887f) + curveTo(10.285f, 16.847f, 10.338f, 16.788f, 10.373f, 16.719f) + lineTo(11.294f, 14.876f) + curveTo(11.364f, 14.736f, 11.508f, 14.647f, 11.665f, 14.646f) + curveTo(12.566f, 14.642f, 13.236f, 14.624f, 13.768f, 14.551f) + curveTo(14.293f, 14.478f, 14.656f, 14.354f, 14.954f, 14.155f) + lineTo(15.071f, 14.073f) + curveTo(15.34f, 13.874f, 15.572f, 13.629f, 15.759f, 13.35f) + curveTo(15.986f, 13.01f, 16.115f, 12.583f, 16.182f, 11.926f) + curveTo(16.249f, 11.262f, 16.25f, 10.409f, 16.25f, 9.23f) + close() + moveTo(10.0f, 10.48f) + curveTo(10.23f, 10.48f, 10.417f, 10.667f, 10.417f, 10.897f) + curveTo(10.417f, 11.127f, 10.23f, 11.313f, 10.0f, 11.313f) + horizontalLineTo(7.5f) + curveTo(7.27f, 11.313f, 7.083f, 11.127f, 7.083f, 10.897f) + curveTo(7.083f, 10.667f, 7.27f, 10.48f, 7.5f, 10.48f) + horizontalLineTo(10.0f) + close() + moveTo(12.5f, 7.147f) + curveTo(12.73f, 7.147f, 12.917f, 7.333f, 12.917f, 7.563f) + curveTo(12.917f, 7.793f, 12.73f, 7.98f, 12.5f, 7.98f) + horizontalLineTo(7.5f) + curveTo(7.27f, 7.98f, 7.083f, 7.793f, 7.083f, 7.563f) + curveTo(7.083f, 7.333f, 7.27f, 7.147f, 7.5f, 7.147f) + horizontalLineTo(12.5f) + close() + moveTo(17.083f, 9.23f) + curveTo(17.083f, 10.392f, 17.084f, 11.295f, 17.011f, 12.011f) + curveTo(16.937f, 12.734f, 16.786f, 13.313f, 16.452f, 13.813f) + curveTo(16.178f, 14.223f, 15.826f, 14.575f, 15.417f, 14.849f) + lineTo(15.416f, 14.848f) + curveTo(14.979f, 15.14f, 14.483f, 15.293f, 13.882f, 15.376f) + curveTo(13.346f, 15.45f, 12.705f, 15.469f, 11.925f, 15.476f) + lineTo(11.117f, 17.092f) + curveTo(11.014f, 17.299f, 10.854f, 17.474f, 10.657f, 17.596f) + curveTo(10.46f, 17.718f, 10.232f, 17.782f, 10.0f, 17.782f) + curveTo(9.768f, 17.782f, 9.541f, 17.718f, 9.343f, 17.596f) + curveTo(9.146f, 17.474f, 8.987f, 17.299f, 8.883f, 17.092f) + lineTo(8.075f, 15.477f) + curveTo(7.295f, 15.47f, 6.655f, 15.45f, 6.118f, 15.376f) + curveTo(5.517f, 15.292f, 5.02f, 15.14f, 4.583f, 14.849f) + curveTo(4.174f, 14.575f, 3.822f, 14.223f, 3.548f, 13.813f) + curveTo(3.214f, 13.313f, 3.063f, 12.734f, 2.989f, 12.011f) + curveTo(2.916f, 11.295f, 2.917f, 10.392f, 2.917f, 9.23f) + curveTo(2.917f, 8.068f, 2.916f, 7.165f, 2.989f, 6.449f) + curveTo(3.063f, 5.726f, 3.214f, 5.147f, 3.548f, 4.647f) + curveTo(3.822f, 4.237f, 4.174f, 3.885f, 4.583f, 3.611f) + curveTo(5.084f, 3.277f, 5.663f, 3.126f, 6.386f, 3.052f) + curveTo(7.102f, 2.98f, 8.005f, 2.98f, 9.167f, 2.98f) + horizontalLineTo(10.833f) + curveTo(11.995f, 2.98f, 12.898f, 2.98f, 13.614f, 3.052f) + curveTo(14.337f, 3.126f, 14.917f, 3.277f, 15.417f, 3.611f) + curveTo(15.826f, 3.885f, 16.178f, 4.237f, 16.452f, 4.647f) + curveTo(16.786f, 5.147f, 16.937f, 5.726f, 17.011f, 6.449f) + curveTo(17.084f, 7.165f, 17.083f, 8.068f, 17.083f, 9.23f) + close() + } + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, fillAlpha = 0.6f, + strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, + strokeLineMiter = 4.0f, pathFillType = NonZero) { + moveTo(16.25f, 9.23f) + curveTo(16.25f, 8.051f, 16.249f, 7.198f, 16.182f, 6.534f) + curveTo(16.115f, 5.877f, 15.986f, 5.45f, 15.759f, 5.11f) + curveTo(15.546f, 4.791f, 15.272f, 4.518f, 14.954f, 4.305f) + curveTo(14.613f, 4.077f, 14.187f, 3.949f, 13.53f, 3.882f) + curveTo(12.865f, 3.814f, 12.012f, 3.813f, 10.833f, 3.813f) + horizontalLineTo(9.167f) + curveTo(7.987f, 3.813f, 7.135f, 3.814f, 6.471f, 3.882f) + curveTo(5.814f, 3.949f, 5.387f, 4.077f, 5.046f, 4.305f) + curveTo(4.728f, 4.518f, 4.454f, 4.791f, 4.242f, 5.11f) + curveTo(4.014f, 5.45f, 3.885f, 5.877f, 3.818f, 6.534f) + curveTo(3.751f, 7.198f, 3.75f, 8.051f, 3.75f, 9.23f) + curveTo(3.75f, 10.409f, 3.751f, 11.262f, 3.818f, 11.926f) + curveTo(3.885f, 12.583f, 4.014f, 13.01f, 4.242f, 13.35f) + curveTo(4.454f, 13.669f, 4.728f, 13.942f, 5.046f, 14.155f) + curveTo(5.344f, 14.354f, 5.708f, 14.478f, 6.233f, 14.551f) + curveTo(6.765f, 14.624f, 7.434f, 14.642f, 8.335f, 14.647f) + curveTo(8.492f, 14.647f, 8.636f, 14.736f, 8.706f, 14.877f) + lineTo(9.627f, 16.719f) + lineTo(9.657f, 16.769f) + curveTo(9.689f, 16.816f, 9.732f, 16.857f, 9.781f, 16.887f) + curveTo(9.847f, 16.928f, 9.923f, 16.949f, 10.0f, 16.949f) + curveTo(10.077f, 16.949f, 10.153f, 16.928f, 10.219f, 16.887f) + curveTo(10.285f, 16.847f, 10.338f, 16.788f, 10.373f, 16.719f) + lineTo(11.294f, 14.876f) + curveTo(11.364f, 14.736f, 11.508f, 14.647f, 11.665f, 14.646f) + curveTo(12.566f, 14.642f, 13.236f, 14.624f, 13.768f, 14.551f) + curveTo(14.293f, 14.478f, 14.656f, 14.354f, 14.954f, 14.155f) + lineTo(15.071f, 14.073f) + curveTo(15.34f, 13.874f, 15.572f, 13.629f, 15.759f, 13.35f) + curveTo(15.986f, 13.01f, 16.115f, 12.583f, 16.182f, 11.926f) + curveTo(16.249f, 11.262f, 16.25f, 10.409f, 16.25f, 9.23f) + close() + moveTo(10.0f, 10.48f) + curveTo(10.23f, 10.48f, 10.417f, 10.667f, 10.417f, 10.897f) + curveTo(10.417f, 11.127f, 10.23f, 11.313f, 10.0f, 11.313f) + horizontalLineTo(7.5f) + curveTo(7.27f, 11.313f, 7.083f, 11.127f, 7.083f, 10.897f) + curveTo(7.083f, 10.667f, 7.27f, 10.48f, 7.5f, 10.48f) + horizontalLineTo(10.0f) + close() + moveTo(12.5f, 7.147f) + curveTo(12.73f, 7.147f, 12.917f, 7.333f, 12.917f, 7.563f) + curveTo(12.917f, 7.793f, 12.73f, 7.98f, 12.5f, 7.98f) + horizontalLineTo(7.5f) + curveTo(7.27f, 7.98f, 7.083f, 7.793f, 7.083f, 7.563f) + curveTo(7.083f, 7.333f, 7.27f, 7.147f, 7.5f, 7.147f) + horizontalLineTo(12.5f) + close() + moveTo(17.083f, 9.23f) + curveTo(17.083f, 10.392f, 17.084f, 11.295f, 17.011f, 12.011f) + curveTo(16.937f, 12.734f, 16.786f, 13.313f, 16.452f, 13.813f) + curveTo(16.178f, 14.223f, 15.826f, 14.575f, 15.417f, 14.849f) + lineTo(15.416f, 14.848f) + curveTo(14.979f, 15.14f, 14.483f, 15.293f, 13.882f, 15.376f) + curveTo(13.346f, 15.45f, 12.705f, 15.469f, 11.925f, 15.476f) + lineTo(11.117f, 17.092f) + curveTo(11.014f, 17.299f, 10.854f, 17.474f, 10.657f, 17.596f) + curveTo(10.46f, 17.718f, 10.232f, 17.782f, 10.0f, 17.782f) + curveTo(9.768f, 17.782f, 9.541f, 17.718f, 9.343f, 17.596f) + curveTo(9.146f, 17.474f, 8.987f, 17.299f, 8.883f, 17.092f) + lineTo(8.075f, 15.477f) + curveTo(7.295f, 15.47f, 6.655f, 15.45f, 6.118f, 15.376f) + curveTo(5.517f, 15.292f, 5.02f, 15.14f, 4.583f, 14.849f) + curveTo(4.174f, 14.575f, 3.822f, 14.223f, 3.548f, 13.813f) + curveTo(3.214f, 13.313f, 3.063f, 12.734f, 2.989f, 12.011f) + curveTo(2.916f, 11.295f, 2.917f, 10.392f, 2.917f, 9.23f) + curveTo(2.917f, 8.068f, 2.916f, 7.165f, 2.989f, 6.449f) + curveTo(3.063f, 5.726f, 3.214f, 5.147f, 3.548f, 4.647f) + curveTo(3.822f, 4.237f, 4.174f, 3.885f, 4.583f, 3.611f) + curveTo(5.084f, 3.277f, 5.663f, 3.126f, 6.386f, 3.052f) + curveTo(7.102f, 2.98f, 8.005f, 2.98f, 9.167f, 2.98f) + horizontalLineTo(10.833f) + curveTo(11.995f, 2.98f, 12.898f, 2.98f, 13.614f, 3.052f) + curveTo(14.337f, 3.126f, 14.917f, 3.277f, 15.417f, 3.611f) + curveTo(15.826f, 3.885f, 16.178f, 4.237f, 16.452f, 4.647f) + curveTo(16.786f, 5.147f, 16.937f, 5.726f, 17.011f, 6.449f) + curveTo(17.084f, 7.165f, 17.083f, 8.068f, 17.083f, 9.23f) + close() + } + } + .build() + return _recordComment!! + } + +private var _recordComment: ImageVector? = null + +@Preview +@Composable +private fun Preview(): Unit { + Box(modifier = Modifier.padding(12.dp)) { + Image(imageVector = AppIcons.RecordComment, contentDescription = "") + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/RecordLike.kt b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/RecordLike.kt new file mode 100644 index 0000000..46408e5 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/RecordLike.kt @@ -0,0 +1,148 @@ +package com.min.dnapp.presentation.ui.icon.appicons + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType +import androidx.compose.ui.graphics.PathFillType.Companion.NonZero +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.StrokeCap.Companion.Butt +import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.ImageVector.Builder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.min.dnapp.presentation.ui.icon.AppIcons +import kotlin.Unit + +public val AppIcons.RecordLike: ImageVector + get() { + if (_recordLike != null) { + return _recordLike!! + } + _recordLike = Builder(name = "RecordLike", defaultWidth = 20.0.dp, defaultHeight = 20.0.dp, + viewportWidth = 20.0f, viewportHeight = 20.0f).apply { + path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(3.127f, 4.487f) + curveTo(4.971f, 2.128f, 8.674f, 2.524f, 9.97f, 5.219f) + curveTo(9.973f, 5.225f, 9.976f, 5.23f, 9.981f, 5.233f) + curveTo(9.987f, 5.236f, 9.993f, 5.238f, 9.999f, 5.238f) + curveTo(10.006f, 5.238f, 10.012f, 5.236f, 10.017f, 5.233f) + curveTo(10.023f, 5.23f, 10.027f, 5.225f, 10.03f, 5.219f) + lineTo(10.158f, 4.973f) + curveTo(11.555f, 2.502f, 15.086f, 2.203f, 16.873f, 4.487f) + lineTo(17.133f, 4.819f) + curveTo(17.864f, 5.754f, 18.217f, 6.927f, 18.123f, 8.106f) + curveTo(18.03f, 9.286f, 17.497f, 10.388f, 16.627f, 11.198f) + lineTo(10.788f, 16.64f) + curveTo(10.693f, 16.728f, 10.601f, 16.816f, 10.516f, 16.881f) + curveTo(10.425f, 16.951f, 10.31f, 17.021f, 10.162f, 17.05f) + curveTo(10.055f, 17.071f, 9.944f, 17.071f, 9.837f, 17.05f) + horizontalLineTo(9.835f) + curveTo(9.687f, 17.021f, 9.572f, 16.949f, 9.483f, 16.88f) + curveTo(9.398f, 16.815f, 9.306f, 16.728f, 9.211f, 16.64f) + lineTo(2.598f, 10.477f) + horizontalLineTo(2.741f) + curveTo(2.247f, 9.783f, 1.944f, 8.965f, 1.876f, 8.106f) + curveTo(1.783f, 6.927f, 2.136f, 5.756f, 2.866f, 4.821f) + lineTo(3.127f, 4.487f) + close() + moveTo(9.211f, 5.578f) + curveTo(8.216f, 3.51f, 5.433f, 3.151f, 3.931f, 4.829f) + lineTo(3.79f, 4.998f) + lineTo(3.53f, 5.331f) + curveTo(2.928f, 6.102f, 2.637f, 7.068f, 2.714f, 8.04f) + curveTo(2.791f, 9.013f, 3.231f, 9.922f, 3.947f, 10.59f) + lineTo(4.72f, 11.31f) + lineTo(9.784f, 16.032f) + curveTo(9.891f, 16.132f, 9.951f, 16.186f, 9.996f, 16.221f) + curveTo(9.997f, 16.222f, 9.998f, 16.222f, 9.999f, 16.222f) + curveTo(10.0f, 16.222f, 10.002f, 16.222f, 10.003f, 16.221f) + curveTo(10.047f, 16.187f, 10.105f, 16.133f, 10.212f, 16.034f) + lineTo(10.213f, 16.033f) + lineTo(16.052f, 10.591f) + curveTo(16.769f, 9.923f, 17.21f, 9.014f, 17.287f, 8.041f) + curveTo(17.364f, 7.069f, 17.072f, 6.101f, 16.469f, 5.331f) + lineTo(16.209f, 4.998f) + curveTo(14.748f, 3.13f, 11.815f, 3.443f, 10.788f, 5.578f) + curveTo(10.716f, 5.725f, 10.604f, 5.851f, 10.465f, 5.938f) + curveTo(10.326f, 6.025f, 10.164f, 6.071f, 9.999f, 6.071f) + curveTo(9.835f, 6.071f, 9.674f, 6.025f, 9.534f, 5.938f) + curveTo(9.395f, 5.851f, 9.283f, 5.726f, 9.212f, 5.579f) + lineTo(9.211f, 5.578f) + close() + } + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, fillAlpha = 0.6f, + strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, + strokeLineMiter = 4.0f, pathFillType = NonZero) { + moveTo(3.127f, 4.487f) + curveTo(4.971f, 2.128f, 8.674f, 2.524f, 9.97f, 5.219f) + curveTo(9.973f, 5.225f, 9.976f, 5.23f, 9.981f, 5.233f) + curveTo(9.987f, 5.236f, 9.993f, 5.238f, 9.999f, 5.238f) + curveTo(10.006f, 5.238f, 10.012f, 5.236f, 10.017f, 5.233f) + curveTo(10.023f, 5.23f, 10.027f, 5.225f, 10.03f, 5.219f) + lineTo(10.158f, 4.973f) + curveTo(11.555f, 2.502f, 15.086f, 2.203f, 16.873f, 4.487f) + lineTo(17.133f, 4.819f) + curveTo(17.864f, 5.754f, 18.217f, 6.927f, 18.123f, 8.106f) + curveTo(18.03f, 9.286f, 17.497f, 10.388f, 16.627f, 11.198f) + lineTo(10.788f, 16.64f) + curveTo(10.693f, 16.728f, 10.601f, 16.816f, 10.516f, 16.881f) + curveTo(10.425f, 16.951f, 10.31f, 17.021f, 10.162f, 17.05f) + curveTo(10.055f, 17.071f, 9.944f, 17.071f, 9.837f, 17.05f) + horizontalLineTo(9.835f) + curveTo(9.687f, 17.021f, 9.572f, 16.949f, 9.483f, 16.88f) + curveTo(9.398f, 16.815f, 9.306f, 16.728f, 9.211f, 16.64f) + lineTo(2.598f, 10.477f) + horizontalLineTo(2.741f) + curveTo(2.247f, 9.783f, 1.944f, 8.965f, 1.876f, 8.106f) + curveTo(1.783f, 6.927f, 2.136f, 5.756f, 2.866f, 4.821f) + lineTo(3.127f, 4.487f) + close() + moveTo(9.211f, 5.578f) + curveTo(8.216f, 3.51f, 5.433f, 3.151f, 3.931f, 4.829f) + lineTo(3.79f, 4.998f) + lineTo(3.53f, 5.331f) + curveTo(2.928f, 6.102f, 2.637f, 7.068f, 2.714f, 8.04f) + curveTo(2.791f, 9.013f, 3.231f, 9.922f, 3.947f, 10.59f) + lineTo(4.72f, 11.31f) + lineTo(9.784f, 16.032f) + curveTo(9.891f, 16.132f, 9.951f, 16.186f, 9.996f, 16.221f) + curveTo(9.997f, 16.222f, 9.998f, 16.222f, 9.999f, 16.222f) + curveTo(10.0f, 16.222f, 10.002f, 16.222f, 10.003f, 16.221f) + curveTo(10.047f, 16.187f, 10.105f, 16.133f, 10.212f, 16.034f) + lineTo(10.213f, 16.033f) + lineTo(16.052f, 10.591f) + curveTo(16.769f, 9.923f, 17.21f, 9.014f, 17.287f, 8.041f) + curveTo(17.364f, 7.069f, 17.072f, 6.101f, 16.469f, 5.331f) + lineTo(16.209f, 4.998f) + curveTo(14.748f, 3.13f, 11.815f, 3.443f, 10.788f, 5.578f) + curveTo(10.716f, 5.725f, 10.604f, 5.851f, 10.465f, 5.938f) + curveTo(10.326f, 6.025f, 10.164f, 6.071f, 9.999f, 6.071f) + curveTo(9.835f, 6.071f, 9.674f, 6.025f, 9.534f, 5.938f) + curveTo(9.395f, 5.851f, 9.283f, 5.726f, 9.212f, 5.579f) + lineTo(9.211f, 5.578f) + close() + } + } + .build() + return _recordLike!! + } + +private var _recordLike: ImageVector? = null + +@Preview +@Composable +private fun Preview(): Unit { + Box(modifier = Modifier.padding(12.dp)) { + Image(imageVector = AppIcons.RecordLike, contentDescription = "") + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/RecordSurprise.kt b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/RecordSurprise.kt new file mode 100644 index 0000000..1564609 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/RecordSurprise.kt @@ -0,0 +1,148 @@ +package com.min.dnapp.presentation.ui.icon.appicons + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType +import androidx.compose.ui.graphics.PathFillType.Companion.NonZero +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.StrokeCap.Companion.Butt +import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.ImageVector.Builder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.min.dnapp.presentation.ui.icon.AppIcons +import kotlin.Unit + +public val AppIcons.RecordSurprise: ImageVector + get() { + if (_recordSurprise != null) { + return _recordSurprise!! + } + _recordSurprise = Builder(name = "RecordSurprise", defaultWidth = 20.0.dp, defaultHeight = + 20.0.dp, viewportWidth = 20.0f, viewportHeight = 20.0f).apply { + path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(17.2f, 10.0f) + curveTo(17.2f, 6.024f, 13.976f, 2.8f, 10.0f, 2.8f) + curveTo(6.024f, 2.8f, 2.8f, 6.024f, 2.8f, 10.0f) + curveTo(2.8f, 13.976f, 6.024f, 17.2f, 10.0f, 17.2f) + curveTo(13.976f, 17.2f, 17.2f, 13.976f, 17.2f, 10.0f) + close() + moveTo(18.0f, 10.0f) + curveTo(18.0f, 14.418f, 14.418f, 18.0f, 10.0f, 18.0f) + curveTo(5.582f, 18.0f, 2.0f, 14.418f, 2.0f, 10.0f) + curveTo(2.0f, 5.582f, 5.582f, 2.0f, 10.0f, 2.0f) + curveTo(14.418f, 2.0f, 18.0f, 5.582f, 18.0f, 10.0f) + close() + } + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, fillAlpha = 0.6f, + strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, + strokeLineMiter = 4.0f, pathFillType = NonZero) { + moveTo(17.2f, 10.0f) + curveTo(17.2f, 6.024f, 13.976f, 2.8f, 10.0f, 2.8f) + curveTo(6.024f, 2.8f, 2.8f, 6.024f, 2.8f, 10.0f) + curveTo(2.8f, 13.976f, 6.024f, 17.2f, 10.0f, 17.2f) + curveTo(13.976f, 17.2f, 17.2f, 13.976f, 17.2f, 10.0f) + close() + moveTo(18.0f, 10.0f) + curveTo(18.0f, 14.418f, 14.418f, 18.0f, 10.0f, 18.0f) + curveTo(5.582f, 18.0f, 2.0f, 14.418f, 2.0f, 10.0f) + curveTo(2.0f, 5.582f, 5.582f, 2.0f, 10.0f, 2.0f) + curveTo(14.418f, 2.0f, 18.0f, 5.582f, 18.0f, 10.0f) + close() + } + path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(7.12f, 8.4f) + curveTo(7.562f, 8.4f, 7.92f, 8.042f, 7.92f, 7.6f) + curveTo(7.92f, 7.158f, 7.562f, 6.8f, 7.12f, 6.8f) + curveTo(6.678f, 6.8f, 6.32f, 7.158f, 6.32f, 7.6f) + curveTo(6.32f, 8.042f, 6.678f, 8.4f, 7.12f, 8.4f) + close() + } + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, fillAlpha = 0.6f, + strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, + strokeLineMiter = 4.0f, pathFillType = NonZero) { + moveTo(7.12f, 8.4f) + curveTo(7.562f, 8.4f, 7.92f, 8.042f, 7.92f, 7.6f) + curveTo(7.92f, 7.158f, 7.562f, 6.8f, 7.12f, 6.8f) + curveTo(6.678f, 6.8f, 6.32f, 7.158f, 6.32f, 7.6f) + curveTo(6.32f, 8.042f, 6.678f, 8.4f, 7.12f, 8.4f) + close() + } + path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(12.88f, 8.4f) + curveTo(13.322f, 8.4f, 13.68f, 8.042f, 13.68f, 7.6f) + curveTo(13.68f, 7.158f, 13.322f, 6.8f, 12.88f, 6.8f) + curveTo(12.438f, 6.8f, 12.08f, 7.158f, 12.08f, 7.6f) + curveTo(12.08f, 8.042f, 12.438f, 8.4f, 12.88f, 8.4f) + close() + } + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, fillAlpha = 0.6f, + strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, + strokeLineMiter = 4.0f, pathFillType = NonZero) { + moveTo(12.88f, 8.4f) + curveTo(13.322f, 8.4f, 13.68f, 8.042f, 13.68f, 7.6f) + curveTo(13.68f, 7.158f, 13.322f, 6.8f, 12.88f, 6.8f) + curveTo(12.438f, 6.8f, 12.08f, 7.158f, 12.08f, 7.6f) + curveTo(12.08f, 8.042f, 12.438f, 8.4f, 12.88f, 8.4f) + close() + } + path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(11.6f, 12.8f) + curveTo(11.6f, 12.268f, 11.026f, 11.6f, 10.0f, 11.6f) + curveTo(8.974f, 11.6f, 8.4f, 12.268f, 8.4f, 12.8f) + curveTo(8.4f, 13.332f, 8.974f, 14.0f, 10.0f, 14.0f) + verticalLineTo(14.8f) + curveTo(8.674f, 14.8f, 7.6f, 13.904f, 7.6f, 12.8f) + curveTo(7.6f, 11.696f, 8.674f, 10.8f, 10.0f, 10.8f) + curveTo(11.325f, 10.8f, 12.4f, 11.696f, 12.4f, 12.8f) + curveTo(12.4f, 13.904f, 11.325f, 14.8f, 10.0f, 14.8f) + verticalLineTo(14.0f) + curveTo(11.026f, 14.0f, 11.6f, 13.332f, 11.6f, 12.8f) + close() + } + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, fillAlpha = 0.6f, + strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, + strokeLineMiter = 4.0f, pathFillType = NonZero) { + moveTo(11.6f, 12.8f) + curveTo(11.6f, 12.268f, 11.026f, 11.6f, 10.0f, 11.6f) + curveTo(8.974f, 11.6f, 8.4f, 12.268f, 8.4f, 12.8f) + curveTo(8.4f, 13.332f, 8.974f, 14.0f, 10.0f, 14.0f) + verticalLineTo(14.8f) + curveTo(8.674f, 14.8f, 7.6f, 13.904f, 7.6f, 12.8f) + curveTo(7.6f, 11.696f, 8.674f, 10.8f, 10.0f, 10.8f) + curveTo(11.325f, 10.8f, 12.4f, 11.696f, 12.4f, 12.8f) + curveTo(12.4f, 13.904f, 11.325f, 14.8f, 10.0f, 14.8f) + verticalLineTo(14.0f) + curveTo(11.026f, 14.0f, 11.6f, 13.332f, 11.6f, 12.8f) + close() + } + } + .build() + return _recordSurprise!! + } + +private var _recordSurprise: ImageVector? = null + +@Preview +@Composable +private fun Preview(): Unit { + Box(modifier = Modifier.padding(12.dp)) { + Image(imageVector = AppIcons.RecordSurprise, contentDescription = "") + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/ShareTriangle.kt b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/ShareTriangle.kt new file mode 100644 index 0000000..caf6778 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/ShareTriangle.kt @@ -0,0 +1,62 @@ +package com.min.dnapp.presentation.ui.icon.appicons + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType +import androidx.compose.ui.graphics.PathFillType.Companion.NonZero +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.StrokeCap.Companion.Butt +import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.ImageVector.Builder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.min.dnapp.presentation.ui.icon.AppIcons +import kotlin.Unit + +public val AppIcons.ShareTriangle: ImageVector + get() { + if (_shareTriangle != null) { + return _shareTriangle!! + } + _shareTriangle = Builder(name = "ShareTriangle", defaultWidth = 12.0.dp, defaultHeight = + 8.0.dp, viewportWidth = 12.0f, viewportHeight = 8.0f).apply { + path(fill = SolidColor(Color(0xFF6E8B74)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(6.0f, 8.0f) + lineTo(12.0f, 0.0f) + horizontalLineTo(0.0f) + lineTo(6.0f, 8.0f) + close() + } + path(fill = SolidColor(Color(0xFFffffff)), stroke = null, fillAlpha = 0.6f, + strokeLineWidth = 0.0f, strokeLineCap = Butt, strokeLineJoin = Miter, + strokeLineMiter = 4.0f, pathFillType = NonZero) { + moveTo(6.0f, 8.0f) + lineTo(12.0f, 0.0f) + horizontalLineTo(0.0f) + lineTo(6.0f, 8.0f) + close() + } + } + .build() + return _shareTriangle!! + } + +private var _shareTriangle: ImageVector? = null + +@Preview +@Composable +private fun Preview(): Unit { + Box(modifier = Modifier.padding(12.dp)) { + Image(imageVector = AppIcons.ShareTriangle, contentDescription = "") + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/Year.kt b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/Year.kt new file mode 100644 index 0000000..7908cf6 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/ui/icon/appicons/Year.kt @@ -0,0 +1,100 @@ +package com.min.dnapp.presentation.ui.icon.appicons + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType +import androidx.compose.ui.graphics.PathFillType.Companion.NonZero +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.StrokeCap.Companion.Butt +import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.ImageVector.Builder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.min.dnapp.presentation.ui.icon.AppIcons +import kotlin.Unit + +public val AppIcons.Year: ImageVector + get() { + if (_year != null) { + return _year!! + } + _year = Builder(name = "Year", defaultWidth = 15.0.dp, defaultHeight = 18.0.dp, + viewportWidth = 15.0f, viewportHeight = 18.0f).apply { + path(fill = SolidColor(Color(0xFFA56C48)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(12.983f, 7.833f) + curveTo(12.983f, 7.465f, 12.685f, 7.167f, 12.317f, 7.167f) + horizontalLineTo(2.983f) + curveTo(2.615f, 7.167f, 2.317f, 7.465f, 2.317f, 7.833f) + verticalLineTo(15.167f) + curveTo(2.317f, 15.535f, 2.615f, 15.833f, 2.983f, 15.833f) + horizontalLineTo(12.317f) + curveTo(12.685f, 15.833f, 12.983f, 15.535f, 12.983f, 15.167f) + verticalLineTo(7.833f) + close() + moveTo(14.317f, 15.167f) + curveTo(14.317f, 16.271f, 13.421f, 17.167f, 12.317f, 17.167f) + horizontalLineTo(2.983f) + curveTo(1.879f, 17.167f, 0.983f, 16.271f, 0.983f, 15.167f) + verticalLineTo(7.833f) + curveTo(0.983f, 6.729f, 1.879f, 5.833f, 2.983f, 5.833f) + horizontalLineTo(12.317f) + curveTo(13.421f, 5.833f, 14.317f, 6.729f, 14.317f, 7.833f) + verticalLineTo(15.167f) + close() + } + path(fill = SolidColor(Color(0xFFA56C48)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(1.65f, 7.333f) + curveTo(1.65f, 6.076f, 1.65f, 5.448f, 2.041f, 5.057f) + curveTo(2.431f, 4.667f, 3.059f, 4.667f, 4.317f, 4.667f) + horizontalLineTo(10.983f) + curveTo(12.241f, 4.667f, 12.869f, 4.667f, 13.259f, 5.057f) + curveTo(13.65f, 5.448f, 13.65f, 6.076f, 13.65f, 7.333f) + horizontalLineTo(1.65f) + close() + } + path(fill = SolidColor(Color(0xFFA56C48)), stroke = null, strokeLineWidth = 0.0f, + strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, + pathFillType = NonZero) { + moveTo(3.65f, 3.5f) + verticalLineTo(1.5f) + curveTo(3.65f, 1.132f, 3.948f, 0.833f, 4.317f, 0.833f) + curveTo(4.685f, 0.833f, 4.983f, 1.132f, 4.983f, 1.5f) + verticalLineTo(3.5f) + curveTo(4.983f, 3.868f, 4.685f, 4.167f, 4.317f, 4.167f) + curveTo(3.948f, 4.167f, 3.65f, 3.868f, 3.65f, 3.5f) + close() + moveTo(10.317f, 3.5f) + verticalLineTo(1.5f) + curveTo(10.317f, 1.132f, 10.615f, 0.833f, 10.983f, 0.833f) + curveTo(11.351f, 0.833f, 11.65f, 1.132f, 11.65f, 1.5f) + verticalLineTo(3.5f) + curveTo(11.65f, 3.868f, 11.351f, 4.167f, 10.983f, 4.167f) + curveTo(10.615f, 4.167f, 10.317f, 3.868f, 10.317f, 3.5f) + close() + } + } + .build() + return _year!! + } + +private var _year: ImageVector? = null + +@Preview +@Composable +private fun Preview(): Unit { + Box(modifier = Modifier.padding(12.dp)) { + Image(imageVector = AppIcons.Year, contentDescription = "") + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/ui/profile/ProfileImageCircle.kt b/app/src/main/java/com/min/dnapp/presentation/ui/profile/ProfileImageCircle.kt new file mode 100644 index 0000000..de0c349 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/ui/profile/ProfileImageCircle.kt @@ -0,0 +1,34 @@ +package com.min.dnapp.presentation.ui.profile + +import androidx.compose.foundation.Image +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import com.min.dnapp.presentation.common.ProfileMapper +import com.min.dnapp.presentation.ui.theme.MomentoTheme + +@Composable +fun ProfileImageCircle( + profileImageName: String?, + modifier: Modifier = Modifier +) { + profileImageName?.let { name -> + val imageResId = ProfileMapper.getProfileImageResId(name) + + Box( + modifier = modifier + .clip(CircleShape) + .border(width = 2.dp, color = MomentoTheme.colors.grayW90, shape = CircleShape) + ) { + Image( + painter = painterResource(imageResId), + contentDescription = null + ) + } + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/ui/theme/Color.kt b/app/src/main/java/com/min/dnapp/presentation/ui/theme/Color.kt new file mode 100644 index 0000000..01ed4d2 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/ui/theme/Color.kt @@ -0,0 +1,49 @@ +package com.min.dnapp.presentation.ui.theme + +import androidx.compose.ui.graphics.Color + +// primary palette +val Brown_Bg = Color(0xFFFDFCFB) +val Brown_W90 = Color(0xFFF6F0ED) +val Brown_W80 = Color(0xFFEDE2DA) +val Brown_W70 = Color(0xFFE4D3C8) +val Brown_W60 = Color(0xFFE4D3C8) +val Brown_W50 = Color(0xFFD2B5A3) +val Brown_W40 = Color(0xFFC9A791) +val Brown_W20 = Color(0xFFB7896D) +val Brown_Base = Color(0xFFA56C48) +val Brown_B20 = Color(0xFF84563A) +val Brown_B40 = Color(0xFF63412B) +val Brown_B60 = Color(0xFF422B1D) +val Brown_B80 = Color(0xFF21160E) + +// secondary palette +val Pink_W80 = Color(0xFFFBF0ED) +val Pink_W60 = Color(0xFFF6E2DB) +val Pink_W40 = Color(0xFFF2D3CA) +val Pink_W20 = Color(0xFFEDC5B8) +val Pink_Base = Color(0xFFE9B6A6) +val Pink_B20 = Color(0xFFBA9285) +val Pink_B40 = Color(0xFF8C6D64) +val Pink_B60 = Color(0xFF5D4942) +val Pink_B80 = Color(0xFF2F2421) + +val Green_W80 = Color(0xFFE2E8E3) +val Green_W60 = Color(0xFFC5D1C7) +val Green_W40 = Color(0xFFA8B9AC) +val Green_W20 = Color(0xFF8BA290) +val Green_Base = Color(0xFF6E8B74) + +// gray +val Gray_W10 = Color(0xFF1B1D1F) +val Gray_W20 = Color(0xFF333333) +val Gray_W40 = Color(0xFF666666) +val Gray_W60 = Color(0xFF999999) +val Gray_W80 = Color(0xFFCCCCCC) +val Gray_W90 = Color(0xFFE5E5E5) +val Black = Color(0xFF000000) +val White = Color(0xFFFFFFFF) + +// 기타 +val KakaoYellow = Color(0xFFFEE500) +val ErrorRed = Color(0xFFEF5350) diff --git a/app/src/main/java/com/min/dnapp/presentation/ui/theme/Theme.kt b/app/src/main/java/com/min/dnapp/presentation/ui/theme/Theme.kt new file mode 100644 index 0000000..3422df4 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/ui/theme/Theme.kt @@ -0,0 +1,154 @@ +package com.min.dnapp.presentation.ui.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.graphics.Color + +@Immutable +data class MomentoColors( + // primary (brown) + val brownBg: Color, + val brownW90: Color, + val brownW80: Color, + val brownW70: Color, + val brownW60: Color, + val brownW50: Color, + val brownW40: Color, + val brownW20: Color, + val brownBase: Color, + val brownB20: Color, + val brownB40: Color, + val brownB60: Color, + val brownB80: Color, + + // secondary (pink) + val pinkW80: Color, + val pinkW60: Color, + val pinkW40: Color, + val pinkW20: Color, + val pinkBase: Color, + val pinkB20: Color, + val pinkB40: Color, + val pinkB60: Color, + val pinkB80: Color, + + // secondary (green) + val greenW80: Color, + val greenW60: Color, + val greenW40: Color, + val greenW20: Color, + val greenBase: Color, + + // gray + val grayW10: Color, + val grayW20: Color, + val grayW40: Color, + val grayW60: Color, + val grayW80: Color, + val grayW90: Color, + + val black: Color, + val white: Color, + + val isDark: Boolean +) + +private val LightColorPalette = MomentoColors( + brownBg = Brown_Bg, + brownW90 = Brown_W90, + brownW80 = Brown_W80, + brownW70 = Brown_W70, + brownW60 = Brown_W60, + brownW50 = Brown_W50, + brownW40 = Brown_W40, + brownW20 = Brown_W20, + brownBase = Brown_Base, + brownB20 = Brown_B20, + brownB40 = Brown_B40, + brownB60 = Brown_B60, + brownB80 = Brown_B80, + pinkW80 = Pink_W80, + pinkW60 = Pink_W60, + pinkW40 = Pink_W40, + pinkW20 = Pink_W20, + pinkBase = Pink_Base, + pinkB20 = Pink_B20, + pinkB40 = Pink_B40, + pinkB60 = Pink_B60, + pinkB80 = Pink_B80, + greenW80 = Green_W80, + greenW60 = Green_W60, + greenW40 = Green_W40, + greenW20 = Green_W20, + greenBase = Green_Base, + grayW10 = Gray_W10, + grayW20 = Gray_W20, + grayW40 = Gray_W40, + grayW60 = Gray_W60, + grayW80 = Gray_W80, + grayW90 = Gray_W90, + black = Black, + white = White, + isDark = false +) + +private val DarkColorPalette = LightColorPalette.copy( + isDark = true +) + +/** + * CompositionLocal을 통해 커스텀 color palette 제공 + */ +private val LocalMomentoColors = staticCompositionLocalOf { + error("No MomentoColorPalette provided") +} + +// Material3 기본 ColorScheme에 커스텀 color 매핑 +private fun momentoLightColorScheme(colors: MomentoColors) = lightColorScheme( + primary = colors.brownBase, + onPrimary = colors.white, + background = colors.brownBg, + onBackground = colors.black, + surface = colors.white, + onSurface = colors.grayW10 +) + +@Composable +fun DngoTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit +) { + val appTypography = getAppTypography() + val colors = if (darkTheme) DarkColorPalette else LightColorPalette + + CompositionLocalProvider( + LocalAppTypography provides appTypography, + LocalMomentoColors provides colors + ) { + MaterialTheme( + colorScheme = momentoLightColorScheme(colors), + content = content + ) + } +} + +/** + * Momento 디자인 속성에 접근하기 위한 객체 + */ +object MomentoTheme { + val typography: AppTypography + @Composable + @ReadOnlyComposable + get() = LocalAppTypography.current + + val colors: MomentoColors + @Composable + @ReadOnlyComposable + get() = LocalMomentoColors.current +} diff --git a/app/src/main/java/com/min/dnapp/presentation/ui/theme/Type.kt b/app/src/main/java/com/min/dnapp/presentation/ui/theme/Type.kt new file mode 100644 index 0000000..0c073d4 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/ui/theme/Type.kt @@ -0,0 +1,101 @@ +package com.min.dnapp.presentation.ui.theme + +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.staticCompositionLocalOf +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.FontWeight +import androidx.compose.ui.unit.em +import androidx.compose.ui.unit.sp +import com.min.dnapp.R + +private val pretendard = FontFamily( + Font(resId = R.font.pretendard_regular, weight = FontWeight.Normal), + Font(resId = R.font.pretendard_medium, weight = FontWeight.Medium), + Font(resId = R.font.pretendard_semibold, weight = FontWeight.SemiBold), + Font(resId = R.font.pretendard_bold, weight = FontWeight.Bold) +) + +/** + * 커스텀 폰트 스타일 정의 + */ +@Immutable +data class AppTypography( + val display: TextStyle, + val title01: TextStyle, + val title02: TextStyle, + val body01: TextStyle, + val body02: TextStyle, + val body03: TextStyle, + val label: TextStyle, + val caption: TextStyle +) + +/** + * CompositionLocal을 통해 커스텀 typography 제공 + */ +val LocalAppTypography = staticCompositionLocalOf { + error("No AppTypography provided") +} + +fun getAppTypography(): AppTypography { + return AppTypography( + display = TextStyle( + fontFamily = pretendard, + fontWeight = FontWeight.Bold, + fontSize = 24.sp, + lineHeight = (24 * 1.3).sp, // 130% + letterSpacing = (-0.01).em // -1% + ), + title01 = TextStyle( + fontFamily = pretendard, + fontWeight = FontWeight.Medium, + fontSize = 20.sp, + lineHeight = (20 * 1.3).sp, + letterSpacing = (-0.01).em + ), + title02 = TextStyle( + fontFamily = pretendard, + fontWeight = FontWeight.SemiBold, + fontSize = 16.sp, + lineHeight = (16 * 1.4).sp, + letterSpacing = (-0.01).em + ), + body01 = TextStyle( + fontFamily = pretendard, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = (16 * 1.4).sp, + letterSpacing = (-0.01).em + ), + body02 = TextStyle( + fontFamily = pretendard, + fontWeight = FontWeight.Normal, + fontSize = 14.sp, + lineHeight = (14 * 1.4).sp, + letterSpacing = (-0.01).em + ), + body03 = TextStyle( + fontFamily = pretendard, + fontWeight = FontWeight.Normal, + fontSize = 12.sp, + lineHeight = (12 * 1.4).sp, + letterSpacing = (-0.01).em + ), + label = TextStyle( + fontFamily = pretendard, + fontWeight = FontWeight.Medium, + fontSize = 12.sp, + lineHeight = (12 * 1.3).sp, + letterSpacing = (-0.01).em + ), + caption = TextStyle( + fontFamily = pretendard, + fontWeight = FontWeight.Normal, + fontSize = 10.sp, + lineHeight = (10 * 1.3).sp, + letterSpacing = (-0.01).em + ) + ) +} diff --git a/app/src/main/java/com/min/dnapp/presentation/write/CheckBadgeViewModel.kt b/app/src/main/java/com/min/dnapp/presentation/write/CheckBadgeViewModel.kt new file mode 100644 index 0000000..3c17c45 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/write/CheckBadgeViewModel.kt @@ -0,0 +1,80 @@ +package com.min.dnapp.presentation.write + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.min.dnapp.domain.usecase.GetBadgeDialogDataUseCase +import com.min.dnapp.domain.usecase.GetCurrentUserIdUseCase +import com.min.dnapp.domain.usecase.GetUserDataUseCase +import com.min.dnapp.domain.usecase.UpdateUserBadgeUseCase +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class CheckBadgeViewModel @Inject constructor( + private val getCurrentUserIdUseCase: GetCurrentUserIdUseCase, + private val getUserDataUseCase: GetUserDataUseCase, + private val getBadgeDialogDataUseCase: GetBadgeDialogDataUseCase, + private val updateUserBadgeUseCase: UpdateUserBadgeUseCase +) : ViewModel() { + + private val _dialogState = MutableStateFlow(WriteFinishDialogState.Hidden) + val dialogState: StateFlow = _dialogState.asStateFlow() + + init { + checkNewBadge() + } + + /** + * 새로운 뱃지 달성 여부 체크 + */ + fun checkNewBadge() { + viewModelScope.launch { + + // 사용자 ID 가져오기 + val uid = try { + getCurrentUserIdUseCase() ?: throw Exception("사용자 인증 정보 없음") + } catch (e: Exception) { + Log.e("write", "사용자 정보 조회 실패", e) + return@launch + } + + val useData = getUserDataUseCase(uid) + val userStamp = useData.stampCnt + val newBadge = getBadgeDialogDataUseCase(userStamp) + + // 뱃지 업데이트 DB에 반영 + if (newBadge != null) { + val result = updateUserBadgeUseCase(newBadge) + + // DB 업데이트 실패 시 + if (result.isFailure) { + Log.e("user", "뱃지 DB 업데이트 실패: ${result.exceptionOrNull()}") + + _dialogState.value = WriteFinishDialogState.StampDialog + return@launch + } + } + + // 1초 지연 후 모달 표시 + delay(1000) + + _dialogState.value = if (newBadge != null) { + // 뱃지 모달 표시 + WriteFinishDialogState.BadgeDialog(newBadge) + } else { + // 스탬프 모달 표시 + WriteFinishDialogState.StampDialog + } + } + } + + fun closeDialog() { + _dialogState.value = WriteFinishDialogState.Hidden + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/write/RecordWriteScreen.kt b/app/src/main/java/com/min/dnapp/presentation/write/RecordWriteScreen.kt new file mode 100644 index 0000000..ce564f0 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/write/RecordWriteScreen.kt @@ -0,0 +1,925 @@ +package com.min.dnapp.presentation.write + +import android.content.Intent +import android.net.Uri +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +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.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.DatePickerDefaults +import androidx.compose.material3.DatePickerDialog +import androidx.compose.material3.DateRangePicker +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.RadioButton +import androidx.compose.material3.RadioButtonDefaults +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarDuration +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Switch +import androidx.compose.material3.SwitchDefaults +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.rememberDateRangePickerState +import androidx.compose.material3.rememberModalBottomSheetState +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.draw.clip +import androidx.compose.ui.draw.scale +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.navigation.NavHostController +import coil3.compose.AsyncImage +import com.min.dnapp.R +import com.min.dnapp.domain.model.EmotionType +import com.min.dnapp.domain.model.LocalPlace +import com.min.dnapp.domain.model.WeatherType +import com.min.dnapp.presentation.ui.component.CustomSnackbar +import com.min.dnapp.presentation.ui.icon.AppIcons +import com.min.dnapp.presentation.ui.icon.appicons.Back +import com.min.dnapp.presentation.ui.icon.appicons.Calendar +import com.min.dnapp.presentation.ui.icon.appicons.Delete +import com.min.dnapp.presentation.ui.icon.appicons.Gallery +import com.min.dnapp.presentation.ui.theme.DngoTheme +import com.min.dnapp.presentation.ui.theme.MomentoTheme +import com.min.dnapp.presentation.write.component.EmotionBottomSheetContent +import com.min.dnapp.presentation.write.component.PlaceBottomSheetContent +import com.min.dnapp.presentation.write.component.PlaceWarningDialog +import com.min.dnapp.presentation.write.component.ShareGuide +import com.min.dnapp.presentation.write.component.WeatherBottomSheetContent +import com.min.dnapp.util.toLocalDate + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun RecordWriteScreen( + navController: NavHostController, + viewModel: RecordWriteViewModel = hiltViewModel() +) { + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + val context = LocalContext.current + + val snackbarHostState = remember { SnackbarHostState() } + + // 공유 안내 말풍선 표시상태 + var isShareGuideVisible by remember { mutableStateOf(true) } + + // 캘린더 모달 표시상태 + var showDatePicker by remember { mutableStateOf(false) } + + var showEmotionBottomSheet by remember { mutableStateOf(false) } + var showWeatherBottomSheet by remember { mutableStateOf(false) } + + // 여행지 추가 모달 표시상태 + var showPlaceBottomSheet by remember { mutableStateOf(false) } + val sheetState = rememberModalBottomSheetState( + // halfExpanded 상태 건너뛰기 + skipPartiallyExpanded = true + ) + // 저장된 여행지 삭제 경고 모달 + var showPlaceWarning by remember { mutableStateOf(false) } + + // Photo Picker 런처 등록 + val singlePhotoPickerLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.PickVisualMedia() + ) { uri -> + // URI 결과 전달 + viewModel.onPhotoSelected(uri) + + // URI 접근권한 지속적으로 요청 + if (uri != null) { + val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION + context.contentResolver.takePersistableUriPermission(uri, flags) + } + } + + // Photo Picker 실행 요청 + LaunchedEffect(viewModel.imageEvent) { + viewModel.imageEvent.collect { shouldLaunch -> + if (shouldLaunch) { + // Photo Picker 실행 + singlePhotoPickerLauncher.launch( + input = PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly) + ) + // 이벤트 처리 후 viewModel에 알림 + viewModel.photoPickerEventHandled() + } + } + } + + // 1회성 이벤트 Flow 수집 및 처리 + LaunchedEffect(Unit) { + // 구독 + viewModel.completeSaveRecordFlow.collect { + // 기록저장 성공 후 화면 이동 + navController.navigate("write_finish") { + popUpTo("record_write") { + inclusive = true + } + } + } + } + + // 메시지 발행을 수집하여 스낵바 표시 + LaunchedEffect(Unit) { + viewModel.snackbarMessage.collect { message -> + snackbarHostState.showSnackbar( + message = message.message, + duration = SnackbarDuration.Short + ) + } + } + + Scaffold( + containerColor = MomentoTheme.colors.brownW90, + topBar = { + CenterAlignedTopAppBar( + title = { + Text( + text = "기록 작성하기", + style = MomentoTheme.typography.title02, + color = MomentoTheme.colors.grayW20 + ) + }, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + containerColor = MomentoTheme.colors.brownW90 + ), + navigationIcon = { + Icon( + modifier = Modifier + .clickable { navController.popBackStack() } + .padding(16.dp), + imageVector = AppIcons.Back, + contentDescription = null + ) + }, + actions = { + Text( + modifier = Modifier + .clickable { + viewModel.saveRecord() + } + .padding(16.dp), + text = "완료", + style = MomentoTheme.typography.title02, + color = MomentoTheme.colors.grayW20 + ) + } + ) + }, + bottomBar = { + Column( + modifier = Modifier + .fillMaxWidth() + .navigationBarsPadding(), + horizontalAlignment = Alignment.End + ) { + // 공유 안내 말풍선 + if (isShareGuideVisible) { + ShareGuide( + onClick = { isShareGuideVisible = false } + ) + } + + // 이미지 아이콘 & 공유여부 스위치 영역 + ImageAndShareSection( + isChecked = uiState.isShareChecked, + onCheckedChange = { newChecked -> + viewModel.updateShare(newChecked) + }, + onGalleryClick = { viewModel.onGalleryIconClicked() } + ) + } + }, + snackbarHost = { + SnackbarHost(hostState = snackbarHostState) { data -> + CustomSnackbar( + snackbarData = data + ) + } + } + ) { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize() + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.SpaceBetween + ) { + Column( + modifier = Modifier + .padding(horizontal = 20.dp) + .fillMaxWidth() + ) { + Spacer(Modifier.height(20.dp)) + + // 날짜 영역 + WriteDateSection( + startMillis = uiState.selectedStartDateMillis, + endMillis = uiState.selectedEndDateMillis, + onClick = { showDatePicker = true } + ) + + Spacer(Modifier.height(20.dp)) + + // 감정 & 날씨 영역 + EmotionAndWeatherSection( + selectedEmotion = uiState.selectedEmotion, + selectedWeather = uiState.selectedWeather, + onClickEmotion = { showEmotionBottomSheet = true }, + onClickWeather = { showWeatherBottomSheet = true } + ) + + Spacer(Modifier.height(20.dp)) + + // 제목 영역 + WriteTitleSection( + recordTitle = uiState.recordTitle, + onValueChange = { newValue -> + viewModel.updateTitle(newValue) + } + ) + + Spacer(Modifier.height(20.dp)) + + // 여행지 영역 + WritePlaceSection( + selectedPlace = uiState.selectedPlace, + overseasPlace = uiState.overseasPlace, + onValueChange = { newValue -> + // 해외 여행지 입력 시, 국내 여행지 저장된 경우 + if (uiState.selectedPlace != null) { viewModel.clearSelectedPlace() } + viewModel.updateOverseas(newValue) + }, + onAddClick = { + // 국내 여행지 추가 클릭 시, 해외 여행지 저장된 경우 + if (uiState.overseasPlace.isNotEmpty()) { viewModel.clearOverseasPlace() } + showPlaceBottomSheet = true + }, + onWarningClick = { showPlaceWarning = true } + ) + + Spacer(Modifier.height(40.dp)) + + // 내용 영역 + WriteContentSection( + selectedImageUri = uiState.selectedImageUri, + recordContent = uiState.recordContent, + onValueChange = { newValue -> + viewModel.updateContent(newValue) + }, + onClick = { viewModel.clearSelectedImageUri() } + ) + } + } + } + + // 기록 등록 진행 시 로딩 인디케이터 표시 + if (uiState.isSaving) { + Box( + modifier = Modifier + .fillMaxSize() + .background(MomentoTheme.colors.black.copy(alpha = 0.5f)), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator( + modifier = Modifier.size(40.dp), + color = MomentoTheme.colors.white, + strokeWidth = 4.dp + ) + } + } + + // 캘린더 모달 + if (showDatePicker) { + val dateRangePickerState = rememberDateRangePickerState( + initialSelectedStartDateMillis = uiState.selectedStartDateMillis, + initialSelectedEndDateMillis = uiState.selectedEndDateMillis + ) + + DatePickerDialog( + onDismissRequest = { showDatePicker = false }, + confirmButton = { + TextButton( + onClick = { + val startMillis = dateRangePickerState.selectedStartDateMillis + val endMillis = dateRangePickerState.selectedEndDateMillis + + viewModel.updateDateRange( + startDateMillis = startMillis, + endDateMillis = endMillis + ) + + showDatePicker = false + } + ) { + Text( + text = "확인", + ) + } + }, + dismissButton = { + TextButton( + onClick = { showDatePicker = false } + ) { + Text( + text = "취소" + ) + } + }, + colors = DatePickerDefaults.colors( + containerColor = MomentoTheme.colors.white + ) + ) { + DateRangePicker( + state = dateRangePickerState, + colors = DatePickerDefaults.colors( + containerColor = MomentoTheme.colors.white, + // 기간(시작일과 종료일 사이 공간)의 배경색 + dayInSelectionRangeContainerColor = MomentoTheme.colors.brownW80 + ), + // 펜 아이콘 숨기기 (캘린더 뷰와 텍스트 입력 뷰 전환 역할) + showModeToggle = false, + title = { + Box( + modifier = Modifier.padding(start = 16.dp, top = 16.dp) + ) { + Text( + text = "여행 날짜 선택", + style = MomentoTheme.typography.title02, + color = MomentoTheme.colors.grayW20 + ) + } + }, + headline = { + Box( + modifier = Modifier.padding(16.dp) + ) { + CustomDateRangeHeadline( + startDateMillis = dateRangePickerState.selectedStartDateMillis, + endDateMillis = dateRangePickerState.selectedEndDateMillis + ) + } + } + ) + } + } + + // 감정 선택 바텀시트 + if (showEmotionBottomSheet) { + ModalBottomSheet( + onDismissRequest = { showEmotionBottomSheet = false }, + dragHandle = null, + containerColor = MomentoTheme.colors.white, + shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp) + ) { + EmotionBottomSheetContent( + onConfirm = { emotionType -> + viewModel.updateEmotion(emotionType) + showEmotionBottomSheet = false + } + ) + } + } + + // 날씨 선택 바텀시트 + if (showWeatherBottomSheet) { + ModalBottomSheet( + onDismissRequest = { showWeatherBottomSheet = false }, + dragHandle = null, + containerColor = MomentoTheme.colors.white, + shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp) + ) { + WeatherBottomSheetContent ( + onConfirm = { weatherType -> + viewModel.updateWeather(weatherType) + showWeatherBottomSheet = false + } + ) + } + } + + // 위치 추가 바텀시트 + if (showPlaceBottomSheet) { + ModalBottomSheet( + onDismissRequest = { showPlaceBottomSheet = false }, + sheetState = sheetState, + dragHandle = null, + containerColor = MomentoTheme.colors.white, + shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp) + ) { + PlaceBottomSheetContent( + searchState = uiState.searchState, + onValueChange = { newValue -> + viewModel.updateQuery(newValue) + }, + onSearch = { viewModel.searchPlace() }, + onClear = { viewModel.clearSearchResult() }, + onConfirm = { place -> + viewModel.updatePlace(place) + showPlaceBottomSheet = false + } + ) + } + } + + // 해외 버튼 클릭 시 경고 모달 표시 (국내 여행지 저장된 경우) + if (showPlaceWarning) { + PlaceWarningDialog( + onDismiss = { showPlaceWarning = false }, + onCancel = { showPlaceWarning = false }, + onConfirm = { + viewModel.clearSelectedPlace() + showPlaceWarning = false + } + ) + } +} + +@Composable +fun WriteDateSection( + startMillis: Long?, + endMillis: Long?, + onClick: () -> Unit +) { + Row( + modifier = Modifier.clickable { onClick() }, + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = AppIcons.Calendar, + contentDescription = null, + tint = MomentoTheme.colors.brownBase + ) + Spacer(Modifier.width(10.dp)) + + when { + // 날짜 선택되지 않은 경우 + startMillis == null -> { + Text( + text = "날짜 선택", + style = MomentoTheme.typography.title02, + color = MomentoTheme.colors.grayW40 + ) + } + + // 시작일만 선택된 경우 or 시작일과 종료일이 같은 날짜에 선택된 경우 + endMillis == null || startMillis == endMillis -> { + val startDate = startMillis.toLocalDate() + + Text( + text = "${startDate?.year}년 ${startDate?.monthValue}월 ${startDate?.dayOfMonth}일", + style = MomentoTheme.typography.title02, + color = MomentoTheme.colors.grayW20 + ) + } + + // 시작일과 종료일 모두 선택된 경우 (다른 날짜) + else -> { + val startDate = startMillis.toLocalDate() + val endDate = endMillis.toLocalDate() + + Text( + text = "${startDate?.year}년 ${startDate?.monthValue}월 ${startDate?.dayOfMonth}일 ~ ${endDate?.year}년 ${endDate?.monthValue}월 ${endDate?.dayOfMonth}일", + style = MomentoTheme.typography.title02, + color = MomentoTheme.colors.grayW20 + ) + } + } + } +} + +@Composable +fun CustomDateRangeHeadline( + startDateMillis: Long?, + endDateMillis: Long? +) { + if (startDateMillis == null) { + Text( + text = "날짜를 선택해주세요", + style = MomentoTheme.typography.body02, + color = MomentoTheme.colors.grayW40 + ) + } else { + val startDate = startDateMillis.toLocalDate() + + if (endDateMillis == null || startDateMillis == endDateMillis) { + // 시작일만 선택된 경우 or 시작일과 종료일이 같은 날짜에 선택된 경우 + Text( + text = "${startDate?.year}년 ${startDate?.monthValue}월 ${startDate?.dayOfMonth}일", + style = MomentoTheme.typography.body02, + color = MomentoTheme.colors.grayW20 + ) + } else { + // 시작일과 종료일 모두 선택된 경우 (다른 날짜) + val endDate = endDateMillis.toLocalDate() + + Row { + Text( + text = "${startDate?.year}년 ${startDate?.monthValue}월 ${startDate?.dayOfMonth}일", + style = MomentoTheme.typography.body02, + color = MomentoTheme.colors.grayW20 + ) + Text( + text = " ~ ", + style = MomentoTheme.typography.body02, + color = MomentoTheme.colors.grayW20 + ) + Text( + text = "${endDate?.year}년 ${endDate?.monthValue}월 ${endDate?.dayOfMonth}일", + style = MomentoTheme.typography.body02, + color = MomentoTheme.colors.grayW20 + ) + } + } + } +} + +@Composable +fun EmotionAndWeatherSection( + selectedEmotion: EmotionType?, + selectedWeather: WeatherType?, + onClickEmotion: () -> Unit, + onClickWeather: () -> Unit +) { + Row { + Row( + modifier = Modifier.clickable { onClickEmotion() }, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "감정 태그", + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.grayW20 + ) + Spacer(Modifier.width(8.dp)) + Box( + modifier = Modifier + .size(32.dp) + .background(color = MomentoTheme.colors.white, shape = RoundedCornerShape(5.dp)) + .border(width = 1.dp, color = MomentoTheme.colors.pinkW60, shape = RoundedCornerShape(5.dp)), + contentAlignment = Alignment.Center + ) { + selectedEmotion?.let { emotionType -> + Image( + painter = painterResource(emotionType.resId), + contentDescription = null + ) + } + } + } + Spacer(Modifier.width(20.dp)) + Row( + modifier = Modifier.clickable { onClickWeather() }, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "날씨", + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.grayW20 + ) + Spacer(Modifier.width(8.dp)) + Box( + modifier = Modifier + .size(32.dp) + .background(color = MomentoTheme.colors.white, shape = RoundedCornerShape(5.dp)) + .border(width = 1.dp, color = MomentoTheme.colors.pinkW60, shape = RoundedCornerShape(5.dp)), + contentAlignment = Alignment.Center + ) { + selectedWeather?.let { weatherType -> + Image( + painter = painterResource(weatherType.resId), + contentDescription = null + ) + } + } + } + } +} + +@Composable +fun WritePlaceSection( + selectedPlace: LocalPlace?, + overseasPlace: String, + onValueChange: (String) -> Unit, + onAddClick: () -> Unit, + onWarningClick: () -> Unit, +) { + val radioOptions = listOf("국내", "해외 (직접 입력)") + var selectedCountry by remember { mutableStateOf("국내") } + + Column( + modifier = Modifier.fillMaxWidth() + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "어디로 다녀오셨나요?", + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.grayW20 + ) + + // 라디오 버튼 + Row( + verticalAlignment = Alignment.CenterVertically + ) { + radioOptions.forEachIndexed { idx, text -> + RadioButton( + modifier = Modifier.size(24.dp), + selected = (text == selectedCountry), + onClick = { + // 해외 버튼 클릭 & 저장된 국내 여행지 있는 경우 + if (idx == 1 && selectedPlace != null) { onWarningClick() } + selectedCountry = text + }, + colors = RadioButtonDefaults.colors( + selectedColor = MomentoTheme.colors.greenW20, + unselectedColor = MomentoTheme.colors.grayW80, + disabledSelectedColor = MomentoTheme.colors.white + ) + ) + Spacer(Modifier.width(4.dp)) + Text( + text = text, + style = MomentoTheme.typography.body03, + color = MomentoTheme.colors.grayW20 + ) + if (idx == 0) { + Spacer(Modifier.width(12.dp)) + } + } + } + } + + Spacer(Modifier.height(12.dp)) + + if (selectedCountry == "국내") { + Row( + modifier = Modifier.clickable { onAddClick() }, + verticalAlignment = Alignment.CenterVertically + ) { + Image( + painter = painterResource(R.drawable.write_place), + contentDescription = null + ) + Spacer(Modifier.width(6.dp)) + if (selectedPlace == null) { + Text( + text = "위치 추가", + style = MomentoTheme.typography.body02, + color = MomentoTheme.colors.grayW40 + ) + } else { + Text( + text = selectedPlace.title, + style = MomentoTheme.typography.body02, + color = MomentoTheme.colors.grayW20 + ) + } + } + } else { + // 해외 + Box( + modifier = Modifier + .fillMaxWidth() + .height(40.dp) + .background(color = MomentoTheme.colors.brownBg, shape = RoundedCornerShape(5.dp)) + .padding(start = 12.dp), + contentAlignment = Alignment.CenterStart + ) { + if (overseasPlace.isEmpty()) { + Text( + text = "예) 도쿄, 발리", + style = MomentoTheme.typography.body02, + color = MomentoTheme.colors.grayW60 + ) + } + BasicTextField( + modifier = Modifier.fillMaxWidth(), + value = overseasPlace, + onValueChange = onValueChange, + textStyle = MomentoTheme.typography.body02, + singleLine = true + ) + } + } + } +} + +@Composable +fun WriteTitleSection( + recordTitle: String, + onValueChange: (String) -> Unit +) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(40.dp) + .background(color = MomentoTheme.colors.brownBg, shape = RoundedCornerShape(5.dp)) + .padding(start = 12.dp), + contentAlignment = Alignment.CenterStart + ) { + if (recordTitle.isEmpty()) { + Text( + text = "제목", + style = MomentoTheme.typography.body02, + color = MomentoTheme.colors.grayW60 + ) + } + BasicTextField( + modifier = Modifier.fillMaxWidth(), + value = recordTitle, + onValueChange = onValueChange, + textStyle = MomentoTheme.typography.body02, + singleLine = true + ) + } +} + +@Composable +fun WriteContentSection( + selectedImageUri: Uri?, + recordContent: String, + onValueChange: (String) -> Unit, + onClick: () -> Unit +) { + Column( + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = "이번 여행에 대해 소개해주세요.", + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.grayW20 + ) + Spacer(Modifier.height(12.dp)) + Box( + modifier = Modifier + .fillMaxWidth() +// .height(300.dp) + .height(280.dp) + .background(color = MomentoTheme.colors.brownBg) + .padding(16.dp) + ) { + if (recordContent.isEmpty()) { + Text( + text = "내용", + style = MomentoTheme.typography.body02, + color = MomentoTheme.colors.grayW60 + ) + } + BasicTextField( + modifier = Modifier.fillMaxSize(), + value = recordContent, + onValueChange = onValueChange, + textStyle = MomentoTheme.typography.body02, + singleLine = false + ) + } + // 선택된 이미지 + selectedImageUri?.let { uri -> + Row( + modifier = Modifier + .fillMaxWidth() + .background(color = MomentoTheme.colors.brownBg) + .padding(start = 16.dp, bottom = 16.dp), + ) { + Box( + contentAlignment = Alignment.TopEnd + ) { + AsyncImage( + modifier = Modifier + .size(72.dp) + .clip(RoundedCornerShape(5.dp)), + model = uri, + contentDescription = null, + contentScale = ContentScale.Crop + ) + + Box( + modifier = Modifier + .offset(x = 8.dp, y = (-8).dp) + .clickable { onClick() } + .background(color = MomentoTheme.colors.white, shape = CircleShape) + ) { + Icon( + modifier = Modifier.size(20.dp), + imageVector = AppIcons.Delete, + contentDescription = null, + tint = MomentoTheme.colors.grayW60 + ) + } + } + } + } + } +} + +@Composable +fun ImageAndShareSection( + isChecked: Boolean, + onCheckedChange: (Boolean) -> Unit, + onGalleryClick: () -> Unit +) { + Row( + modifier = Modifier + .fillMaxWidth() + .background(color = MomentoTheme.colors.white), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + modifier = Modifier + .clickable { onGalleryClick() } + .padding(horizontal = 20.dp, vertical = 10.dp), + imageVector = AppIcons.Gallery, + contentDescription = null, + tint = MomentoTheme.colors.grayW60 + ) + + Row( + modifier = Modifier.padding(end = 20.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "공유 여부", + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.grayW20 + ) + Spacer(Modifier.width(8.dp)) + Switch( + // 기본 크기의 80%로 축소 + modifier = Modifier.scale(0.8f), + checked = isChecked, + onCheckedChange = onCheckedChange, + thumbContent = { FixedSizeThumbBox() }, + colors = SwitchDefaults.colors( + // on - switch 배경 색 + checkedTrackColor = MomentoTheme.colors.greenW20, + // off - switch 배경 색 + uncheckedTrackColor = MomentoTheme.colors.grayW80, + // off - switch 테두리 색 + uncheckedBorderColor = Color.Transparent, + // off - thumb 테두리 색 + uncheckedThumbColor = MomentoTheme.colors.white + ) + ) + } + } +} + +@Composable +fun FixedSizeThumbBox() { + Box( + modifier = Modifier + .size(SwitchDefaults.IconSize) + .background(color = MomentoTheme.colors.white, shape = CircleShape) + ) +} + +@Preview +@Composable +fun RecordWriteScreenPreview() { + DngoTheme { +// RecordWriteScreen() + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/write/RecordWriteUiState.kt b/app/src/main/java/com/min/dnapp/presentation/write/RecordWriteUiState.kt new file mode 100644 index 0000000..e08d4a9 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/write/RecordWriteUiState.kt @@ -0,0 +1,28 @@ +package com.min.dnapp.presentation.write + +import android.net.Uri +import com.min.dnapp.domain.model.EmotionType +import com.min.dnapp.domain.model.LocalPlace +import com.min.dnapp.domain.model.WeatherType + +data class SearchState( + val isLoading: Boolean = false, + val places: List = emptyList(), + val error: String? = null, + val query: String = "" +) + +data class RecordWriteUiState( + val isSaving: Boolean = false, + val recordTitle: String = "", + val recordContent: String = "", + val selectedStartDateMillis: Long? = null, + val selectedEndDateMillis: Long? = null, + val selectedEmotion: EmotionType? = null, + val selectedWeather : WeatherType? = null, + val searchState: SearchState = SearchState(), + val selectedPlace: LocalPlace? = null, + val overseasPlace: String = "", + val selectedImageUri: Uri? = null, + val isShareChecked: Boolean = true +) diff --git a/app/src/main/java/com/min/dnapp/presentation/write/RecordWriteViewModel.kt b/app/src/main/java/com/min/dnapp/presentation/write/RecordWriteViewModel.kt new file mode 100644 index 0000000..d625acb --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/write/RecordWriteViewModel.kt @@ -0,0 +1,314 @@ +package com.min.dnapp.presentation.write + +import android.net.Uri +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.min.dnapp.domain.model.EmotionType +import com.min.dnapp.domain.model.LocalPlace +import com.min.dnapp.domain.model.WeatherType +import com.min.dnapp.domain.usecase.LocalSearchUseCase +import com.min.dnapp.domain.usecase.SaveRecordUseCase +import com.min.dnapp.presentation.common.SnackbarMessage +import com.min.dnapp.util.Resource +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Job +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class RecordWriteViewModel @Inject constructor( + private val localSearchUseCase: LocalSearchUseCase, + private val saveRecordUseCase: SaveRecordUseCase +) : ViewModel() { + + private val _uiState = MutableStateFlow(RecordWriteUiState()) + val uiState: StateFlow = _uiState.asStateFlow() + + // Photo Picker를 실행하도록 요청하는 이벤트 + private val _imageEvent = MutableStateFlow(false) + val imageEvent: StateFlow = _imageEvent.asStateFlow() + + private val _completeSaveRecord = Channel() + val completeSaveRecordFlow = _completeSaveRecord.receiveAsFlow() + + private val _snackbarMessage = MutableSharedFlow() + val snackbarMessage = _snackbarMessage.asSharedFlow() + + // 이전 검색 작업을 취소하기 위한 Job + private var searchJob: Job? = null + +// init { +// searchPlace("광안리해수욕장") +// } + + /** + * 제목 - textField의 입력 값 업데이트 + */ + fun updateTitle(newText: String) { + _uiState.value = _uiState.value.copy(recordTitle = newText) + Log.d("write", "updateTitle - newText : $newText") + } + + /** + * 내용 - textField의 입력 값 업데이트 + */ + fun updateContent(newText: String) { + _uiState.value = _uiState.value.copy(recordContent = newText) + Log.d("write", "updateContent - newText : $newText") + } + + /** + * 선택된 출발일과 도착일을 동시에 업데이트 + */ + fun updateDateRange(startDateMillis: Long?, endDateMillis: Long?) { + _uiState.value = _uiState.value.copy( + selectedStartDateMillis = startDateMillis, + selectedEndDateMillis = endDateMillis + ) + } + + /** + * 선택된 감정 업데이트 + */ + fun updateEmotion(emotionType: EmotionType) { + _uiState.value = _uiState.value.copy(selectedEmotion = emotionType) + Log.d("write", "selectEmotion - emotionType : $emotionType") + } + + /** + * 선택된 날씨 업데이트 + */ + fun updateWeather(weatherType: WeatherType) { + _uiState.value = _uiState.value.copy(selectedWeather = weatherType) + Log.d("write", "selectWeather - weatherType : $weatherType") + } + + /** + * textField의 입력 값만 업데이트하고, 검색 API 호출은 생략 + */ + fun updateQuery(newQuery: String) { + _uiState.value = _uiState.value.copy(searchState = _uiState.value.searchState.copy(query = newQuery)) + + // 검색어가 빈 경우 목록 지우기 + if (newQuery.isBlank()) { + // 진행중인 검색 작업 취소 + searchJob?.cancel() + Log.d("naver", "updateQuery - newQuery blank") + } + } + + /** + * 검색 버튼 클릭 시, 검색 API 호출 + */ + fun searchPlace() { + val currentQuery = _uiState.value.searchState.query + + // 검색어가 빈 경우 + if (currentQuery.isBlank()) { + Log.d("naver", "searchPlace - newQuery blank") + return + } + + // 진행중인(이전 검색) 작업이 있으면 취소 + searchJob?.cancel() + + // Flow 수집 및 상태 업데이트 시작 + searchJob = localSearchUseCase(currentQuery) + .onEach { result -> + when (result) { + is Resource.Loading -> { + // 로딩 시작 + _uiState.value = _uiState.value.copy(searchState = _uiState.value.searchState.copy( + isLoading = true, + places = result.data ?: emptyList(), + error = null + )) + Log.d("naver", "search for $currentQuery : Loading...") + } + is Resource.Success -> { + // 성공 + _uiState.value = _uiState.value.copy(searchState = _uiState.value.searchState.copy( + isLoading = false, + places = result.data ?: emptyList(), + error = null + )) + Log.d("naver", "search success : ${result.data?.size} 개") + } + is Resource.Error -> { + // 에러 + _uiState.value = _uiState.value.copy(searchState = _uiState.value.searchState.copy( + isLoading = false, + places = result.data ?: emptyList(), + error = result.message ?: "알 수 없는 에러 발생" + )) + Log.e("naver", "search error : ${result.message}") + } + } + } + // viewModelScope에서 flow를 실행 + .launchIn(viewModelScope) + } + + /** + * 검색 결과 목록 초기화 + * (textField에 포커스가 잡히거나, 새 검색 시작할 때 사용) + */ + fun clearSearchResult() { + searchJob?.cancel() + + // query는 그대로 유지, 결과 목록만 초기화 + _uiState.value = _uiState.value.copy(searchState = _uiState.value.searchState.copy( + isLoading = false, + places = emptyList(), + error = null + )) + Log.d("naver", "result cleared") + } + + /** + * 국내 - 선택된 장소 업데이트 + */ + fun updatePlace(place: LocalPlace) { + _uiState.value = _uiState.value.copy(selectedPlace = place) + } + + /** + * 해외 - textField의 입력 값 업데이트 + */ + fun updateOverseas(newText: String) { + _uiState.value = _uiState.value.copy(overseasPlace = newText) + Log.d("write", "updateOverseas - newText : $newText") + } + + /** + * 공유여부 설정 상태 업데이트 + */ + fun updateShare(newChecked: Boolean) { + _uiState.value = _uiState.value.copy(isShareChecked = newChecked) + Log.d("write", "updateShare - newChecked : $newChecked") + } + + /** + * 갤러리 아이콘 클릭 - Photo Picker 실행 요청 + */ + fun onGalleryIconClicked() { + _imageEvent.value = true + } + + /** + * Photo Picker 실행 이벤트가 처리되었음을 알림 + */ + fun photoPickerEventHandled() { + _imageEvent.value = false + } + + /** + * Photo Picker에서 선택된 URI를 받아 상태 업데이트 + */ + fun onPhotoSelected(uri: Uri?) { + _uiState.value = _uiState.value.copy(selectedImageUri = uri) + Log.d("write", "onPhotoSelected - uri : $uri") + } + + /** + * Firebase에 기록 저장 + */ + fun saveRecord() { + // 필수 항목 미입력 시, 메시지 발행 후 종료 + getSnackbarMessage(uiState.value)?.let { message -> + viewModelScope.launch { + _snackbarMessage.emit(message) + } + return + } + + // 중복 저장 방지 + if (uiState.value.isSaving) return + + val currentUiState = uiState.value + val imageUrl = currentUiState.selectedImageUri + + _uiState.update { it.copy(isSaving = true) } + + viewModelScope.launch { + val result = saveRecordUseCase(currentUiState, imageUrl) + + result.onSuccess { + // 저장 성공 + _completeSaveRecord.send(Unit) + }.onFailure { exception -> + // 저장 실패 + Log.e("write", "saveRecord - exception : $exception") + } + + _uiState.update { it.copy(isSaving = false) } + } + } + + private fun getSnackbarMessage(uiState: RecordWriteUiState): SnackbarMessage? { + if (uiState.recordTitle.isBlank()) { + return SnackbarMessage( + message = WriteMessage.TITLE_EMPTY + ) + } + if (uiState.recordContent.isBlank()) { + return SnackbarMessage( + message = WriteMessage.CONTENT_EMPTY + ) + } + if (uiState.selectedStartDateMillis == null) { + return SnackbarMessage( + message = WriteMessage.DATE_EMPTY + ) + } + if (uiState.selectedEmotion == null) { + return SnackbarMessage( + message = WriteMessage.EMOTION_EMPTY + ) + } + if (uiState.selectedWeather == null) { + return SnackbarMessage( + message = WriteMessage.WEATHER_EMPTY + ) + } + if (uiState.selectedPlace == null && uiState.overseasPlace.isBlank()) { + return SnackbarMessage( + message = WriteMessage.PLACE_EMPTY + ) + } + + return null + } + + /** + * 저장된 국내 여행지 삭제 + */ + fun clearSelectedPlace() { + _uiState.update { it.copy(selectedPlace = null) } + } + + /** + * 저장된 해외 여행지 삭제 + */ + fun clearOverseasPlace() { + _uiState.update { it.copy(overseasPlace = "") } + } + + /** + * 선택된 이미지 삭제 + */ + fun clearSelectedImageUri() { + _uiState.update { it.copy(selectedImageUri = null) } + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/write/WriteFinishDialogState.kt b/app/src/main/java/com/min/dnapp/presentation/write/WriteFinishDialogState.kt new file mode 100644 index 0000000..25ab12f --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/write/WriteFinishDialogState.kt @@ -0,0 +1,9 @@ +package com.min.dnapp.presentation.write + +import com.min.dnapp.domain.model.Badge + +sealed class WriteFinishDialogState { + data object Hidden: WriteFinishDialogState() + data object StampDialog: WriteFinishDialogState() + data class BadgeDialog(val badge: Badge): WriteFinishDialogState() +} diff --git a/app/src/main/java/com/min/dnapp/presentation/write/WriteFinishScreen.kt b/app/src/main/java/com/min/dnapp/presentation/write/WriteFinishScreen.kt new file mode 100644 index 0000000..e24efe3 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/write/WriteFinishScreen.kt @@ -0,0 +1,124 @@ +package com.min.dnapp.presentation.write + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +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.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.navigation.NavHostController +import com.min.dnapp.R +import com.min.dnapp.presentation.ui.theme.DngoTheme +import com.min.dnapp.presentation.ui.theme.MomentoTheme +import com.min.dnapp.presentation.write.component.LevelUpDialog +import com.min.dnapp.presentation.write.component.WriteStampDialog + +@Composable +fun WriteFinishScreen( + navController: NavHostController, + viewModel: CheckBadgeViewModel = hiltViewModel() +) { + val currentDialogState by viewModel.dialogState.collectAsStateWithLifecycle() + + Surface( + modifier = Modifier + .systemBarsPadding() + .fillMaxSize(), + color = MomentoTheme.colors.white + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 20.dp), + verticalArrangement = Arrangement.SpaceBetween + ) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(Modifier.height(100.dp)) + Image( + painter = painterResource(R.drawable.write_finish), + contentDescription = null + ) + Spacer(Modifier.height(40.dp)) + Text( + text = "여행 기록 작성이 완료되었어요.", + style = MomentoTheme.typography.title01, + color = MomentoTheme.colors.grayW20 + ) + } + + // 버튼 영역 + Column( + modifier = Modifier.fillMaxWidth() + ) { + Box( + modifier = Modifier + .clickable { + navController.navigate("home") { + // 스택 모두 제거 + popUpTo(navController.graph.id) { inclusive = true } + } + } + .fillMaxWidth() + .background(color = MomentoTheme.colors.brownBase, shape = RoundedCornerShape(10.dp)) + .padding(vertical = 16.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = "홈으로", + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.white + ) + } + Spacer(Modifier.height(16.dp)) + } + } + } + + when (currentDialogState) { + is WriteFinishDialogState.BadgeDialog -> { + val data = currentDialogState as WriteFinishDialogState.BadgeDialog + + LevelUpDialog( + badge = data.badge, + onDismiss = { viewModel.closeDialog() }, + onConfirm = { viewModel.closeDialog() }, + ) + } + is WriteFinishDialogState.StampDialog -> { + WriteStampDialog( + onDismiss = { viewModel.closeDialog() }, + onConfirm = { viewModel.closeDialog() } + ) + } + is WriteFinishDialogState.Hidden -> {} + } +} + +@Preview +@Composable +fun WriteFinishScreenPreview() { + DngoTheme { +// WriteFinishScreen() + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/write/WriteMessage.kt b/app/src/main/java/com/min/dnapp/presentation/write/WriteMessage.kt new file mode 100644 index 0000000..8cec782 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/write/WriteMessage.kt @@ -0,0 +1,10 @@ +package com.min.dnapp.presentation.write + +object WriteMessage { + const val TITLE_EMPTY = "제목을 입력해주세요" + const val CONTENT_EMPTY = "내용을 입력해주세요" + const val DATE_EMPTY = "날짜를 선택해주세요" + const val EMOTION_EMPTY = "감정 태그를 선택해주세요" + const val WEATHER_EMPTY = "날씨를 선택해주세요" + const val PLACE_EMPTY = "여행지를 입력해주세요" +} diff --git a/app/src/main/java/com/min/dnapp/presentation/write/component/EmotionBottomSheetContent.kt b/app/src/main/java/com/min/dnapp/presentation/write/component/EmotionBottomSheetContent.kt new file mode 100644 index 0000000..8ed1817 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/write/component/EmotionBottomSheetContent.kt @@ -0,0 +1,161 @@ +package com.min.dnapp.presentation.write.component + +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.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +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.res.painterResource +import androidx.compose.ui.unit.dp +import com.min.dnapp.R +import com.min.dnapp.domain.model.EmotionType +import com.min.dnapp.presentation.ui.component.SelectButton +import com.min.dnapp.presentation.ui.theme.MomentoTheme + +@Composable +fun EmotionBottomSheetContent( + onConfirm: (EmotionType) -> Unit +) { + // 선택된 EmotionType 객체 저장 + var selectedEmotion by remember { mutableStateOf(null) } + // emotion 선택 시 버튼 활성화 + val isButtonEnabled = selectedEmotion != null + + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(Modifier.height(20.dp)) + + Text( + text = "기분은 어땠나요?", + style = MomentoTheme.typography.title02, + color = MomentoTheme.colors.grayW20 + ) + + Spacer(Modifier.height(20.dp)) + + HorizontalDivider(thickness = 1.dp, color = MomentoTheme.colors.grayW90) + + Spacer(Modifier.height(12.dp)) + + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(20.dp) + ) { + // happy / love / surprise + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly + ) { + EmotionItem( + emotionType = EmotionType.HAPPY, + isSelected = selectedEmotion == EmotionType.HAPPY, + onSelect = { emotionType -> selectedEmotion = emotionType } + ) + EmotionItem( + emotionType = EmotionType.LOVE, + isSelected = selectedEmotion == EmotionType.LOVE, + onSelect = { emotionType -> selectedEmotion = emotionType } + ) + EmotionItem( + emotionType = EmotionType.SURPRISE, + isSelected = selectedEmotion == EmotionType.SURPRISE, + onSelect = { emotionType -> selectedEmotion = emotionType } + ) + } + + // angry / feel / sad + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly + ) { + EmotionItem( + emotionType = EmotionType.ANGRY, + isSelected = selectedEmotion == EmotionType.ANGRY, + onSelect = { emotionType -> selectedEmotion = emotionType } + ) + EmotionItem( + emotionType = EmotionType.FEEL, + isSelected = selectedEmotion == EmotionType.FEEL, + onSelect = { emotionType -> selectedEmotion = emotionType } + ) + EmotionItem( + emotionType = EmotionType.SAD, + isSelected = selectedEmotion == EmotionType.SAD, + onSelect = { emotionType -> selectedEmotion = emotionType } + ) + } + + // shine + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + EmotionItem( + emotionType = EmotionType.SHINE, + isSelected = selectedEmotion == EmotionType.SHINE, + onSelect = { emotionType -> selectedEmotion = emotionType } + ) + } + } + + Spacer(Modifier.height(32.dp)) + + // 선택 버튼 + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp) + ) { + SelectButton( + enabled = isButtonEnabled, + onConfirm = { + selectedEmotion?.let(onConfirm) + } + ) + Spacer(Modifier.height(16.dp)) + } + } +} + +@Composable +fun EmotionItem( + emotionType: EmotionType, + isSelected: Boolean, + onSelect: (EmotionType) -> Unit +) { + Box( + contentAlignment = Alignment.Center + ) { + Image( + modifier = Modifier.clickable { + onSelect(emotionType) + }, + painter = painterResource(emotionType.resId), + contentDescription = null + ) + if (isSelected) { + Image( + modifier = Modifier.size(52.dp), + painter = painterResource(R.drawable.ew_check), + contentDescription = null + ) + } + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/write/component/LevelUpDialog.kt b/app/src/main/java/com/min/dnapp/presentation/write/component/LevelUpDialog.kt new file mode 100644 index 0000000..43d4341 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/write/component/LevelUpDialog.kt @@ -0,0 +1,134 @@ +package com.min.dnapp.presentation.write.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import com.airbnb.lottie.compose.LottieAnimation +import com.airbnb.lottie.compose.LottieCompositionSpec +import com.airbnb.lottie.compose.animateLottieCompositionAsState +import com.airbnb.lottie.compose.rememberLottieComposition +import com.min.dnapp.domain.model.Badge +import com.min.dnapp.presentation.ui.theme.DngoTheme +import com.min.dnapp.presentation.ui.theme.MomentoTheme + +@Composable +fun LevelUpDialog( + badge: Badge, + onDismiss: () -> Unit, + onConfirm: () -> Unit +) { + Dialog( + onDismissRequest = onDismiss + ) { + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(16.dp), + color = MomentoTheme.colors.white + ) { + Box( + modifier = Modifier.fillMaxWidth() + ) { + Column( + modifier = Modifier.padding(horizontal = 20.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(Modifier.height(20.dp)) + Text( + text = "${badge.name}가 된 것을 축하해요!", + style = MomentoTheme.typography.title01, + color = MomentoTheme.colors.grayW20 + ) + Spacer(Modifier.height(8.dp)) + Text( + text = "여행 기록 ${badge.minStamp}개 달성", + style = MomentoTheme.typography.title02, + color = MomentoTheme.colors.grayW60 + ) + Spacer(Modifier.height(20.dp)) + Image( + modifier = Modifier.size(160.dp), + painter = painterResource(badge.resId), + contentDescription = null + ) + Spacer(Modifier.height(20.dp)) + Text( + text = badge.description, + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.grayW20 + ) + Spacer(Modifier.height(20.dp)) + Box( + modifier = Modifier + .clickable { onConfirm() } + .fillMaxWidth() + .background( + color = MomentoTheme.colors.brownBase, + shape = RoundedCornerShape(10.dp) + ) + .padding(vertical = 16.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = "확인했어요", + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.white + ) + } + Spacer(Modifier.height(20.dp)) + } + + // Box 영역 전체 덮도록 설정 + LottieFireworksAnimation( + modifier = Modifier.matchParentSize() + ) + } + } + } +} + +@Composable +fun LottieFireworksAnimation( + modifier: Modifier = Modifier +) { + val composition by rememberLottieComposition(spec = LottieCompositionSpec.Asset("fireworks.json")) + + // 애니메이션 1번만 재생 + val progress by animateLottieCompositionAsState( + composition = composition, + iterations = 1, + isPlaying = true, + restartOnPlay = false + ) + + LottieAnimation( + modifier = modifier, + composition = composition, + progress = progress + ) +} + +@Preview +@Composable +fun LevelUpDialogPreview() { + DngoTheme { +// LevelUpDialog() + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/write/component/PlaceBottomSheetContent.kt b/app/src/main/java/com/min/dnapp/presentation/write/component/PlaceBottomSheetContent.kt new file mode 100644 index 0000000..aed456f --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/write/component/PlaceBottomSheetContent.kt @@ -0,0 +1,203 @@ +package com.min.dnapp.presentation.write.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +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.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +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.focus.onFocusChanged +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.min.dnapp.domain.model.LocalPlace +import com.min.dnapp.presentation.ui.component.SelectButton +import com.min.dnapp.presentation.ui.theme.DngoTheme +import com.min.dnapp.presentation.ui.theme.MomentoTheme +import com.min.dnapp.presentation.write.SearchState + +@Composable +fun PlaceBottomSheetContent( + searchState: SearchState, + onValueChange: (String) -> Unit, + onSearch: () -> Unit, + onClear: () -> Unit, + onConfirm: (LocalPlace) -> Unit, +) { + // 검색 결과 목록에서 선택된 아이템 + var selectedPlace by remember { mutableStateOf(null) } + + // UI 컨트롤러 + val keyboardController = LocalSoftwareKeyboardController.current + val focusManager = LocalFocusManager.current + + val onSearchAction = { + // 여행지 검색 실행 + onSearch() + // 키보드 숨기기 + keyboardController?.hide() + // 포커스 해제 + focusManager.clearFocus() + } + + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(Modifier.height(20.dp)) + + Box( + modifier = Modifier + .padding(horizontal = 20.dp) + .fillMaxWidth() + .border(width = 1.dp, color = MomentoTheme.colors.grayW80, shape = RoundedCornerShape(6.dp)) + .padding(horizontal = 16.dp, vertical = 12.dp) + ) { + if (searchState.query.isEmpty()) { + Text( + text = "여행지를 입력해주세요", + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.grayW60 + ) + } + BasicTextField( + modifier = Modifier + .fillMaxWidth() + .onFocusChanged { focusState -> + if (focusState.isFocused) { + selectedPlace = null + onClear() + } + }, + value = searchState.query, + onValueChange = onValueChange, + textStyle = MomentoTheme.typography.body01, + singleLine = true, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), + keyboardActions = KeyboardActions( + onSearch = { onSearchAction() } + ) + ) + } + + Spacer(Modifier.height(20.dp)) + + // 검색 진행 시 로딩 인디케이터 표시 + if (searchState.isLoading) { + CircularProgressIndicator( + modifier = Modifier.size(40.dp), + color = MomentoTheme.colors.brownW20, + strokeWidth = 4.dp + ) + } + + // 검색 결과 목록 + Column( + modifier = Modifier.fillMaxWidth() + ) { + searchState.places.forEach { item -> + PlaceItem( + placeName = item.title, + placeCategory = item.category, + placeRoadAddress = item.roadAddress, + isSelected = selectedPlace == item, + onClick = { selectedPlace = item } + ) + } + } + + Spacer(Modifier.height(20.dp)) + + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp) + ) { + SelectButton( + enabled = selectedPlace != null, + onConfirm = { + // 콜백으로 이벤트 전달 + selectedPlace?.let(onConfirm) + } + ) + Spacer(Modifier.height(16.dp)) + } + } +} + +@Composable +fun PlaceItem( + placeName: String, + placeCategory: String, + placeRoadAddress: String, + isSelected: Boolean = false, + onClick: () -> Unit +) { + Column( + modifier = Modifier + .clickable { onClick() } + .fillMaxWidth() + .background( + if (isSelected) MomentoTheme.colors.brownW80 else Color.Transparent + ) + .padding(20.dp), + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = placeName, + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.grayW20 + ) + Text( + text = placeCategory, + style = MomentoTheme.typography.body02, + color = MomentoTheme.colors.grayW40 + ) + } + Spacer(Modifier.height(4.dp)) + Text( + text = placeRoadAddress, + style = MomentoTheme.typography.body02, + color = MomentoTheme.colors.grayW60 + ) + } + HorizontalDivider(thickness = 1.dp, color = MomentoTheme.colors.grayW80) +} + +@Preview(showBackground = true) +@Composable +fun PlaceBottomSheetContentPreview() { + DngoTheme { +// PlaceBottomSheetContent( +// value = "", +// onValueChange = {}, +// onConfirm = {} +// ) + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/write/component/PlaceWarningDialog.kt b/app/src/main/java/com/min/dnapp/presentation/write/component/PlaceWarningDialog.kt new file mode 100644 index 0000000..4fd666a --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/write/component/PlaceWarningDialog.kt @@ -0,0 +1,115 @@ +package com.min.dnapp.presentation.write.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +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.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import com.min.dnapp.presentation.ui.theme.DngoTheme +import com.min.dnapp.presentation.ui.theme.MomentoTheme + +@Composable +fun PlaceWarningDialog( + onDismiss: () -> Unit, + onCancel: () -> Unit, + onConfirm: () -> Unit +) { + Dialog( + onDismissRequest = onDismiss + ) { + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(16.dp), + color = MomentoTheme.colors.white + ) { + Column( + modifier = Modifier.padding(horizontal = 20.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(Modifier.height(20.dp)) + + Text( + text = "여행지를 변경하시겠어요?", + style = MomentoTheme.typography.title01, + color = MomentoTheme.colors.grayW20 + ) + + Spacer(Modifier.height(8.dp)) + + Text( + text = "해외 여행지를 입력하면 \n기존에 저장한 국내 여행지가 삭제됩니다.", + style = MomentoTheme.typography.body02, + color = MomentoTheme.colors.grayW20, + textAlign = TextAlign.Center, + ) + + Spacer(Modifier.height(20.dp)) + + // 취소/변경 버튼 + Row( + modifier = Modifier.fillMaxWidth() + ) { + Box( + modifier = Modifier + .clickable { onCancel() } + .weight(1f) + .background(color = MomentoTheme.colors.white) + .border(width = 1.dp, color = MomentoTheme.colors.grayW80, shape = RoundedCornerShape(5.dp)) + .padding(vertical = 16.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = "취소", + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.grayW20 + ) + } + Spacer(Modifier.width(12.dp)) + Box( + modifier = Modifier + .clickable { onConfirm() } + .weight(1f) + .background( + color = MomentoTheme.colors.brownBase, + shape = RoundedCornerShape(5.dp) + ) + .padding(vertical = 16.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = "변경", + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.white + ) + } + } + + Spacer(Modifier.height(20.dp)) + } + } + } +} + +@Preview +@Composable +fun PlaceWarningDialogPreview() { + DngoTheme { +// PlaceWarningDialog() + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/write/component/ShareGuide.kt b/app/src/main/java/com/min/dnapp/presentation/write/component/ShareGuide.kt new file mode 100644 index 0000000..bb5bc8c --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/write/component/ShareGuide.kt @@ -0,0 +1,64 @@ +package com.min.dnapp.presentation.write.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.min.dnapp.presentation.ui.icon.AppIcons +import com.min.dnapp.presentation.ui.icon.appicons.DeleteLine +import com.min.dnapp.presentation.ui.icon.appicons.ShareTriangle +import com.min.dnapp.presentation.ui.theme.MomentoTheme + +@Composable +fun ShareGuide( + onClick: () -> Unit +) { + Box( + modifier = Modifier.padding(end = 20.dp) + ) { + Column( + horizontalAlignment = Alignment.End + ) { + Row( + modifier = Modifier + .background(color = MomentoTheme.colors.greenW60, shape = RoundedCornerShape(5.dp)) + .padding(start = 12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "발견 탭에 나의 여행 기록을 공유합니다", + style = MomentoTheme.typography.label, + color = MomentoTheme.colors.grayW20 + ) + Icon( + modifier = Modifier + .clickable { onClick() } + .padding(12.dp), + imageVector = AppIcons.DeleteLine, + contentDescription = null, + tint = MomentoTheme.colors.grayW20 + ) + } + + Row { + Icon( + imageVector = AppIcons.ShareTriangle, + contentDescription = null, + tint = MomentoTheme.colors.greenW60 + ) + Spacer(Modifier.width(16.dp)) + } + } + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/write/component/WeatherBottomSheetContent.kt b/app/src/main/java/com/min/dnapp/presentation/write/component/WeatherBottomSheetContent.kt new file mode 100644 index 0000000..e85502f --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/write/component/WeatherBottomSheetContent.kt @@ -0,0 +1,161 @@ +package com.min.dnapp.presentation.write.component + +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.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +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.res.painterResource +import androidx.compose.ui.unit.dp +import com.min.dnapp.R +import com.min.dnapp.domain.model.WeatherType +import com.min.dnapp.presentation.ui.component.SelectButton +import com.min.dnapp.presentation.ui.theme.MomentoTheme + +@Composable +fun WeatherBottomSheetContent( + onConfirm: (WeatherType) -> Unit +) { + // 선택된 WeatherType 객체 저장 + var selectedWeather by remember { mutableStateOf(null) } + // weather 선택 시 버튼 활성화 + val isButtonEnabled = selectedWeather != null + + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(Modifier.height(20.dp)) + + Text( + text = "날씨는 어땠나요?", + style = MomentoTheme.typography.title02, + color = MomentoTheme.colors.grayW20 + ) + + Spacer(Modifier.height(20.dp)) + + HorizontalDivider(thickness = 1.dp, color = MomentoTheme.colors.grayW90) + + Spacer(Modifier.height(12.dp)) + + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(20.dp) + ) { + // sun / wind / moon + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly + ) { + WeatherItem( + weatherType = WeatherType.SUN, + isSelected = selectedWeather == WeatherType.SUN, + onSelect = { weatherType -> selectedWeather = weatherType } + ) + WeatherItem( + weatherType = WeatherType.WIND, + isSelected = selectedWeather == WeatherType.WIND, + onSelect = { weatherType -> selectedWeather = weatherType } + ) + WeatherItem( + weatherType = WeatherType.MOON, + isSelected = selectedWeather == WeatherType.MOON, + onSelect = { weatherType -> selectedWeather = weatherType } + ) + } + + // thunder / rain / cloud + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly + ) { + WeatherItem( + weatherType = WeatherType.THUNDER, + isSelected = selectedWeather == WeatherType.THUNDER, + onSelect = { weatherType -> selectedWeather = weatherType } + ) + WeatherItem( + weatherType = WeatherType.RAIN, + isSelected = selectedWeather == WeatherType.RAIN, + onSelect = { weatherType -> selectedWeather = weatherType } + ) + WeatherItem( + weatherType = WeatherType.CLOUD, + isSelected = selectedWeather == WeatherType.CLOUD, + onSelect = { weatherType -> selectedWeather = weatherType } + ) + } + + // snow + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + WeatherItem( + weatherType = WeatherType.SNOW, + isSelected = selectedWeather == WeatherType.SNOW, + onSelect = { weatherType -> selectedWeather = weatherType } + ) + } + } + + Spacer(Modifier.height(32.dp)) + + // 선택 버튼 + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp) + ) { + SelectButton( + enabled = isButtonEnabled, + onConfirm = { + selectedWeather?.let(onConfirm) + } + ) + Spacer(Modifier.height(16.dp)) + } + } +} + +@Composable +fun WeatherItem( + weatherType: WeatherType, + isSelected: Boolean, + onSelect: (WeatherType) -> Unit +) { + Box( + contentAlignment = Alignment.Center + ) { + Image( + modifier = Modifier.clickable { + onSelect(weatherType) + }, + painter = painterResource(weatherType.resId), + contentDescription = null + ) + if (isSelected) { + Image( + modifier = Modifier.size(48.dp), + painter = painterResource(R.drawable.ew_check), + contentDescription = null + ) + } + } +} diff --git a/app/src/main/java/com/min/dnapp/presentation/write/component/WriteStampDialog.kt b/app/src/main/java/com/min/dnapp/presentation/write/component/WriteStampDialog.kt new file mode 100644 index 0000000..cd9b680 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/presentation/write/component/WriteStampDialog.kt @@ -0,0 +1,71 @@ +package com.min.dnapp.presentation.write.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +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.unit.dp +import androidx.compose.ui.window.Dialog +import com.min.dnapp.R +import com.min.dnapp.presentation.ui.theme.MomentoTheme + +@Composable +fun WriteStampDialog( + onDismiss: () -> Unit, + onConfirm: () -> Unit +) { + Dialog( + onDismissRequest = onDismiss + ) { + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(16.dp), + color = MomentoTheme.colors.white + ) { + Column( + modifier = Modifier.padding(horizontal = 20.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(Modifier.height(20.dp)) + Image( + painter = painterResource(R.drawable.write_stamp), + contentDescription = null + ) + Spacer(Modifier.height(20.dp)) + Text( + text = "스탬프 1개가 지급되었어요!", + style = MomentoTheme.typography.title01, + color = MomentoTheme.colors.grayW20 + ) + Spacer(Modifier.height(20.dp)) + Box( + modifier = Modifier + .clickable { onConfirm() } + .fillMaxWidth() + .background(color = MomentoTheme.colors.brownBase, shape = RoundedCornerShape(10.dp)) + .padding(vertical = 16.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = "확인했어요", + style = MomentoTheme.typography.body01, + color = MomentoTheme.colors.white + ) + } + Spacer(Modifier.height(20.dp)) + } + } + } +} diff --git a/app/src/main/java/com/min/dnapp/util/DateTimeExt.kt b/app/src/main/java/com/min/dnapp/util/DateTimeExt.kt new file mode 100644 index 0000000..09493bc --- /dev/null +++ b/app/src/main/java/com/min/dnapp/util/DateTimeExt.kt @@ -0,0 +1,78 @@ +package com.min.dnapp.util + +import android.util.Log +import java.text.SimpleDateFormat +import java.time.Instant +import java.time.LocalDate +import java.time.ZoneId +import java.util.Date +import java.util.Locale +import java.util.concurrent.TimeUnit + +/** + * Long(밀리초) 값을 시스템 기본 시간대를 기준으로 LocalDate로 변환 + */ +fun Long?.toLocalDate(): LocalDate? { + if (this == null || this == 0L) { + return null + } + + return Instant.ofEpochMilli(this) + .atZone(ZoneId.systemDefault()) + .toLocalDate() +} + +/** + * 원하는 날짜 포맷으로 변환 + */ +fun Long.toDateString(format: String): String { + val date = Date(this) + Log.d("record", "toDateString - date: $date") + + val formatter = SimpleDateFormat(format, Locale.getDefault()) + Log.d("record", "toDateString - formatter: $formatter") + + return formatter.format(date) +} + +/** + * 현재 시간 기준, 경과 시간으로 변환 + * + * @return (예: "5분 전, "2시간 전") + */ +fun Long?.toTimeAgoString(): String { + if (this == null || this == 0L) { + return "날짜 없음" + } + + // 현재 시간 + val nowMillis = System.currentTimeMillis() + // 입력된 시간 + val inputMillis = this + + // 시간 차이 + val diffMillis = Math.abs(nowMillis - inputMillis) + + // 시간 차이를 단위별로 변환 + val seconds = TimeUnit.MILLISECONDS.toSeconds(diffMillis) + val minutes = TimeUnit.MILLISECONDS.toMinutes(diffMillis) + val hours = TimeUnit.MILLISECONDS.toHours(diffMillis) + val days = TimeUnit.MILLISECONDS.toDays(diffMillis) + + return when { + // 1분 미만 + seconds < 60 -> "방금 전" + // 1시간 미만 + minutes < 60 -> "${minutes}분 전" + // 24시간 미만 + hours < 24 -> "${hours}시간 전" + // 7일 미만 + days < 7 -> "${days}일 전" + // 30일 미만 + days < 30 -> "${days / 7}주 전" + // 1년 미만 + days < 365 -> "${days / 30}달 전" + // 1년 이상 + else -> "${days / 365}년 전" + } +} diff --git a/app/src/main/java/com/min/dnapp/util/Resource.kt b/app/src/main/java/com/min/dnapp/util/Resource.kt new file mode 100644 index 0000000..6404418 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/util/Resource.kt @@ -0,0 +1,7 @@ +package com.min.dnapp.util + +sealed class Resource(val data: T? = null, val message: String? = null) { + class Success(data: T) : Resource(data) + class Error(message: String, data: T? = null) : Resource(data, message) + class Loading(data: T? = null) : Resource(data) +} \ No newline at end of file diff --git a/app/src/main/java/com/min/dnapp/util/StringExt.kt b/app/src/main/java/com/min/dnapp/util/StringExt.kt new file mode 100644 index 0000000..1cb89b7 --- /dev/null +++ b/app/src/main/java/com/min/dnapp/util/StringExt.kt @@ -0,0 +1,17 @@ +package com.min.dnapp.util + +/** + * 문자열에서 태그를 제거하는 확장함수 + * 네이버 검색 API의 title 필드에 포함된 태그를 제거하는데 사용 + */ +fun String.removeTag(): String { + return this.replace(Regex("<(/)?b>"), "") +} + +/** + * 카테고리 문자열에서 가장 마지막 카테고리를 추출하는 확장함수 + * 예: "음식점>일식>돈가스" -> "돈가스" + */ +fun String.extractLastCategory(): String { + return this.split(">").lastOrNull() ?: this +} diff --git a/app/src/main/res/drawable/badge_bronze.xml b/app/src/main/res/drawable/badge_bronze.xml new file mode 100644 index 0000000..e55da67 --- /dev/null +++ b/app/src/main/res/drawable/badge_bronze.xml @@ -0,0 +1,250 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/badge_gold.xml b/app/src/main/res/drawable/badge_gold.xml new file mode 100644 index 0000000..f95f729 --- /dev/null +++ b/app/src/main/res/drawable/badge_gold.xml @@ -0,0 +1,250 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/badge_new.xml b/app/src/main/res/drawable/badge_new.xml new file mode 100644 index 0000000..027c806 --- /dev/null +++ b/app/src/main/res/drawable/badge_new.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/badge_silver.xml b/app/src/main/res/drawable/badge_silver.xml new file mode 100644 index 0000000..95c20e1 --- /dev/null +++ b/app/src/main/res/drawable/badge_silver.xml @@ -0,0 +1,370 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/beach.png b/app/src/main/res/drawable/beach.png new file mode 100644 index 0000000..52c9aa0 Binary files /dev/null and b/app/src/main/res/drawable/beach.png differ diff --git a/app/src/main/res/drawable/bell_comment.xml b/app/src/main/res/drawable/bell_comment.xml new file mode 100644 index 0000000..9c3e3ea --- /dev/null +++ b/app/src/main/res/drawable/bell_comment.xml @@ -0,0 +1,22 @@ + + + + + diff --git a/app/src/main/res/drawable/emotion_angry.xml b/app/src/main/res/drawable/emotion_angry.xml new file mode 100644 index 0000000..ccabfb6 --- /dev/null +++ b/app/src/main/res/drawable/emotion_angry.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/emotion_feel.xml b/app/src/main/res/drawable/emotion_feel.xml new file mode 100644 index 0000000..cee3641 --- /dev/null +++ b/app/src/main/res/drawable/emotion_feel.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/emotion_happy.xml b/app/src/main/res/drawable/emotion_happy.xml new file mode 100644 index 0000000..9883e53 --- /dev/null +++ b/app/src/main/res/drawable/emotion_happy.xml @@ -0,0 +1,30 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/emotion_love.xml b/app/src/main/res/drawable/emotion_love.xml new file mode 100644 index 0000000..a5a35a8 --- /dev/null +++ b/app/src/main/res/drawable/emotion_love.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/app/src/main/res/drawable/emotion_sad.xml b/app/src/main/res/drawable/emotion_sad.xml new file mode 100644 index 0000000..e3a6ef7 --- /dev/null +++ b/app/src/main/res/drawable/emotion_sad.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/emotion_shine.xml b/app/src/main/res/drawable/emotion_shine.xml new file mode 100644 index 0000000..86c8197 --- /dev/null +++ b/app/src/main/res/drawable/emotion_shine.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/drawable/emotion_surprise.xml b/app/src/main/res/drawable/emotion_surprise.xml new file mode 100644 index 0000000..0672f65 --- /dev/null +++ b/app/src/main/res/drawable/emotion_surprise.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ew_check.xml b/app/src/main/res/drawable/ew_check.xml new file mode 100644 index 0000000..a0fb31b --- /dev/null +++ b/app/src/main/res/drawable/ew_check.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_explore.xml b/app/src/main/res/drawable/ic_explore.xml new file mode 100644 index 0000000..4c647e3 --- /dev/null +++ b/app/src/main/res/drawable/ic_explore.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_home.xml b/app/src/main/res/drawable/ic_home.xml new file mode 100644 index 0000000..bb80386 --- /dev/null +++ b/app/src/main/res/drawable/ic_home.xml @@ -0,0 +1,16 @@ + + + + + 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 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 0000000..2b068d1 --- /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_momento.xml b/app/src/main/res/drawable/ic_momento.xml new file mode 100644 index 0000000..b181568 --- /dev/null +++ b/app/src/main/res/drawable/ic_momento.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_momento_foreground.xml b/app/src/main/res/drawable/ic_momento_foreground.xml new file mode 100644 index 0000000..b181568 --- /dev/null +++ b/app/src/main/res/drawable/ic_momento_foreground.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_my.xml b/app/src/main/res/drawable/ic_my.xml new file mode 100644 index 0000000..0691659 --- /dev/null +++ b/app/src/main/res/drawable/ic_my.xml @@ -0,0 +1,20 @@ + + + + + + diff --git a/app/src/main/res/drawable/image_basic.xml b/app/src/main/res/drawable/image_basic.xml new file mode 100644 index 0000000..600ee09 --- /dev/null +++ b/app/src/main/res/drawable/image_basic.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/login_cloud.xml b/app/src/main/res/drawable/login_cloud.xml new file mode 100644 index 0000000..2ae5c65 --- /dev/null +++ b/app/src/main/res/drawable/login_cloud.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/login_momento.xml b/app/src/main/res/drawable/login_momento.xml new file mode 100644 index 0000000..841b2d4 --- /dev/null +++ b/app/src/main/res/drawable/login_momento.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/login_mount.xml b/app/src/main/res/drawable/login_mount.xml new file mode 100644 index 0000000..d8a7779 --- /dev/null +++ b/app/src/main/res/drawable/login_mount.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/logo_momento.xml b/app/src/main/res/drawable/logo_momento.xml new file mode 100644 index 0000000..0f08d1a --- /dev/null +++ b/app/src/main/res/drawable/logo_momento.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/logo_onboarding.xml b/app/src/main/res/drawable/logo_onboarding.xml new file mode 100644 index 0000000..00ffcb3 --- /dev/null +++ b/app/src/main/res/drawable/logo_onboarding.xml @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/logo_onboarding2.xml b/app/src/main/res/drawable/logo_onboarding2.xml new file mode 100644 index 0000000..a3d99bc --- /dev/null +++ b/app/src/main/res/drawable/logo_onboarding2.xml @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/logo_onboarding3.xml b/app/src/main/res/drawable/logo_onboarding3.xml new file mode 100644 index 0000000..af4c493 --- /dev/null +++ b/app/src/main/res/drawable/logo_onboarding3.xml @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/logo_profile.xml b/app/src/main/res/drawable/logo_profile.xml new file mode 100644 index 0000000..2294644 --- /dev/null +++ b/app/src/main/res/drawable/logo_profile.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/logo_profile2.xml b/app/src/main/res/drawable/logo_profile2.xml new file mode 100644 index 0000000..f1fd730 --- /dev/null +++ b/app/src/main/res/drawable/logo_profile2.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/logo_profile3.xml b/app/src/main/res/drawable/logo_profile3.xml new file mode 100644 index 0000000..eb43256 --- /dev/null +++ b/app/src/main/res/drawable/logo_profile3.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/logo_profile4.xml b/app/src/main/res/drawable/logo_profile4.xml new file mode 100644 index 0000000..08be0b3 --- /dev/null +++ b/app/src/main/res/drawable/logo_profile4.xml @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/logo_profile5.xml b/app/src/main/res/drawable/logo_profile5.xml new file mode 100644 index 0000000..d42ecc8 --- /dev/null +++ b/app/src/main/res/drawable/logo_profile5.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/logo_profile6.xml b/app/src/main/res/drawable/logo_profile6.xml new file mode 100644 index 0000000..e5307b7 --- /dev/null +++ b/app/src/main/res/drawable/logo_profile6.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/logo_profile7.xml b/app/src/main/res/drawable/logo_profile7.xml new file mode 100644 index 0000000..fc59bbc --- /dev/null +++ b/app/src/main/res/drawable/logo_profile7.xml @@ -0,0 +1,241 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/logo_profile8.xml b/app/src/main/res/drawable/logo_profile8.xml new file mode 100644 index 0000000..d774ddd --- /dev/null +++ b/app/src/main/res/drawable/logo_profile8.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/logo_snackbar.xml b/app/src/main/res/drawable/logo_snackbar.xml new file mode 100644 index 0000000..0846c94 --- /dev/null +++ b/app/src/main/res/drawable/logo_snackbar.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/logo_splash.xml b/app/src/main/res/drawable/logo_splash.xml new file mode 100644 index 0000000..2afcf51 --- /dev/null +++ b/app/src/main/res/drawable/logo_splash.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/app/src/main/res/drawable/my_record.xml b/app/src/main/res/drawable/my_record.xml new file mode 100644 index 0000000..57b8b4b --- /dev/null +++ b/app/src/main/res/drawable/my_record.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/app/src/main/res/drawable/my_stamp.xml b/app/src/main/res/drawable/my_stamp.xml new file mode 100644 index 0000000..d5d158b --- /dev/null +++ b/app/src/main/res/drawable/my_stamp.xml @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/record_empty.xml b/app/src/main/res/drawable/record_empty.xml new file mode 100644 index 0000000..8f693ed --- /dev/null +++ b/app/src/main/res/drawable/record_empty.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/drawable/splash_layer_list.xml b/app/src/main/res/drawable/splash_layer_list.xml new file mode 100644 index 0000000..019027e --- /dev/null +++ b/app/src/main/res/drawable/splash_layer_list.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/trip.png b/app/src/main/res/drawable/trip.png new file mode 100644 index 0000000..f75814c Binary files /dev/null and b/app/src/main/res/drawable/trip.png differ diff --git a/app/src/main/res/drawable/trip2.png b/app/src/main/res/drawable/trip2.png new file mode 100644 index 0000000..f218399 Binary files /dev/null and b/app/src/main/res/drawable/trip2.png differ diff --git a/app/src/main/res/drawable/trip3.png b/app/src/main/res/drawable/trip3.png new file mode 100644 index 0000000..f5253ff Binary files /dev/null and b/app/src/main/res/drawable/trip3.png differ diff --git a/app/src/main/res/drawable/weather_cloud.xml b/app/src/main/res/drawable/weather_cloud.xml new file mode 100644 index 0000000..dc5f758 --- /dev/null +++ b/app/src/main/res/drawable/weather_cloud.xml @@ -0,0 +1,33 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/weather_moon.xml b/app/src/main/res/drawable/weather_moon.xml new file mode 100644 index 0000000..d637baa --- /dev/null +++ b/app/src/main/res/drawable/weather_moon.xml @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/weather_rain.xml b/app/src/main/res/drawable/weather_rain.xml new file mode 100644 index 0000000..524aa23 --- /dev/null +++ b/app/src/main/res/drawable/weather_rain.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/weather_snow.xml b/app/src/main/res/drawable/weather_snow.xml new file mode 100644 index 0000000..208b6ac --- /dev/null +++ b/app/src/main/res/drawable/weather_snow.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/weather_sun.xml b/app/src/main/res/drawable/weather_sun.xml new file mode 100644 index 0000000..07c0feb --- /dev/null +++ b/app/src/main/res/drawable/weather_sun.xml @@ -0,0 +1,33 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/weather_thunder.xml b/app/src/main/res/drawable/weather_thunder.xml new file mode 100644 index 0000000..591c1f5 --- /dev/null +++ b/app/src/main/res/drawable/weather_thunder.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/weather_wind.xml b/app/src/main/res/drawable/weather_wind.xml new file mode 100644 index 0000000..eb1bf98 --- /dev/null +++ b/app/src/main/res/drawable/weather_wind.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/write_finish.xml b/app/src/main/res/drawable/write_finish.xml new file mode 100644 index 0000000..88b33a8 --- /dev/null +++ b/app/src/main/res/drawable/write_finish.xml @@ -0,0 +1,200 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/write_place.xml b/app/src/main/res/drawable/write_place.xml new file mode 100644 index 0000000..f1a5275 --- /dev/null +++ b/app/src/main/res/drawable/write_place.xml @@ -0,0 +1,24 @@ + + + + + + diff --git a/app/src/main/res/drawable/write_stamp.xml b/app/src/main/res/drawable/write_stamp.xml new file mode 100644 index 0000000..fa61a9c --- /dev/null +++ b/app/src/main/res/drawable/write_stamp.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/font/pretendard_bold.otf b/app/src/main/res/font/pretendard_bold.otf new file mode 100644 index 0000000..8e5e30a Binary files /dev/null and b/app/src/main/res/font/pretendard_bold.otf differ diff --git a/app/src/main/res/font/pretendard_medium.otf b/app/src/main/res/font/pretendard_medium.otf new file mode 100644 index 0000000..0575069 Binary files /dev/null and b/app/src/main/res/font/pretendard_medium.otf differ diff --git a/app/src/main/res/font/pretendard_regular.otf b/app/src/main/res/font/pretendard_regular.otf new file mode 100644 index 0000000..08bf4cf Binary files /dev/null and b/app/src/main/res/font/pretendard_regular.otf differ diff --git a/app/src/main/res/font/pretendard_semibold.otf b/app/src/main/res/font/pretendard_semibold.otf new file mode 100644 index 0000000..e7e36ab Binary files /dev/null and b/app/src/main/res/font/pretendard_semibold.otf differ diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_momento.xml b/app/src/main/res/mipmap-anydpi-v26/ic_momento.xml new file mode 100644 index 0000000..7f9f2eb --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_momento.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_momento_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_momento_round.xml new file mode 100644 index 0000000..7f9f2eb --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_momento_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi/ic_launcher.xml b/app/src/main/res/mipmap-anydpi/ic_launcher.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ 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 0000000..c209e78 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_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d 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-hdpi/ic_momento.webp b/app/src/main/res/mipmap-hdpi/ic_momento.webp new file mode 100644 index 0000000..14206db Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_momento.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_momento_round.webp b/app/src/main/res/mipmap-hdpi/ic_momento_round.webp new file mode 100644 index 0000000..c2337e9 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_momento_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 0000000..4f0f1d6 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_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d 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-mdpi/ic_momento.webp b/app/src/main/res/mipmap-mdpi/ic_momento.webp new file mode 100644 index 0000000..df539ec Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_momento.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_momento_round.webp b/app/src/main/res/mipmap-mdpi/ic_momento_round.webp new file mode 100644 index 0000000..12f0f58 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_momento_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 0000000..948a307 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_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 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-xhdpi/ic_momento.webp b/app/src/main/res/mipmap-xhdpi/ic_momento.webp new file mode 100644 index 0000000..9dd5fd3 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_momento.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_momento_round.webp b/app/src/main/res/mipmap-xhdpi/ic_momento_round.webp new file mode 100644 index 0000000..673c329 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_momento_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 0000000..28d4b77 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_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 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-xxhdpi/ic_momento.webp b/app/src/main/res/mipmap-xxhdpi/ic_momento.webp new file mode 100644 index 0000000..ade4bcf Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_momento.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_momento_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_momento_round.webp new file mode 100644 index 0000000..c3213c3 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_momento_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 0000000..aa7d642 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_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_momento.webp b/app/src/main/res/mipmap-xxxhdpi/ic_momento.webp new file mode 100644 index 0000000..4c74cbb Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_momento.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_momento_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_momento_round.webp new file mode 100644 index 0000000..cec8ca1 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_momento_round.webp differ diff --git a/app/src/main/res/values-v31/themes.xml b/app/src/main/res/values-v31/themes.xml new file mode 100644 index 0000000..f255e1e --- /dev/null +++ b/app/src/main/res/values-v31/themes.xml @@ -0,0 +1,15 @@ + + + + + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..3f4a0dc --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,11 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + #FFEDE2DA + \ No newline at end of file diff --git a/app/src/main/res/values/ic_momento_background.xml b/app/src/main/res/values/ic_momento_background.xml new file mode 100644 index 0000000..d08fd9e --- /dev/null +++ b/app/src/main/res/values/ic_momento_background.xml @@ -0,0 +1,4 @@ + + + #FFFFFF + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..60fb829 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + 모멘토 + \ 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 0000000..0cba15e --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,14 @@ + + + + + + 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 0000000..4df9255 --- /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 0000000..9ee9997 --- /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/min/dnapp/ExampleUnitTest.kt b/app/src/test/java/com/min/dnapp/ExampleUnitTest.kt new file mode 100644 index 0000000..047d7f7 --- /dev/null +++ b/app/src/test/java/com/min/dnapp/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.min.dnapp + +import org.junit.Test + +import org.junit.Assert.* + +/** + * 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) + } +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..0bee734 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,10 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { + alias(libs.plugins.android.application) apply false + alias(libs.plugins.kotlin.android) apply false + alias(libs.plugins.kotlin.compose) apply false + alias(libs.plugins.google.services) apply false + alias(libs.plugins.ksp) apply false + alias(libs.plugins.hilt) apply false + alias(libs.plugins.firebase.crashlytics) apply false +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..20e2a01 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,23 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. For more details, visit +# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..c44dc68 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,73 @@ +[versions] +agp = "8.11.1" +kotlin = "2.2.0" +coreKtx = "1.17.0" +junit = "4.13.2" +junitVersion = "1.3.0" +espressoCore = "3.7.0" +lifecycleRuntimeKtx = "2.9.2" +activityCompose = "1.10.1" +#composeBom = "2024.09.00" +composeBom = "2025.05.00" +google = "4.4.3" +firebaseBom = "34.1.0" +crashlytics = "3.0.6" +kakao = "2.21.6" +nav = "2.9.3" +hilt = "2.56.2" +ksp = "2.2.0-2.0.2" +hiltNavigationCompose = "1.2.0" +splash = "1.0.1" +retrofit = "3.0.0" +kotlinSerialization = "1.9.0" +okhttp = "5.0.0" +coil = "3.1.0" +lottie = "6.6.9" +datastore = "1.1.7" + +[libraries] +androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } +junit = { group = "junit", name = "junit", version.ref = "junit" } +androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } +androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } +androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } +androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } +androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } +androidx-ui = { group = "androidx.compose.ui", name = "ui" } +androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } +androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } +androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } +androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } +androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } +androidx-material3 = { group = "androidx.compose.material3", name = "material3" } + +firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" } +firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics-ndk" } +firebase-analytics = { module = "com.google.firebase:firebase-analytics" } +firebase-auth = { module = "com.google.firebase:firebase-auth" } +firebase-firestore = { module = "com.google.firebase:firebase-firestore" } +firebase-storage = { module = "com.google.firebase:firebase-storage" } +kakao-sdk = { module = "com.kakao.sdk:v2-user", version.ref = "kakao" } +androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "nav" } +hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" } +hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hilt" } +hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltNavigationCompose" } +splash = { module = "androidx.core:core-splashscreen", version.ref = "splash" } +retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" } +retrofit-converter-kotlinx-serialization = { module = "com.squareup.retrofit2:converter-kotlinx-serialization", version.ref = "retrofit" } +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinSerialization" } +okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" } +coil = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" } +coil-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" } +lottie-compose = { module = "com.airbnb.android:lottie-compose", version.ref = "lottie" } +datastore-preferences = { module = "androidx.datastore:datastore-preferences" , version.ref = "datastore" } + +[plugins] +android-application = { id = "com.android.application", version.ref = "agp" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +google-services = { id = "com.google.gms.google-services", version.ref = "google" } +hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } +ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } +kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "crashlytics" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..d12884d --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sun Aug 24 15:58:59 KST 2025 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..ac1b06f --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..18526ed --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + repositories { + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } + mavenCentral() + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + maven { url = java.net.URI("https://devrepo.kakao.com/nexus/content/groups/public/") } + } +} + +rootProject.name = "Dngo" +include(":app") + \ No newline at end of file