diff --git a/.codecov.yml b/.codecov.yml
new file mode 100644
index 00000000..4ef10f8b
--- /dev/null
+++ b/.codecov.yml
@@ -0,0 +1,24 @@
+coverage:
+ status:
+ project:
+ default:
+ target: 60%
+ threshold: 2%
+
+comment:
+ layout: "reach, diff, flags, files"
+ behavior: default
+ require_changes: true
+
+parsers:
+ gcov:
+ branch_detection:
+ conditional: true
+ loop: true
+ method: true
+ macro: true
+
+ignore:
+ - "**/di/**"
+ - "**/BuildConfig.*"
+ - "**/generated/**"
diff --git a/.coderabbit.yaml b/.coderabbit.yaml
new file mode 100644
index 00000000..e31fc13e
--- /dev/null
+++ b/.coderabbit.yaml
@@ -0,0 +1,19 @@
+# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
+language: "ko-KR"
+early_access: false
+reviews:
+ profile: "chill"
+ request_changes_workflow: true
+ high_level_summary: true
+ poem: false
+ review_status: true
+ collapse_walkthrough: false
+ abort_on_close: true
+ auto_review:
+ enabled: true
+ drafts: false
+ finishing_touches:
+ unit_tests:
+ enabled: true
+chat:
+ auto_reply: true
diff --git a/.github/workflows/android_cd.yml b/.github/workflows/android_cd.yml
index c00a6bba..af61cefd 100644
--- a/.github/workflows/android_cd.yml
+++ b/.github/workflows/android_cd.yml
@@ -3,10 +3,10 @@ name: Orbit CD
on:
push:
branches:
- - main
+ - 'release/**'
pull_request:
branches:
- - main
+ - 'release/**'
jobs:
cd:
@@ -14,11 +14,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- # 1. Code Checkout
+ # 1. Checkout
- name: Checkout code
uses: actions/checkout@v4
- # 2. Gradle Cache
+ # 2. Cache Gradle
- name: Cache Gradle dependencies
uses: actions/cache@v4
with:
@@ -29,7 +29,7 @@ jobs:
restore-keys: |
${{ runner.os }}-gradle-
- # 3. JDK 17
+ # 3. Set up JDK 17
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
@@ -37,131 +37,74 @@ jobs:
distribution: 'corretto'
cache: gradle
- # 4. Grant Execute Permission
+ # 4. Change gradlew permissions
- name: Change gradlew permissions
run: chmod +x gradlew
- # 5. Install Firebase CLI
- - name: Install Firebase CLI
- run: curl -sL https://firebase.tools | bash
-
- # 6. Decode google-services.json for debug
- - name: Decode google-services.json (debug)
- env:
- FIREBASE_SECRET: ${{ secrets.FIREBASE_SECRET_DEBUG }}
- run: |
- mkdir -p app/src/dev
- echo $FIREBASE_SECRET | base64 --decode > app/src/dev/google-services.json
-
- # 7. Decode google-services.json for release
+ # 5. Decode google-services.json (release)
- name: Decode google-services.json (release)
env:
- FIREBASE_SECRET: ${{ secrets.FIREBASE_SECRET_RELEASE }}
+ FIREBASE_SECRET: ${{ secrets.FIREBASE_SECRET_RELEASE }}
run: echo $FIREBASE_SECRET | base64 --decode > app/google-services.json
- # 8. Add Local Properties
+ # 6. Add Local Properties
- name: Add Local Properties
env:
BASE_URL: ${{ secrets.BASE_URL }}
AMPLITUDE_API_KEY: ${{ secrets.AMPLITUDE_API_KEY }}
- ADMOB_APP_ID_DEBUG: ${{ secrets.ADMOB_APP_ID_DEBUG }}
ADMOB_APP_ID_RELEASE: ${{ secrets.ADMOB_APP_ID_RELEASE }}
- ADMOB_AD_UNIT_ID_DEBUG: ${{ secrets.ADMOB_AD_UNIT_ID_DEBUG }}
ADMOB_AD_UNIT_ID_RELEASE: ${{ secrets.ADMOB_AD_UNIT_ID_RELEASE }}
run: |
- echo -e "baseUrl=$BASE_URL" > local.properties
- echo -e "amplitudeApiKey=$AMPLITUDE_API_KEY" >> local.properties
- echo -e "admobAppIdDebug=$ADMOB_APP_ID_DEBUG" >> local.properties
- echo -e "admobAppIdRelease=$ADMOB_APP_ID_RELEASE" >> local.properties
- echo -e "admobAdUnitIdDebug=$ADMOB_AD_UNIT_ID_DEBUG" >> local.properties
- echo -e "admobAdUnitIdRelease=$ADMOB_AD_UNIT_ID_RELEASE" >> local.properties
-
- # 9. Debug Local Properties Check
- - name: Debug Local Properties
- run: cat local.properties
-
- # 10. Ktlint
- - name: Run Ktlint Check
- run: ./gradlew ktlintCheck --stacktrace
-
- # 11. Debug APK Build
- - name: Build Debug APK
- run: ./gradlew assembleDebug --stacktrace
-
- # 12. Release AAB Build
- - name: Build Release AAB
- run: ./gradlew bundleRelease --stacktrace
+ echo "baseUrl=$BASE_URL" > local.properties
+ echo "amplitudeApiKey=$AMPLITUDE_API_KEY" >> local.properties
+ echo "admobAppIdRelease=$ADMOB_APP_ID_RELEASE" >> local.properties
+ echo "admobAdUnitIdRelease=$ADMOB_AD_UNIT_ID_RELEASE" >> local.properties
- # 13. Release APK Build
+ # 7. Build Release APK
- name: Build Release APK
run: ./gradlew assembleRelease --stacktrace
- # 14. AAB Artifact Upload
- - name: Upload Release AAB
- uses: actions/upload-artifact@v4
- with:
- name: release-aab
- path: app/build/outputs/bundle/release/app-release.aab
-
- # 15. APK Artifact Upload
+ # 8. Upload Release APK
- name: Upload Release APK
uses: actions/upload-artifact@v4
with:
name: release-apk
path: app/build/outputs/apk/release/app-release.apk
- # 16. Set up Firebase Service Account Credentials
- - name: Set up Firebase Service Account Credentials
+ # 9. Set up Firebase Credentials
+ - name: Set up Firebase Credentials
env:
GOOGLE_APPLICATION_CREDENTIALS_JSON: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS_JSON }}
run: |
echo "$GOOGLE_APPLICATION_CREDENTIALS_JSON" | base64 --decode > $HOME/firebase-credentials.json
- echo "๐ฅ Firebase Credentials JSON ์์ฑ ์๋ฃ!"
- ls -l $HOME/firebase-credentials.json
- export GOOGLE_APPLICATION_CREDENTIALS=$HOME/firebase-credentials.json
- echo "GOOGLE_APPLICATION_CREDENTIALS=$GOOGLE_APPLICATION_CREDENTIALS"
-
- # 17. Firebase CLI ์ธ์ฆ ํ์ธ
- - name: Check Firebase CLI Authentication
- run: |
- export GOOGLE_APPLICATION_CREDENTIALS=$HOME/firebase-credentials.json
+ echo "GOOGLE_APPLICATION_CREDENTIALS=$HOME/firebase-credentials.json" >> $GITHUB_ENV
- echo "๐ GOOGLE_APPLICATION_CREDENTIALS ์ค์ ๊ฐ:"
- echo $GOOGLE_APPLICATION_CREDENTIALS
- ls -l $GOOGLE_APPLICATION_CREDENTIALS
-
- echo "๐ ํ์ฌ Firebase ํ๋ก์ ํธ ๋ชฉ๋ก ํ์ธ:"
- firebase projects:list || (echo "โ Firebase ์ธ์ฆ ์คํจ!"; exit 1)
+ # 10. Install Firebase CLI
+ - name: Install Firebase CLI
+ run: curl -sL https://firebase.tools | bash
- # 18. Firebase App Distribution Upload
+ # 11. Upload to Firebase App Distribution
- name: Upload APK to Firebase App Distribution
env:
- GOOGLE_APPLICATION_CREDENTIALS: $HOME/firebase-credentials.json
+ GOOGLE_APPLICATION_CREDENTIALS: ${{ env.GOOGLE_APPLICATION_CREDENTIALS }}
FIREBASE_APP_ID: ${{ secrets.FIREBASE_APP_ID }}
run: |
- echo "๐ฅ FIREBASE_APP_ID ํ์ธ: $FIREBASE_APP_ID"
-
- # ๋ง์ฝ FIREBASE_APP_ID๊ฐ ์์ผ๋ฉด ์๋ฌ ์ถ๋ ฅ ํ ์ข
๋ฃ
if [ -z "$FIREBASE_APP_ID" ]; then
- echo "โ ERROR: FIREBASE_APP_ID๊ฐ ์ค์ ๋์ง ์์์ต๋๋ค. GitHub Secrets์์ ํ์ธํ์ธ์."
+ echo "โ ERROR: FIREBASE_APP_ID is missing!"
exit 1
fi
- # GOOGLE_APPLICATION_CREDENTIALS๋ฅผ ๋ค์ ์ค์
- export GOOGLE_APPLICATION_CREDENTIALS=$HOME/firebase-credentials.json
- echo "GOOGLE_APPLICATION_CREDENTIALS=$GOOGLE_APPLICATION_CREDENTIALS"
-
firebase appdistribution:distribute app/build/outputs/apk/release/app-release.apk \
- --app "$FIREBASE_APP_ID" \
- --release-notes "๐ ์๋ก์ด ๋ฐ๋ชจ ๋ฒ์ ์ด ๋ฐฐํฌ๋์์ต๋๋ค!" \
- --groups "orbit-tester-group"
+ --app "$FIREBASE_APP_ID" \
+ --release-notes "๐ release ๋ธ๋์น์์ ์ ๋น๋๊ฐ ์
๋ก๋๋์์ต๋๋ค!" \
+ --groups "orbit-tester-group"
- # 19. Notify Discord
+ # 12. Notify Discord
- name: Notify Discord
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
run: |
curl -H "Content-Type: application/json" \
- -X POST \
- -d '{"content": "๐ ์๋ก์ด ๋ฐ๋ชจ ๋ฒ์ ์ด Firebase App Distribution์ ์
๋ก๋๋์์ต๋๋ค!\nAPK ๋ค์ด๋ก๋: https://appdistribution.firebase.google.com"}' \
- $DISCORD_WEBHOOK_URL
+ -X POST \
+ -d '{"content": "๐ Firebase App Distribution์ ์ APK๊ฐ ์
๋ก๋๋์์ต๋๋ค!\n๐ ๋ค์ด๋ก๋ ๋งํฌ: https://appdistribution.firebase.google.com"}' \
+ $DISCORD_WEBHOOK_URL
diff --git a/.github/workflows/android_ci.yml b/.github/workflows/android_ci.yml
index b8974a0d..3e073b75 100644
--- a/.github/workflows/android_ci.yml
+++ b/.github/workflows/android_ci.yml
@@ -1,12 +1,12 @@
name: Orbit CI
on:
- pull_request:
- branches: [develop]
- paths:
- - 'app/**'
- - 'build.gradle'
- - '**/*.kt'
+ pull_request:
+ branches: [develop]
+ paths:
+ - '**/*.kt'
+ - 'build.gradle'
+ - 'app/**'
jobs:
build:
@@ -80,3 +80,16 @@ jobs:
# Run Lint and Build
- name: Run lint and build
run: ./gradlew ktlintCheck assembleDebug
+
+ # Run Unit Test and Generate Coverage
+ - name: Run unit tests and generate coverage
+ run: ./gradlew generateTestCoverageReport
+
+ # Upload Coverage to Codecov
+ - name: Upload coverage to Codecov
+ uses: codecov/codecov-action@v4
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+ files: data/build/reports/jacoco/testDebugUnitTestCoverage/testDebugUnitTestCoverage.xml
+ name: codecov-report
+ fail_ci_if_error: true
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 4acc31ae..6c350ede 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -8,11 +8,12 @@ plugins {
android {
namespace = "com.yapp.orbit"
+ compileSdk = 35
defaultConfig {
- versionCode = 5
- versionName = "1.0.3"
- targetSdk = 34
+ versionCode = 6
+ versionName = "1.1.3"
+ targetSdk = 35
}
buildTypes {
@@ -29,22 +30,30 @@ android {
dependencies {
implementation(projects.core.common)
+ implementation(projects.core.analytics)
implementation(projects.core.buildconfig)
implementation(projects.core.network)
implementation(projects.core.designsystem)
implementation(projects.core.datastore)
implementation(projects.core.alarm)
implementation(projects.core.media)
+ implementation(projects.core.ui)
implementation(projects.data)
implementation(projects.domain)
+ implementation(projects.feature.splash)
implementation(projects.feature.onboarding)
implementation(projects.feature.home)
implementation(projects.feature.alarmInteraction)
implementation(projects.feature.fortune)
implementation(projects.feature.mission)
implementation(projects.feature.setting)
- implementation(projects.feature.navigator)
+ implementation(projects.feature.webview)
+ implementation(platform(libs.firebase.bom))
+ implementation(libs.compose.material)
implementation(libs.firebase.analytics)
implementation(libs.firebase.crashlytics)
implementation(libs.play.services.ads)
+ implementation(libs.kotlin.reflect)
+ implementation(libs.hilt.worker)
+ implementation(libs.androidx.work.runtime)
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 51ec3303..e7abd75a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -11,6 +11,7 @@
+
+ tools:targetApi="33">
-
+
@@ -79,5 +79,15 @@
+
+
+
+
diff --git a/feature/navigator/src/main/java/com/yapp/navigator/MainActivity.kt b/app/src/main/java/com/yapp/orbit/MainActivity.kt
similarity index 74%
rename from feature/navigator/src/main/java/com/yapp/navigator/MainActivity.kt
rename to app/src/main/java/com/yapp/orbit/MainActivity.kt
index 7728e44f..35358445 100644
--- a/feature/navigator/src/main/java/com/yapp/navigator/MainActivity.kt
+++ b/app/src/main/java/com/yapp/orbit/MainActivity.kt
@@ -1,10 +1,9 @@
-package com.yapp.navigator
+package com.yapp.orbit
import android.annotation.SuppressLint
import android.content.pm.ActivityInfo
import android.os.Bundle
import androidx.activity.ComponentActivity
-import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.runtime.CompositionLocalProvider
@@ -25,16 +24,7 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
- enableEdgeToEdge(
- statusBarStyle = SystemBarStyle.light(
- android.graphics.Color.TRANSPARENT,
- android.graphics.Color.TRANSPARENT,
- ),
- navigationBarStyle = SystemBarStyle.light(
- android.graphics.Color.BLACK,
- android.graphics.Color.BLACK,
- ),
- )
+ enableEdgeToEdge()
setContent {
val navigator = rememberOrbitNavigator()
diff --git a/app/src/main/java/com/yapp/orbit/OrbitApplication.kt b/app/src/main/java/com/yapp/orbit/OrbitApplication.kt
index 7391cf6e..b06538f8 100644
--- a/app/src/main/java/com/yapp/orbit/OrbitApplication.kt
+++ b/app/src/main/java/com/yapp/orbit/OrbitApplication.kt
@@ -1,13 +1,24 @@
package com.yapp.orbit
import android.app.Application
+import androidx.hilt.work.HiltWorkerFactory
+import androidx.work.Configuration
import com.google.android.gms.ads.MobileAds
import dagger.hilt.android.HiltAndroidApp
+import javax.inject.Inject
@HiltAndroidApp
-class OrbitApplication : Application() {
+class OrbitApplication() : Application(), Configuration.Provider {
+
+ @Inject lateinit var workerFactory: HiltWorkerFactory
+
override fun onCreate() {
super.onCreate()
MobileAds.initialize(this)
}
+
+ override val workManagerConfiguration: Configuration
+ get() = Configuration.Builder()
+ .setWorkerFactory(workerFactory)
+ .build()
}
diff --git a/feature/navigator/src/main/java/com/yapp/navigator/OrbitNavHost.kt b/app/src/main/java/com/yapp/orbit/OrbitNavHost.kt
similarity index 63%
rename from feature/navigator/src/main/java/com/yapp/navigator/OrbitNavHost.kt
rename to app/src/main/java/com/yapp/orbit/OrbitNavHost.kt
index 082c2ca5..5dd9ce4b 100644
--- a/feature/navigator/src/main/java/com/yapp/navigator/OrbitNavHost.kt
+++ b/app/src/main/java/com/yapp/orbit/OrbitNavHost.kt
@@ -1,4 +1,4 @@
-package com.yapp.navigator
+package com.yapp.orbit
import android.annotation.SuppressLint
import androidx.compose.animation.AnimatedVisibility
@@ -6,6 +6,7 @@ import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
@@ -25,6 +26,9 @@ import com.yapp.mission.missionScreen
import com.yapp.onboarding.onboardingNavGraph
import com.yapp.setting.settingNavGraph
import com.yapp.splash.splashScreen
+import com.yapp.ui.component.bottomsheet.OrbitBottomSheetState
+import com.yapp.ui.component.bottomsheet.rememberOrbitBottomSheetState
+import com.yapp.ui.component.navigation.NavigationBarScrim
import com.yapp.ui.component.snackbar.CustomSnackBarVisuals
import com.yapp.ui.component.snackbar.OrbitSnackBar
import com.yapp.webview.webViewScreen
@@ -34,35 +38,45 @@ import com.yapp.webview.webViewScreen
internal fun OrbitNavHost(
modifier: Modifier = Modifier,
navigator: OrbitNavigator = rememberOrbitNavigator(),
+ bottomSheetState: OrbitBottomSheetState = rememberOrbitBottomSheetState(),
) {
val snackBarHostState = remember { SnackbarHostState() }
- Scaffold(
- modifier = modifier,
- snackbarHost = {
- OrbitSnackBarHost(snackBarHostState = snackBarHostState)
- },
- containerColor = OrbitTheme.colors.gray_900,
- ) {
- NavHost(
- navController = navigator.navController,
- startDestination = navigator.startDestination,
- modifier = Modifier.navigationBarsPadding(),
+ Box {
+ Scaffold(
+ modifier = modifier,
+ snackbarHost = { OrbitSnackBarHost(snackBarHostState) },
+ containerColor = OrbitTheme.colors.gray_900,
) {
- splashScreen(navigator = navigator)
- onboardingNavGraph(navigator = navigator)
- homeNavGraph(
- navigator = navigator,
- snackBarHostState = snackBarHostState,
- )
- missionScreen(navigator = navigator)
- fortuneNavGraph(
+ OrbitNavigationGraph(
navigator = navigator,
+ bottomSheetState = bottomSheetState,
snackBarHostState = snackBarHostState,
)
- settingNavGraph(navigator = navigator)
- webViewScreen(navigator = navigator)
}
+
+ NavigationBarScrim()
+ }
+}
+
+@Composable
+private fun OrbitNavigationGraph(
+ navigator: OrbitNavigator,
+ bottomSheetState: OrbitBottomSheetState,
+ snackBarHostState: SnackbarHostState,
+) {
+ NavHost(
+ modifier = Modifier.navigationBarsPadding(),
+ navController = navigator.navController,
+ startDestination = navigator.startDestination,
+ ) {
+ splashScreen(navigator)
+ onboardingNavGraph(navigator, bottomSheetState)
+ homeNavGraph(navigator, bottomSheetState, snackBarHostState)
+ missionScreen(navigator)
+ fortuneNavGraph(navigator, snackBarHostState)
+ settingNavGraph(navigator)
+ webViewScreen(navigator)
}
}
@@ -73,9 +87,7 @@ private fun OrbitSnackBarHost(
AnimatedVisibility(
visible = snackBarHostState.currentSnackbarData != null,
enter = slideInVertically(initialOffsetY = { it }) + fadeIn(),
- exit = slideOutVertically(
- targetOffsetY = { it },
- ) + fadeOut(),
+ exit = slideOutVertically(targetOffsetY = { it }) + fadeOut(),
) {
SnackbarHost(
hostState = snackBarHostState,
@@ -88,9 +100,9 @@ private fun OrbitSnackBarHost(
end = 20.dp,
bottom = visuals?.bottomPadding ?: 12.dp,
),
- label = visuals?.actionLabel ?: "",
+ label = visuals?.actionLabel.orEmpty(),
iconRes = visuals?.iconRes,
- message = visuals?.message ?: "",
+ message = visuals?.message.orEmpty(),
onAction = { snackBarHostState.currentSnackbarData?.performAction() },
)
},
diff --git a/app/src/main/java/com/yapp/orbit/di/AppVersionModule.kt b/app/src/main/java/com/yapp/orbit/di/AppVersionModule.kt
new file mode 100644
index 00000000..6297ecc3
--- /dev/null
+++ b/app/src/main/java/com/yapp/orbit/di/AppVersionModule.kt
@@ -0,0 +1,18 @@
+package com.yapp.orbit.di
+
+import com.yapp.orbit.BuildConfig
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Named
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object AppVersionModule {
+ @Provides
+ @Singleton
+ @Named("appVersion")
+ fun provideAppVersion(): String = BuildConfig.VERSION_NAME
+}
diff --git a/build-logic/src/main/java/com/yapp/convention/ComposeAndroid.kt b/build-logic/src/main/java/com/yapp/convention/ComposeAndroid.kt
index 64edd3f7..f705f061 100644
--- a/build-logic/src/main/java/com/yapp/convention/ComposeAndroid.kt
+++ b/build-logic/src/main/java/com/yapp/convention/ComposeAndroid.kt
@@ -16,6 +16,8 @@ internal fun Project.configureComposeAndroid() {
val bom = libs.findLibrary("compose.bom").get()
add("implementation", platform(bom))
+ add("implementation", libs.findLibrary("activity.compose").get())
+
add("implementation", libs.findLibrary("compose.material3").get())
add("implementation", libs.findLibrary("compose.ui").get())
add("implementation", libs.findLibrary("compose.ui.tooling.preview").get())
diff --git a/build-logic/src/main/java/com/yapp/convention/HiltAndroid.kt b/build-logic/src/main/java/com/yapp/convention/HiltAndroid.kt
index 6ccba65c..15db09ff 100644
--- a/build-logic/src/main/java/com/yapp/convention/HiltAndroid.kt
+++ b/build-logic/src/main/java/com/yapp/convention/HiltAndroid.kt
@@ -14,6 +14,9 @@ internal fun Project.configureHiltAndroid() {
dependencies {
"implementation"(libs.findLibrary("hilt.android").get())
"ksp"(libs.findLibrary("hilt.android.compiler").get())
+ "ksp"(libs.findLibrary("androidx-hilt-compiler").get())
+ "implementation"(libs.findLibrary("hilt-navigation-compose").get())
+ "implementation"(libs.findLibrary("hilt-worker").get())
}
}
diff --git a/build-logic/src/main/java/com/yapp/convention/TestAndroid.kt b/build-logic/src/main/java/com/yapp/convention/TestAndroid.kt
index 9fe992f6..e4fcee6b 100644
--- a/build-logic/src/main/java/com/yapp/convention/TestAndroid.kt
+++ b/build-logic/src/main/java/com/yapp/convention/TestAndroid.kt
@@ -1,16 +1,33 @@
package com.yapp.convention
import org.gradle.api.Project
+import org.gradle.kotlin.dsl.dependencies
internal fun Project.configureTestAndroid() {
configureJUnitAndroid()
+ // feature ๋ชจ๋์๋ง UI ํ
์คํธ ๊ด๋ จ ์ค์ ์ ์ฉ
+ if (path.startsWith(":feature:")) {
+ configureComposeUiTest()
+ }
+}
+
+internal fun Project.configureComposeUiTest() {
+ val libs = extensions.libs
+ dependencies {
+ "androidTestImplementation"(libs.findLibrary("compose-ui-test-junit4").get())
+ "debugImplementation"(libs.findLibrary("compose-ui-test-manifest").get())
+ }
}
@Suppress("UnstableApiUsage")
internal fun Project.configureJUnitAndroid() {
androidExtension.apply {
- testOptions {
- unitTests.all { it.useJUnitPlatform() }
+ defaultConfig { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" }
+
+ val libs = extensions.libs
+ dependencies {
+ "androidTestImplementation"(libs.findLibrary("androidx-test-ext-junit").get())
+ "androidTestImplementation"(libs.findLibrary("androidx-test-runner").get())
}
}
}
diff --git a/build-logic/src/main/java/com/yapp/convention/TestCoverage.kt b/build-logic/src/main/java/com/yapp/convention/TestCoverage.kt
new file mode 100644
index 00000000..70510798
--- /dev/null
+++ b/build-logic/src/main/java/com/yapp/convention/TestCoverage.kt
@@ -0,0 +1,58 @@
+package com.yapp.convention
+
+import com.android.build.api.dsl.ApplicationExtension
+import com.android.build.api.dsl.LibraryExtension
+import org.gradle.api.Project
+import org.gradle.api.tasks.testing.Test
+import org.gradle.kotlin.dsl.configure
+import org.gradle.kotlin.dsl.withType
+import org.gradle.testing.jacoco.plugins.JacocoPluginExtension
+import org.gradle.testing.jacoco.plugins.JacocoTaskExtension
+import org.gradle.testing.jacoco.tasks.JacocoReport
+
+internal fun Project.configureTestCoverage() {
+ pluginManager.apply("jacoco")
+
+ val libs = extensions.libs
+ extensions.configure {
+ toolVersion = libs.findVersion("jacoco").get().toString()
+ }
+
+ // ๋ชจ๋ ์ ๋ ํ
์คํธ์ Jacoco ์ค์ ์ ์ฉ
+ tasks.withType().configureEach {
+ extensions.configure {
+ isIncludeNoLocationClasses = true
+ excludes = listOf("jdk.internal.*")
+ }
+ }
+
+ // Android ๋ชจ๋์ด๋ฉด ์ปค๋ฒ๋ฆฌ์ง ์ค์ ์ถ๊ฐ
+ extensions.findByType(ApplicationExtension::class.java)?.buildTypes?.configureEach {
+ enableUnitTestCoverage = true
+ }
+
+ extensions.findByType(LibraryExtension::class.java)?.buildTypes?.configureEach {
+ enableUnitTestCoverage = true
+ }
+
+ // ์ปค๋ฒ๋ฆฌ์ง ๋ฆฌํฌํธ Task ๋ฑ๋ก
+ tasks.register("generateTestCoverageReport") {
+ group = "verification"
+ description = "Run unit tests and generate coverage report."
+
+ dependsOn("testDebugUnitTest")
+ dependsOn("createDebugUnitTestCoverageReport")
+ }
+
+ // .exec ํ์ผ ์์ ๊ฒฝ์ฐ createDebugUnitTestCoverageReport task ์คํต
+ tasks.matching { it.name == "createDebugUnitTestCoverageReport" }.configureEach {
+ onlyIf {
+ val execFile = layout.buildDirectory
+ .file("outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec")
+ .get().asFile
+ execFile.exists()
+ }
+
+ (this as? JacocoReport)?.reports?.xml?.required?.set(true)
+ }
+}
diff --git a/build-logic/src/main/java/com/yapp/convention/TestKotlin.kt b/build-logic/src/main/java/com/yapp/convention/TestKotlin.kt
index 3b7d98c7..790833c0 100644
--- a/build-logic/src/main/java/com/yapp/convention/TestKotlin.kt
+++ b/build-logic/src/main/java/com/yapp/convention/TestKotlin.kt
@@ -1,23 +1,16 @@
package com.yapp.convention
import org.gradle.api.Project
-import org.gradle.api.tasks.testing.Test
import org.gradle.kotlin.dsl.dependencies
-import org.gradle.kotlin.dsl.withType
-internal fun Project.configureTest() {
- configureJUnit()
+internal fun Project.configureTestKotlin() {
val libs = extensions.libs
dependencies {
+ // JUnit4 ๋จ์ ํ
์คํธ ํ๋ ์์ํฌ
"testImplementation"(libs.findLibrary("junit4").get())
- "testImplementation"(libs.findLibrary("junit-jupiter").get())
- "testImplementation"(libs.findLibrary("coroutines-test").get())
+ // ์ฝ๋ฃจํด ๊ด๋ จ ํ
์คํธ ๋๊ตฌ (TestCoroutineScope, runTest ๋ฑ..)
+ "testImplementation"(libs.findLibrary("kotlinx-coroutines-test").get())
+ // Kotlin ๊ธฐ๋ฐ mock ๊ฐ์ฒด ์์ฑ, ํ์ ๊ฒ์ฆ
"testImplementation"(libs.findLibrary("mockk").get())
}
}
-
-internal fun Project.configureJUnit() {
- tasks.withType().configureEach {
- useJUnitPlatform()
- }
-}
diff --git a/build-logic/src/main/java/orbit.android.feature.gradle.kts b/build-logic/src/main/java/orbit.android.feature.gradle.kts
index d9568c85..47d5c072 100644
--- a/build-logic/src/main/java/orbit.android.feature.gradle.kts
+++ b/build-logic/src/main/java/orbit.android.feature.gradle.kts
@@ -1,4 +1,3 @@
-import com.yapp.convention.configureHiltAndroid
import com.yapp.convention.libs
plugins {
@@ -6,20 +5,11 @@ plugins {
id("orbit.android.compose")
}
-android {
- defaultConfig {
- testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
- }
-}
-
-configureHiltAndroid()
-
dependencies {
implementation(project(":core:designsystem"))
implementation(project(":core:ui"))
val libs = project.extensions.libs
- implementation(libs.findLibrary("hilt-navigation-compose").get())
implementation(libs.findLibrary("compose-navigation").get())
implementation(libs.findLibrary("lifecycle-viewmodel").get())
implementation(libs.findLibrary("lifecycle-runtime").get())
diff --git a/build-logic/src/main/java/orbit.android.library.gradle.kts b/build-logic/src/main/java/orbit.android.library.gradle.kts
index 3ee18d12..f63f2be4 100644
--- a/build-logic/src/main/java/orbit.android.library.gradle.kts
+++ b/build-logic/src/main/java/orbit.android.library.gradle.kts
@@ -1,6 +1,9 @@
import com.yapp.convention.configureCoroutine
import com.yapp.convention.configureHiltAndroid
import com.yapp.convention.configureKotlinAndroid
+import com.yapp.convention.configureTestAndroid
+import com.yapp.convention.configureTestCoverage
+import com.yapp.convention.configureTestKotlin
plugins {
id("com.android.library")
@@ -9,3 +12,6 @@ plugins {
configureKotlinAndroid()
configureCoroutine()
configureHiltAndroid()
+configureTestAndroid()
+configureTestKotlin()
+configureTestCoverage()
diff --git a/build.gradle.kts b/build.gradle.kts
index 46e42947..3f1d3f7f 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -7,12 +7,12 @@ plugins {
alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.kotlin.serialization) apply false
alias(libs.plugins.ksp) apply false
+ alias(libs.plugins.room) apply false
alias(libs.plugins.hilt) apply false
alias(libs.plugins.compose.compiler) apply false
alias(libs.plugins.google.service) apply false
alias(libs.plugins.firebase.app.distribution) apply false
alias(libs.plugins.firebase.crashlytics) apply false
-// alias(libs.plugins.sentry) apply false
}
apply {
diff --git a/core/alarm/build.gradle.kts b/core/alarm/build.gradle.kts
index 0747052e..c5ab38e0 100644
--- a/core/alarm/build.gradle.kts
+++ b/core/alarm/build.gradle.kts
@@ -11,7 +11,7 @@ android {
dependencies {
implementation(projects.core.analytics)
- implementation(projects.core.datastore)
+ implementation(projects.core.common)
implementation(projects.core.designsystem)
implementation(projects.core.media)
implementation(projects.domain)
diff --git a/core/alarm/src/main/java/com/yapp/alarm/AlarmConstants.kt b/core/alarm/src/main/java/com/yapp/alarm/AlarmConstants.kt
index 67d359f1..3c2541bc 100644
--- a/core/alarm/src/main/java/com/yapp/alarm/AlarmConstants.kt
+++ b/core/alarm/src/main/java/com/yapp/alarm/AlarmConstants.kt
@@ -7,6 +7,8 @@ object AlarmConstants {
const val ACTION_ALARM_INTERACTION_ACTIVITY_CLOSE = "com.yapp.orbit.ACTION_ALERT_INTERACTION_CLOSE"
const val EXTRA_NOTIFICATION_ID = "com.yapp.orbit.EXTRA_NOTIFICATION_ID"
+ const val EXTRA_MISSION_TYPE = "com.yapp.orbit.EXTRA_MISSION_TYPE"
+ const val EXTRA_MISSION_COUNT = "com.yapp.orbit.EXTRA_MISSION_COUNT"
const val EXTRA_ALARM = "com.yapp.orbit.EXTRA_ALARM"
const val EXTRA_ALARM_DAY = "com.yapp.orbit.EXTRA_ALARM_DAY"
@@ -16,8 +18,6 @@ object AlarmConstants {
const val SNOOZE_ID_OFFSET = 10000
- const val WEEK_INTERVAL_MILLIS: Long = 7 * 24 * 60 * 60 * 1000
-
val HOLIDAYS_2025 = setOf(
"2025-01-01", "2025-01-27", "2025-01-28", "2025-01-29", "2025-01-30",
"2025-03-01", "2025-03-03", "2025-05-05", "2025-05-06", "2025-06-06",
diff --git a/core/alarm/src/main/java/com/yapp/alarm/AlarmHelper.kt b/core/alarm/src/main/java/com/yapp/alarm/AlarmHelper.kt
deleted file mode 100644
index 4823278e..00000000
--- a/core/alarm/src/main/java/com/yapp/alarm/AlarmHelper.kt
+++ /dev/null
@@ -1,156 +0,0 @@
-package com.yapp.alarm
-
-import android.app.AlarmManager
-import android.app.Application
-import android.util.Log
-import com.yapp.alarm.pendingIntent.schedule.createAlarmReceiverPendingIntentForSchedule
-import com.yapp.alarm.pendingIntent.schedule.createAlarmReceiverPendingIntentForUnSchedule
-import com.yapp.domain.model.Alarm
-import com.yapp.domain.model.AlarmDay
-import com.yapp.domain.model.toAlarmDays
-import com.yapp.domain.model.toDayOfWeek
-import java.time.Instant
-import java.time.LocalDateTime
-import java.time.ZoneId
-import java.time.format.DateTimeFormatter
-import javax.inject.Inject
-
-class AlarmHelper @Inject constructor(
- private val app: Application,
- private val alarmManager: AlarmManager,
-) {
- fun scheduleAlarm(alarm: Alarm) {
- val selectedDays = alarm.repeatDays.toAlarmDays()
-
- if (selectedDays.isEmpty()) {
- setNonRepeatingAlarm(alarm)
- } else {
- selectedDays.forEach { day ->
- setRepeatingAlarm(day, alarm)
- }
- }
- }
-
- fun scheduleWeeklyAlarm(alarm: Alarm, day: AlarmDay) {
- val initialTriggerMillis = getNextAlarmTimeMillis(alarm, day) + AlarmConstants.WEEK_INTERVAL_MILLIS
- val triggerMillis = findNextNonHolidayDate(initialTriggerMillis)
-
- val pendingIntent = createAlarmReceiverPendingIntentForSchedule(app, alarm, day)
-
- alarmManager.setExactAndAllowWhileIdle(
- AlarmManager.RTC_WAKEUP,
- triggerMillis,
- pendingIntent,
- )
-
- Log.d("AlarmHelper", "Scheduled weekly alarm for $day at: $triggerMillis")
- }
-
- fun unScheduleAlarm(alarm: Alarm) {
- val selectedDays = alarm.repeatDays.toAlarmDays()
-
- if (selectedDays.isEmpty()) {
- val pendingIntent = createAlarmReceiverPendingIntentForUnSchedule(
- app,
- alarm,
- null,
- )
- alarmManager.cancel(pendingIntent)
- } else {
- selectedDays.forEach { day ->
- val pendingIntent = createAlarmReceiverPendingIntentForUnSchedule(
- app,
- alarm,
- day,
- )
- alarmManager.cancel(pendingIntent)
- }
- }
- }
-
- fun cancelSnoozedAlarm(alarmId: Long) {
- val snoozedAlarmId = alarmId + AlarmConstants.SNOOZE_ID_OFFSET
- val pendingIntent = createAlarmReceiverPendingIntentForUnSchedule(app, Alarm(id = snoozedAlarmId))
- alarmManager.cancel(pendingIntent)
- Log.d("AlarmHelper", "Canceled snoozed alarm with id: $snoozedAlarmId")
- }
-
- private fun setRepeatingAlarm(day: AlarmDay, alarm: Alarm) {
- val alarmReceiverPendingIntent =
- createAlarmReceiverPendingIntentForSchedule(app, alarm, day)
- val firstAlarmTriggerMillis = getNextAlarmTimeMillis(alarm, day)
-
- Log.d("AlarmHelper", "Setting repeating alarm id: ${alarm.id} at: $firstAlarmTriggerMillis")
-
- alarmManager.setExactAndAllowWhileIdle(
- AlarmManager.RTC_WAKEUP,
- firstAlarmTriggerMillis,
- alarmReceiverPendingIntent,
- )
- }
-
- private fun setNonRepeatingAlarm(alarm: Alarm) {
- val alarmReceiverPendingIntent =
- createAlarmReceiverPendingIntentForSchedule(app, alarm)
-
- val triggerMillis = getNextAlarmTimeMillis(alarm, null)
-
- Log.d("AlarmHelper", "Setting one-time alarm at: $triggerMillis")
-
- alarmManager.setExactAndAllowWhileIdle(
- AlarmManager.RTC_WAKEUP,
- triggerMillis,
- alarmReceiverPendingIntent,
- )
- }
-
- private fun getNextAlarmTimeMillis(alarm: Alarm, day: AlarmDay?): Long {
- val now = LocalDateTime.now().withNano(0) // ๋ฐ๋ฆฌ์ด ์ ๊ฑฐํ์ฌ ์ ํํ ์ด ๊ธฐ์ค ์ค์
-
- val alarmHour = when {
- alarm.isAm && alarm.hour == 12 -> 0
- !alarm.isAm && alarm.hour != 12 -> alarm.hour + 12
- else -> alarm.hour
- }
-
- var alarmDateTime = now.withHour(alarmHour).withMinute(alarm.minute).withSecond(alarm.second)
-
- if (day != null) {
- val targetDayOfWeek = day.toDayOfWeek()
- while (alarmDateTime.dayOfWeek != targetDayOfWeek || alarmDateTime.isBefore(now)) {
- alarmDateTime = alarmDateTime.plusDays(1)
- }
- } else {
- if (alarmDateTime.isBefore(now)) {
- alarmDateTime = alarmDateTime.plusDays(1)
- }
- }
-
- val epochMillis = alarmDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()
-
- Log.d("AlarmHelper", "Alarm scheduled at: $alarmDateTime (epochMillis=$epochMillis)")
-
- return epochMillis
- }
-
- private fun findNextNonHolidayDate(initialMillis: Long): Long {
- val dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
-
- var adjustedMillis = initialMillis
-
- while (true) {
- val localDate = Instant.ofEpochMilli(adjustedMillis)
- .atZone(ZoneId.systemDefault())
- .toLocalDate()
-
- val dateString = localDate.format(dateFormatter)
-
- if (!AlarmConstants.HOLIDAYS_2025.contains(dateString)) {
- return adjustedMillis // ๊ณตํด์ผ์ด ์๋๋ผ๋ฉด ํด๋น ๋ ์ง ๋ฐํ
- }
-
- // ๊ณตํด์ผ์ด๋ผ๋ฉด ๋ค์ 1์ฃผ ๋ค๋ก ์ด๋
- adjustedMillis += AlarmConstants.WEEK_INTERVAL_MILLIS
- }
- }
-}
diff --git a/core/alarm/src/main/java/com/yapp/alarm/AlarmModule.kt b/core/alarm/src/main/java/com/yapp/alarm/AlarmModule.kt
deleted file mode 100644
index d126a638..00000000
--- a/core/alarm/src/main/java/com/yapp/alarm/AlarmModule.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.yapp.alarm
-
-import android.app.AlarmManager
-import android.content.Context
-import dagger.Module
-import dagger.Provides
-import dagger.hilt.InstallIn
-import dagger.hilt.android.qualifiers.ApplicationContext
-import dagger.hilt.components.SingletonComponent
-import javax.inject.Singleton
-
-@Module
-@InstallIn(SingletonComponent::class)
-object AlarmModule {
-
- @Provides
- @Singleton
- fun provideAlarmManager(@ApplicationContext context: Context): AlarmManager {
- return context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
- }
-}
diff --git a/core/alarm/src/main/java/com/yapp/alarm/AlarmTimeCalculator.kt b/core/alarm/src/main/java/com/yapp/alarm/AlarmTimeCalculator.kt
new file mode 100644
index 00000000..f7d6e590
--- /dev/null
+++ b/core/alarm/src/main/java/com/yapp/alarm/AlarmTimeCalculator.kt
@@ -0,0 +1,98 @@
+package com.yapp.alarm
+
+import com.yapp.domain.model.Alarm
+import com.yapp.domain.model.AlarmDay
+import com.yapp.domain.model.toDayOfWeek
+import java.time.Clock
+import java.time.LocalDateTime
+import java.time.ZoneId
+import java.time.format.DateTimeFormatter
+import javax.inject.Inject
+
+class AlarmTimeCalculator @Inject constructor(
+ private val clock: Clock,
+) {
+ private val holidayDateFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
+
+ private fun isHoliday(dateToCheck: LocalDateTime): Boolean {
+ if (dateToCheck.year == 2025) {
+ val dateString = dateToCheck.format(holidayDateFormatter)
+ return AlarmConstants.HOLIDAYS_2025.contains(dateString)
+ }
+ return false
+ }
+
+ private fun skipHolidaysIfEnabled(initialDateTime: LocalDateTime, alarm: Alarm): LocalDateTime {
+ if (!alarm.isHolidayAlarmOff) return initialDateTime
+
+ var adjustedDateTime = initialDateTime
+ while (isHoliday(adjustedDateTime)) {
+ adjustedDateTime = adjustedDateTime.plusWeeks(1)
+ }
+
+ return adjustedDateTime
+ }
+
+ private fun getAlarmDateTimeOnDate(alarm: Alarm, now: LocalDateTime): LocalDateTime {
+ return now
+ .withHour(alarm.hour)
+ .withMinute(alarm.minute)
+ .withSecond(alarm.second)
+ .withNano(0)
+ }
+
+ fun calculateNextRepeatingTimeMillis(
+ alarm: Alarm,
+ alarmDay: AlarmDay,
+ zoneId: ZoneId = clock.zone,
+ ): Long {
+ val now = LocalDateTime.now(clock)
+ val targetDayOfWeek = alarmDay.toDayOfWeek()
+
+ val alarmDateTimeToday = getAlarmDateTimeOnDate(alarm, now)
+
+ var nextAlarmDateTimeCandidate = alarmDateTimeToday
+
+ while (nextAlarmDateTimeCandidate.dayOfWeek != targetDayOfWeek || nextAlarmDateTimeCandidate.isBefore(now)) {
+ nextAlarmDateTimeCandidate = nextAlarmDateTimeCandidate.plusDays(1)
+ }
+
+ nextAlarmDateTimeCandidate = skipHolidaysIfEnabled(nextAlarmDateTimeCandidate, alarm)
+
+ return nextAlarmDateTimeCandidate.atZone(zoneId).toInstant().toEpochMilli()
+ }
+
+ fun calculateNonRepeatingTimeMillis(
+ alarm: Alarm,
+ zoneId: ZoneId = clock.zone,
+ ): Long {
+ val now = LocalDateTime.now(clock)
+ var alarmDateTime = getAlarmDateTimeOnDate(alarm, now)
+
+ if (alarmDateTime.isBefore(now)) {
+ alarmDateTime = alarmDateTime.plusDays(1)
+ }
+
+ return alarmDateTime.atZone(zoneId).toInstant().toEpochMilli()
+ }
+
+ fun calculateNextWeeklyRescheduledTimeMillis(
+ alarm: Alarm,
+ alarmTargetDay: AlarmDay,
+ zoneId: ZoneId = clock.zone,
+ ): Long {
+ val now = LocalDateTime.now(clock)
+ val targetDayOfWeek = alarmTargetDay.toDayOfWeek()
+
+ var initialAlarmDateTimeCandidate = getAlarmDateTimeOnDate(alarm, now)
+
+ while (initialAlarmDateTimeCandidate.dayOfWeek != targetDayOfWeek || initialAlarmDateTimeCandidate.isBefore(now)) {
+ initialAlarmDateTimeCandidate = initialAlarmDateTimeCandidate.plusDays(1)
+ }
+
+ val nextWeeklyAlarmDateTimeCandidate = initialAlarmDateTimeCandidate.plusWeeks(1)
+ val nextWeeklyAlarmDateTime = skipHolidaysIfEnabled(nextWeeklyAlarmDateTimeCandidate, alarm)
+
+ return nextWeeklyAlarmDateTime.atZone(zoneId).toInstant().toEpochMilli()
+ }
+}
diff --git a/core/alarm/src/main/java/com/yapp/alarm/AndroidAlarmScheduler.kt b/core/alarm/src/main/java/com/yapp/alarm/AndroidAlarmScheduler.kt
new file mode 100644
index 00000000..49c4581a
--- /dev/null
+++ b/core/alarm/src/main/java/com/yapp/alarm/AndroidAlarmScheduler.kt
@@ -0,0 +1,101 @@
+package com.yapp.alarm
+
+import android.app.AlarmManager
+import android.app.Application
+import android.util.Log
+import com.yapp.alarm.pendingIntent.schedule.createAlarmReceiverPendingIntentForSchedule
+import com.yapp.alarm.pendingIntent.schedule.createAlarmReceiverPendingIntentForUnSchedule
+import com.yapp.domain.model.Alarm
+import com.yapp.domain.model.AlarmDay
+import com.yapp.domain.model.toAlarmDays
+import com.yapp.domain.scheduler.AlarmScheduler
+import javax.inject.Inject
+
+class AndroidAlarmScheduler @Inject constructor(
+ private val app: Application,
+ private val alarmManager: AlarmManager,
+ private val alarmTimeCalculator: AlarmTimeCalculator,
+) : AlarmScheduler {
+
+ private fun logSchedule(tag: String, alarm: Alarm, triggerMillis: Long, extra: String = "") {
+ Log.d("ScheduleTrace", "scheduleAlarm Called", Throwable())
+ Log.d(
+ "AlarmSchedule",
+ "[$tag] id=${alarm.id}, repeatDays=${alarm.repeatDays}, " +
+ "time=${java.time.Instant.ofEpochMilli(triggerMillis)} $extra",
+ )
+ }
+
+ override fun scheduleAlarm(alarm: Alarm) {
+ val selectedDays = alarm.repeatDays.toAlarmDays()
+
+ if (selectedDays.isEmpty()) {
+ setNonRepeatingAlarm(alarm)
+ } else {
+ selectedDays.forEach { day ->
+ setRepeatingAlarm(day, alarm)
+ }
+ }
+ }
+
+ private fun setRepeatingAlarm(day: AlarmDay, alarm: Alarm) {
+ val triggerMillis = alarmTimeCalculator.calculateNextRepeatingTimeMillis(alarm, day)
+ val pendingIntent = createAlarmReceiverPendingIntentForSchedule(app, alarm, day)
+ logSchedule("REPEAT", alarm, triggerMillis, "day=$day")
+ alarmManager.setExactAndAllowWhileIdle(
+ AlarmManager.RTC_WAKEUP,
+ triggerMillis,
+ pendingIntent,
+ )
+ }
+
+ private fun setNonRepeatingAlarm(alarm: Alarm) {
+ val triggerMillis = alarmTimeCalculator.calculateNonRepeatingTimeMillis(alarm)
+ val pendingIntent = createAlarmReceiverPendingIntentForSchedule(app, alarm)
+ logSchedule("NON_REPEAT", alarm, triggerMillis)
+ alarmManager.setExactAndAllowWhileIdle(
+ AlarmManager.RTC_WAKEUP,
+ triggerMillis,
+ pendingIntent,
+ )
+ }
+
+ fun rescheduleUpcomingWeeklyAlarm(alarm: Alarm, day: AlarmDay) {
+ val triggerMillis = alarmTimeCalculator.calculateNextWeeklyRescheduledTimeMillis(alarm, day)
+ val pendingIntent = createAlarmReceiverPendingIntentForSchedule(app, alarm, day)
+ logSchedule("RESCHEDULE_WEEKLY", alarm, triggerMillis, "day=$day")
+ alarmManager.setExactAndAllowWhileIdle(
+ AlarmManager.RTC_WAKEUP,
+ triggerMillis,
+ pendingIntent,
+ )
+ }
+
+ override fun unScheduleAlarm(alarm: Alarm) {
+ val selectedDays = alarm.repeatDays.toAlarmDays()
+
+ if (selectedDays.isEmpty()) {
+ val pendingIntent = createAlarmReceiverPendingIntentForUnSchedule(
+ app,
+ alarm,
+ null,
+ )
+ alarmManager.cancel(pendingIntent)
+ } else {
+ selectedDays.forEach { day ->
+ val pendingIntent = createAlarmReceiverPendingIntentForUnSchedule(
+ app,
+ alarm,
+ day,
+ )
+ alarmManager.cancel(pendingIntent)
+ }
+ }
+ }
+
+ fun cancelSnoozedAlarm(alarmId: Long) {
+ val snoozedAlarmId = alarmId + AlarmConstants.SNOOZE_ID_OFFSET
+ val pendingIntent = createAlarmReceiverPendingIntentForUnSchedule(app, Alarm(id = snoozedAlarmId))
+ alarmManager.cancel(pendingIntent)
+ }
+}
diff --git a/core/alarm/src/main/java/com/yapp/alarm/di/AlarmModule.kt b/core/alarm/src/main/java/com/yapp/alarm/di/AlarmModule.kt
new file mode 100644
index 00000000..d5b5d624
--- /dev/null
+++ b/core/alarm/src/main/java/com/yapp/alarm/di/AlarmModule.kt
@@ -0,0 +1,39 @@
+package com.yapp.alarm.di
+
+import android.app.AlarmManager
+import android.content.Context
+import com.yapp.alarm.AlarmTimeCalculator
+import com.yapp.alarm.AndroidAlarmScheduler
+import com.yapp.domain.scheduler.AlarmScheduler
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import java.time.Clock
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+abstract class AlarmModule {
+ @Binds
+ @Singleton
+ abstract fun bindsAlarmScheduler(
+ alarmScheduler: AndroidAlarmScheduler,
+ ): AlarmScheduler
+
+ companion object {
+ @Provides
+ @Singleton
+ fun provideAlarmTimeCalculator(clock: Clock): AlarmTimeCalculator {
+ return AlarmTimeCalculator(clock)
+ }
+
+ @Provides
+ @Singleton
+ fun provideAlarmManager(@ApplicationContext context: Context): AlarmManager {
+ return context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
+ }
+ }
+}
diff --git a/core/alarm/src/main/java/com/yapp/alarm/pendingIntent/interaction/AlarmDismissPendingIntent.kt b/core/alarm/src/main/java/com/yapp/alarm/pendingIntent/interaction/AlarmDismissPendingIntent.kt
index 3bd687df..3d702fb1 100644
--- a/core/alarm/src/main/java/com/yapp/alarm/pendingIntent/interaction/AlarmDismissPendingIntent.kt
+++ b/core/alarm/src/main/java/com/yapp/alarm/pendingIntent/interaction/AlarmDismissPendingIntent.kt
@@ -12,8 +12,10 @@ import com.yapp.alarm.receivers.AlarmReceiver
fun createAlarmDismissPendingIntent(
applicationContext: Context,
pendingIntentId: Long,
+ missionType: Int,
+ missionCount: Int,
): PendingIntent {
- val alarmDismissIntent = createAlarmDismissIntent(applicationContext, pendingIntentId)
+ val alarmDismissIntent = createAlarmDismissIntent(applicationContext, pendingIntentId, missionType, missionCount)
return PendingIntent.getBroadcast(
applicationContext,
pendingIntentId.toInt(),
@@ -25,18 +27,29 @@ fun createAlarmDismissPendingIntent(
fun createAlarmDismissIntent(
context: Context,
notificationId: Long,
+ missionType: Int,
+ missionCount: Int,
): Intent {
return Intent(AlarmConstants.ACTION_ALARM_DISMISSED).apply {
setClass(context, AlarmReceiver::class.java)
putExtra(AlarmConstants.EXTRA_NOTIFICATION_ID, notificationId)
+ putExtra(AlarmConstants.EXTRA_MISSION_TYPE, missionType)
+ putExtra(AlarmConstants.EXTRA_MISSION_COUNT, missionCount)
}
}
fun createNavigateToMissionPendingIntent(
applicationContext: Context,
notificationId: Long,
+ missionType: Int,
+ missionCount: Int,
): PendingIntent {
- val navigateToMissionIntent = createNavigateToMissionIntent(applicationContext, notificationId)
+ val navigateToMissionIntent = createNavigateToMissionIntent(
+ context = applicationContext,
+ notificationId = notificationId,
+ missionType = missionType,
+ missionCount = missionCount,
+ )
return PendingIntent.getActivity(
applicationContext,
notificationId.toInt(),
@@ -48,8 +61,11 @@ fun createNavigateToMissionPendingIntent(
fun createNavigateToMissionIntent(
context: Context,
notificationId: Long,
+ missionType: Int,
+ missionCount: Int,
): Intent {
- return Intent(Intent.ACTION_VIEW, "orbitapp://mission?notificationId=$notificationId".toUri()).apply {
+ val uriString = "orbitapp://mission?notificationId=$notificationId&missionType=$missionType&missionCount=$missionCount"
+ return Intent(Intent.ACTION_VIEW, uriString.toUri()).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
setPackage(context.packageName)
}
diff --git a/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmInteractionActivityReceiver.kt b/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmInteractionActivityReceiver.kt
index 4b703bf9..029072a4 100644
--- a/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmInteractionActivityReceiver.kt
+++ b/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmInteractionActivityReceiver.kt
@@ -6,21 +6,22 @@ import android.content.Intent
import androidx.activity.ComponentActivity
import androidx.core.net.toUri
import com.yapp.alarm.AlarmConstants
-import com.yapp.datastore.UserPreferences
+import com.yapp.domain.model.FortuneCreateStatus
+import com.yapp.domain.model.MissionType
+import com.yapp.domain.repository.FortuneRepository
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
-import java.time.LocalDate
-import java.time.format.DateTimeFormatter
+import kotlinx.coroutines.withContext
import javax.inject.Inject
@AndroidEntryPoint
class AlarmInteractionActivityReceiver(private val activity: ComponentActivity) : BroadcastReceiver() {
@Inject
- lateinit var userPreferences: UserPreferences
+ lateinit var fortuneRepository: FortuneRepository
override fun onReceive(context: Context?, intent: Intent?) {
val isSnoozed = intent?.getBooleanExtra(AlarmConstants.EXTRA_IS_SNOOZED, false) ?: false
@@ -29,19 +30,70 @@ class AlarmInteractionActivityReceiver(private val activity: ComponentActivity)
activity.finish()
if (!isSnoozed) {
- CoroutineScope(Dispatchers.IO).launch {
- val fortuneDate = userPreferences.fortuneDateFlow.firstOrNull()
- val todayDate = LocalDate.now().format(DateTimeFormatter.ISO_DATE)
-
- if (fortuneDate != todayDate) {
- context?.let {
- val missionIntent =
- Intent(Intent.ACTION_VIEW, "orbitapp://mission".toUri()).apply {
- addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- setPackage(context.packageName)
+ val notificationId = intent.getLongExtra(AlarmConstants.EXTRA_NOTIFICATION_ID, -1L)
+ val missionTypeRaw = intent.getIntExtra(AlarmConstants.EXTRA_MISSION_TYPE, -1)
+ val missionCount = intent.getIntExtra(AlarmConstants.EXTRA_MISSION_COUNT, -1)
+
+ val missionType = MissionType.fromInt(missionTypeRaw)
+
+ val hasValidMissionData = (
+ notificationId != -1L &&
+ missionType != MissionType.NONE &&
+ missionCount != -1
+ )
+
+ val pending = goAsync()
+ CoroutineScope(Dispatchers.Main).launch {
+ try {
+ if (!hasValidMissionData) {
+ val (fortuneCreateStatus, hasUnseenFortune) = withContext(Dispatchers.IO) {
+ val status = fortuneRepository.fortuneCreateStatusFlow.first()
+ val unseen = fortuneRepository.hasUnseenFortuneFlow.first()
+ status to unseen
+ }
+
+ when (fortuneCreateStatus) {
+ is FortuneCreateStatus.Creating -> {
+ context?.let { ctx ->
+ val uri = "orbitapp://fortune".toUri()
+ val fortuneIntent = Intent(Intent.ACTION_VIEW, uri).apply {
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ setPackage(ctx.packageName)
+ }
+ ctx.startActivity(fortuneIntent)
+ }
}
- it.startActivity(missionIntent)
+
+ is FortuneCreateStatus.Success -> {
+ if (hasUnseenFortune) {
+ context?.let { ctx ->
+ val uri = "orbitapp://fortune".toUri()
+ val fortuneIntent =
+ Intent(Intent.ACTION_VIEW, uri).apply {
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ setPackage(ctx.packageName)
+ }
+ ctx.startActivity(fortuneIntent)
+ }
+ }
+ }
+
+ FortuneCreateStatus.Failure, FortuneCreateStatus.Idle -> { }
+ }
+ } else {
+ context?.let { ctx ->
+ val uriString =
+ "orbitapp://mission?notificationId=$notificationId&missionType=${missionType.value}&missionCount=$missionCount"
+ val missionIntent =
+ Intent(Intent.ACTION_VIEW, uriString.toUri()).apply {
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ setPackage(ctx.packageName)
+ }
+ ctx.startActivity(missionIntent)
+ }
}
+ } finally {
+ pending.finish()
}
}
}
diff --git a/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt b/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt
index 2e97ee68..011dadef 100644
--- a/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt
+++ b/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt
@@ -7,20 +7,21 @@ import android.os.Build
import android.util.Log
import android.widget.Toast
import com.yapp.alarm.AlarmConstants
-import com.yapp.alarm.AlarmHelper
+import com.yapp.alarm.AndroidAlarmScheduler
import com.yapp.alarm.services.AlarmService
import com.yapp.analytics.AnalyticsEvent
import com.yapp.analytics.AnalyticsHelper
-import com.yapp.datastore.UserPreferences
import com.yapp.domain.model.Alarm
+import com.yapp.domain.model.toAlarmDay
import com.yapp.domain.model.toTimeString
+import com.yapp.domain.repository.FortuneRepository
import com.yapp.domain.usecase.AlarmUseCase
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
+import java.time.LocalDate
import java.time.LocalDateTime
import javax.inject.Inject
@@ -31,10 +32,10 @@ class AlarmReceiver : BroadcastReceiver() {
lateinit var analyticsHelper: AnalyticsHelper
@Inject
- lateinit var alarmHelper: AlarmHelper
+ lateinit var androidAlarmScheduler: AndroidAlarmScheduler
@Inject
- lateinit var userPreferences: UserPreferences
+ lateinit var fortuneRepository: FortuneRepository
@Inject
lateinit var alarmUseCase: AlarmUseCase
@@ -46,10 +47,11 @@ class AlarmReceiver : BroadcastReceiver() {
val alarmServiceIntent = createAlarmServiceIntent(context, intent)
when (intent.action) {
AlarmConstants.ACTION_ALARM_TRIGGERED -> {
- Log.d("AlarmReceiver", "Alarm Triggered")
-
val alarm: Alarm? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- alarmServiceIntent.getParcelableExtra(AlarmConstants.EXTRA_ALARM, Alarm::class.java)
+ alarmServiceIntent.getParcelableExtra(
+ AlarmConstants.EXTRA_ALARM,
+ Alarm::class.java,
+ )
} else {
@Suppress("DEPRECATION")
alarmServiceIntent.getParcelableExtra(AlarmConstants.EXTRA_ALARM)
@@ -68,8 +70,6 @@ class AlarmReceiver : BroadcastReceiver() {
}
AlarmConstants.ACTION_ALARM_SNOOZED -> {
- Log.d("AlarmReceiver", "Alarm Snoozed")
-
val alarm: Alarm? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(AlarmConstants.EXTRA_ALARM, Alarm::class.java)
} else {
@@ -86,44 +86,68 @@ class AlarmReceiver : BroadcastReceiver() {
)
alarm?.let { handleSnooze(context, it) }
- Toast.makeText(context, "์๋์ด ${alarm?.snoozeInterval}๋ถ ํ ๋ค์ ์ธ๋ ค์", Toast.LENGTH_SHORT).show()
+ Toast.makeText(
+ context,
+ "์๋์ด ${alarm?.snoozeInterval}๋ถ ํ ๋ค์ ์ธ๋ ค์",
+ Toast.LENGTH_SHORT,
+ ).show()
}
AlarmConstants.ACTION_ALARM_DISMISSED -> {
- Log.d("AlarmReceiver", "Alarm Dismissed")
-
- val alarmId = intent.getLongExtra(AlarmConstants.EXTRA_NOTIFICATION_ID, -1L)
- if (alarmId != -1L) {
- CoroutineScope(Dispatchers.IO).launch {
- val alarms = alarmUseCase.getAllAlarms().first().sortedBy { it.isAlarmActive }
- val isFirstAlarm = alarms.firstOrNull()?.id == alarmId
-
- analyticsHelper.logEvent(
- AnalyticsEvent(
- type = "alarm_dismiss",
- properties = mapOf(
- AnalyticsEvent.AlarmPropertiesKeys.ALARM_ID to "$alarmId",
- AnalyticsEvent.AlarmPropertiesKeys.DISMISS_IS_FIRST_ALARM to isFirstAlarm,
- ),
- ),
- )
- val existingId = userPreferences.firstDismissedAlarmIdFlow.firstOrNull()
- if (existingId == null) {
- // ์ฒซ ๋ฒ์งธ ์๋ ํด์ ๊ธฐ๋ก
- userPreferences.saveFirstDismissedAlarmId(alarmId)
- } else if (existingId != alarmId) {
- // ๋ ๋ฒ์งธ ์๋ ํด์ ๊ฐ์ง - ๊ธฐ์กด ๊ธฐ๋ก ์ญ์
- userPreferences.clearDismissedAlarmId()
- }
- }
+ val notificationId = intent.getLongExtra(AlarmConstants.EXTRA_NOTIFICATION_ID, -1L)
+ val missionType = intent.getIntExtra(AlarmConstants.EXTRA_MISSION_TYPE, -1)
+ val missionCount = intent.getIntExtra(AlarmConstants.EXTRA_MISSION_COUNT, -1)
- alarmHelper.cancelSnoozedAlarm(alarmId)
- } else {
- Log.e("AlarmReceiver", "์๋ ID ์์ ์คํจ")
+ if (notificationId == -1L) {
+ Log.e("AlarmReceiver", "notificationId ์์ ์คํจ")
+ return
}
- alarmHelper.cancelSnoozedAlarm(alarmId)
+
+ androidAlarmScheduler.cancelSnoozedAlarm(notificationId)
context.stopService(alarmServiceIntent)
- sendBroadCastToCloseAlarmInteractionActivity(context)
+
+ CoroutineScope(Dispatchers.IO).launch {
+ val alarms = alarmUseCase.getAllAlarms().first()
+
+ val isSnoozeId = notificationId >= AlarmConstants.SNOOZE_ID_OFFSET
+
+ fun Alarm.ringsToday(): Boolean {
+ if (repeatDays == 0) return true
+
+ val todayAlarmDay = LocalDate.now().dayOfWeek.toAlarmDay()
+ return (repeatDays and todayAlarmDay.bitValue) != 0
+ }
+
+ val earliestIdToday: Long? = alarms
+ .asSequence()
+ .filter { (it.isAlarmActive || it.id == notificationId) && it.ringsToday() }
+ .sortedWith(compareBy({ it.hour }, { it.minute }, { it.second }))
+ .firstOrNull()
+ ?.id
+
+ val isEarliestAlarmDismissedToday =
+ !isSnoozeId && (earliestIdToday == notificationId)
+
+ if (isEarliestAlarmDismissedToday) fortuneRepository.markFirstAlarmDismissedToday()
+
+ val isFirstAlarm = earliestIdToday == notificationId
+ analyticsHelper.logEvent(
+ AnalyticsEvent(
+ type = "alarm_dismiss",
+ properties = mapOf(
+ AnalyticsEvent.AlarmPropertiesKeys.ALARM_ID to "$notificationId",
+ AnalyticsEvent.AlarmPropertiesKeys.DISMISS_IS_FIRST_ALARM to isFirstAlarm,
+ ),
+ ),
+ )
+
+ sendBroadCastToCloseAlarmInteractionActivity(
+ context = context,
+ notificationId = notificationId,
+ missionType = missionType,
+ missionCount = missionCount,
+ )
+ }
Toast.makeText(context, "์๋์ด ํด์ ๋์์ด์", Toast.LENGTH_SHORT).show()
}
@@ -152,8 +176,7 @@ class AlarmReceiver : BroadcastReceiver() {
.plusMinutes(alarm.snoozeInterval.toLong())
val updatedAlarm = alarm.copy(
- isAm = snoozeDateTime.hour < 12,
- hour = if (snoozeDateTime.hour == 0) 12 else if (snoozeDateTime.hour > 12) snoozeDateTime.hour - 12 else snoozeDateTime.hour,
+ hour = snoozeDateTime.hour,
minute = snoozeDateTime.minute,
second = snoozeDateTime.second,
repeatDays = 0,
@@ -161,21 +184,22 @@ class AlarmReceiver : BroadcastReceiver() {
id = alarm.id + AlarmConstants.SNOOZE_ID_OFFSET,
)
- Log.d(
- "AlarmReceiver",
- "Scheduling snooze alarm: alarmId=${updatedAlarm.id}, newTime=${updatedAlarm.hour}:${updatedAlarm.minute}, remaining snoozeCount=$newSnoozeCount",
- )
-
context.stopService(Intent(context, AlarmService::class.java))
- alarmHelper.scheduleAlarm(updatedAlarm)
+ androidAlarmScheduler.scheduleAlarm(updatedAlarm)
}
- private fun sendBroadCastToCloseAlarmInteractionActivity(context: Context) {
- Log.d("AlarmReceiver", "Send Broadcast to close Alarm Interaction Activity")
- val alarmAlertActivityCloseIntent =
- Intent(AlarmConstants.ACTION_ALARM_INTERACTION_ACTIVITY_CLOSE).apply {
- putExtra(AlarmConstants.EXTRA_IS_SNOOZED, false)
- }
- context.sendBroadcast(alarmAlertActivityCloseIntent)
+ private fun sendBroadCastToCloseAlarmInteractionActivity(
+ context: Context,
+ notificationId: Long,
+ missionType: Int,
+ missionCount: Int,
+ ) {
+ val intent = Intent(AlarmConstants.ACTION_ALARM_INTERACTION_ACTIVITY_CLOSE).apply {
+ putExtra(AlarmConstants.EXTRA_IS_SNOOZED, false)
+ putExtra(AlarmConstants.EXTRA_NOTIFICATION_ID, notificationId)
+ putExtra(AlarmConstants.EXTRA_MISSION_TYPE, missionType)
+ putExtra(AlarmConstants.EXTRA_MISSION_COUNT, missionCount)
+ }
+ context.sendBroadcast(intent)
}
}
diff --git a/core/alarm/src/main/java/com/yapp/alarm/receivers/RescheduleAlarmReceiver.kt b/core/alarm/src/main/java/com/yapp/alarm/receivers/RescheduleAlarmReceiver.kt
index b26ce622..b42e9fab 100644
--- a/core/alarm/src/main/java/com/yapp/alarm/receivers/RescheduleAlarmReceiver.kt
+++ b/core/alarm/src/main/java/com/yapp/alarm/receivers/RescheduleAlarmReceiver.kt
@@ -3,11 +3,12 @@ package com.yapp.alarm.receivers
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
-import com.yapp.alarm.AlarmHelper
+import com.yapp.alarm.AndroidAlarmScheduler
import com.yapp.domain.usecase.AlarmUseCase
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import javax.inject.Inject
@@ -18,7 +19,7 @@ class RescheduleAlarmReceiver : BroadcastReceiver() {
lateinit var alarmUseCase: AlarmUseCase
@Inject
- lateinit var alarmHelper: AlarmHelper
+ lateinit var androidAlarmScheduler: AndroidAlarmScheduler
override fun onReceive(context: Context?, intent: Intent?) {
context ?: return
@@ -31,11 +32,10 @@ class RescheduleAlarmReceiver : BroadcastReceiver() {
private fun rescheduleAlarm() {
CoroutineScope(Dispatchers.IO).launch {
- alarmUseCase.getAllAlarms().collect { alarms ->
- alarms.forEach { alarm ->
- alarmHelper.scheduleAlarm(alarm)
- }
- }
+ val alarms = alarmUseCase.getAllAlarms().first()
+ alarms
+ .filter { it.isAlarmActive }
+ .forEach { alarm -> androidAlarmScheduler.scheduleAlarm(alarm) }
}
}
}
diff --git a/core/alarm/src/main/java/com/yapp/alarm/scheduler/PostFortuneTaskScheduler.kt b/core/alarm/src/main/java/com/yapp/alarm/scheduler/PostFortuneTaskScheduler.kt
new file mode 100644
index 00000000..e16e8dd4
--- /dev/null
+++ b/core/alarm/src/main/java/com/yapp/alarm/scheduler/PostFortuneTaskScheduler.kt
@@ -0,0 +1,5 @@
+package com.yapp.alarm.scheduler
+
+interface PostFortuneTaskScheduler {
+ fun enqueueOnceForToday()
+}
diff --git a/core/alarm/src/main/java/com/yapp/alarm/services/AlarmService.kt b/core/alarm/src/main/java/com/yapp/alarm/services/AlarmService.kt
index c982fe7e..56141679 100644
--- a/core/alarm/src/main/java/com/yapp/alarm/services/AlarmService.kt
+++ b/core/alarm/src/main/java/com/yapp/alarm/services/AlarmService.kt
@@ -18,14 +18,15 @@ import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.net.toUri
import com.yapp.alarm.AlarmConstants
-import com.yapp.alarm.AlarmHelper
+import com.yapp.alarm.AndroidAlarmScheduler
import com.yapp.alarm.pendingIntent.interaction.createAlarmAlertPendingIntent
import com.yapp.alarm.pendingIntent.interaction.createAlarmDismissPendingIntent
import com.yapp.alarm.pendingIntent.interaction.createAlarmSnoozePendingIntent
import com.yapp.alarm.pendingIntent.interaction.createNavigateToMissionPendingIntent
-import com.yapp.datastore.UserPreferences
+import com.yapp.alarm.scheduler.PostFortuneTaskScheduler
import com.yapp.domain.model.Alarm
import com.yapp.domain.model.AlarmDay
+import com.yapp.domain.model.MissionType
import com.yapp.domain.usecase.AlarmUseCase
import com.yapp.media.sound.SoundPlayer
import dagger.hilt.android.AndroidEntryPoint
@@ -33,10 +34,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
-import java.time.LocalDate
-import java.time.format.DateTimeFormatter
import javax.inject.Inject
@AndroidEntryPoint
@@ -51,10 +49,10 @@ class AlarmService : Service() {
private lateinit var vibrator: Vibrator
@Inject
- lateinit var alarmHelper: AlarmHelper
+ lateinit var androidAlarmScheduler: AndroidAlarmScheduler
@Inject
- lateinit var userPreferences: UserPreferences
+ lateinit var postFortuneTaskScheduler: PostFortuneTaskScheduler
private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
@@ -81,7 +79,7 @@ class AlarmService : Service() {
super.onDestroy()
}
- private suspend fun handleIntent(intent: Intent) {
+ private fun handleIntent(intent: Intent) {
val alarm: Alarm? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(AlarmConstants.EXTRA_ALARM, Alarm::class.java)
} else {
@@ -103,7 +101,7 @@ class AlarmService : Service() {
// ๋ฐ๋ณต ์์ผ ์๋ ์, ๋ค์ ์ฃผ ๋์ผ ์์ผ ์๋ ์์ฝ
if (!isOneTimeAlarm) {
intent.getStringExtra(AlarmConstants.EXTRA_ALARM_DAY)?.let {
- alarmHelper.scheduleWeeklyAlarm(alarm, AlarmDay.valueOf(it))
+ androidAlarmScheduler.rescheduleUpcomingWeeklyAlarm(alarm, AlarmDay.valueOf(it))
}
}
@@ -113,7 +111,7 @@ class AlarmService : Service() {
false -> {
startForeground(
notificationId.toInt(),
- createNotification(alarm, shouldNavigateToMission()),
+ createNotification(alarm, shouldNavigateToMission(alarm.missionType)),
)
if (alarm.isVibrationEnabled) startVibration()
if (alarm.isSoundEnabled) startSound(alarm.soundUri, alarm.soundVolume)
@@ -123,12 +121,14 @@ class AlarmService : Service() {
if (isOneTimeAlarm) {
turnOffAlarm(alarmId = notificationId)
}
+
+ postFortuneTaskScheduler.enqueueOnceForToday()
}
- private suspend fun shouldNavigateToMission(): Boolean {
- val fortuneDate = userPreferences.fortuneDateFlow.firstOrNull()
- val todayDate = LocalDate.now().format(DateTimeFormatter.ISO_DATE)
- return fortuneDate != todayDate
+ private fun shouldNavigateToMission(
+ missionType: MissionType,
+ ): Boolean {
+ return missionType != MissionType.NONE
}
private fun createNotification(alarm: Alarm, shouldNavigateToMission: Boolean): Notification {
@@ -139,11 +139,15 @@ class AlarmService : Service() {
createNavigateToMissionPendingIntent(
applicationContext = applicationContext,
notificationId = alarm.id,
+ missionType = alarm.missionType.value,
+ missionCount = alarm.missionCount,
)
} else {
createAlarmDismissPendingIntent(
applicationContext = applicationContext,
pendingIntentId = alarm.id,
+ missionType = alarm.missionType.value,
+ missionCount = alarm.missionCount,
)
}
diff --git a/core/alarm/src/test/kotlin/com/yapp/alarm/AlarmTimeCalculatorTest.kt b/core/alarm/src/test/kotlin/com/yapp/alarm/AlarmTimeCalculatorTest.kt
new file mode 100644
index 00000000..a4055be2
--- /dev/null
+++ b/core/alarm/src/test/kotlin/com/yapp/alarm/AlarmTimeCalculatorTest.kt
@@ -0,0 +1,468 @@
+import com.yapp.alarm.AlarmTimeCalculator
+import com.yapp.domain.model.Alarm
+import com.yapp.domain.model.AlarmDay
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import java.time.Clock
+import java.time.LocalDateTime
+import java.time.LocalTime
+import java.time.ZoneId
+
+class AlarmTimeCalculatorTest {
+
+ private val testZoneId: ZoneId = ZoneId.of("Asia/Seoul")
+
+ // --- ๊ธฐ์ค ์๊ฐ (Fixed Clocks) ---
+ private val MONDAY_2024_07_22_10AM: LocalDateTime = LocalDateTime.of(2024, 7, 22, 10, 0, 0)
+ private val clockMonday2024_10am: Clock = Clock.fixed(
+ MONDAY_2024_07_22_10AM.toInstant(
+ testZoneId.rules.getOffset(MONDAY_2024_07_22_10AM)
+ ), testZoneId
+ )
+
+ private val FRIDAY_2024_07_26_3PM: LocalDateTime = LocalDateTime.of(2024, 7, 26, 15, 0, 0)
+ private val clockFriday2024_3pm: Clock = Clock.fixed(
+ FRIDAY_2024_07_26_3PM.toInstant(testZoneId.rules.getOffset(FRIDAY_2024_07_26_3PM)),
+ testZoneId
+ )
+
+ private val MONDAY_2025_01_20_10AM: LocalDateTime = LocalDateTime.of(2025, 1, 20, 10, 0, 0)
+ private val clockMonday2025_01_20_10am: Clock = Clock.fixed(
+ MONDAY_2025_01_20_10AM.toInstant(
+ testZoneId.rules.getOffset(MONDAY_2025_01_20_10AM)
+ ), testZoneId
+ )
+
+ private val MONDAY_2025_01_20_2_01PM: LocalDateTime = LocalDateTime.of(2025, 1, 20, 14, 1, 0)
+ private val clockMonday2025_PrevHoliday_2_01pm: Clock = Clock.fixed(
+ MONDAY_2025_01_20_2_01PM.toInstant(
+ testZoneId.rules.getOffset(MONDAY_2025_01_20_2_01PM)
+ ), testZoneId
+ )
+
+ private val MONDAY_HOLIDAY_2025_01_27_10AM: LocalDateTime =
+ LocalDateTime.of(2025, 1, 27, 10, 0, 0)
+ private val clockMondayHoliday2025_10am: Clock = Clock.fixed(
+ MONDAY_HOLIDAY_2025_01_27_10AM.toInstant(
+ testZoneId.rules.getOffset(MONDAY_HOLIDAY_2025_01_27_10AM)
+ ), testZoneId
+ )
+
+ private val MONDAY_2025_02_17_10AM: LocalDateTime = LocalDateTime.of(2025, 2, 17, 10, 0, 0)
+ private val clockMonday2025_02_17_10am: Clock = Clock.fixed(
+ MONDAY_2025_02_17_10AM.toInstant(
+ testZoneId.rules.getOffset(MONDAY_2025_02_17_10AM)
+ ), testZoneId
+ )
+
+ private fun createTestAlarm(
+ hour: Int,
+ minute: Int,
+ second: Int = 0,
+ isHolidayAlarmOff: Boolean = false,
+ repeatDays: Int = 0, // ๊ธฐ๋ณธ๊ฐ์ ๋น๋ฐ๋ณต
+ ): Alarm {
+ return Alarm(
+ hour = hour,
+ minute = minute,
+ second = second,
+ repeatDays = repeatDays,
+ isHolidayAlarmOff = isHolidayAlarmOff,
+ )
+ }
+
+ private fun getExpectedMillis(dateTime: LocalDateTime, zone: ZoneId = testZoneId): Long {
+ return dateTime.atZone(zone).toInstant().toEpochMilli()
+ }
+
+ // --- ๋น๋ฐ๋ณต ์๋ ์๊ฐ ๊ณ์ฐ (calculateNonRepeatingTimeMillis) ํ
์คํธ ---
+ @Test
+ fun `๋น๋ฐ๋ณต_์๋์๊ฐ์ด_์ค๋_๋ฏธ๋์ด๋ฉด_์ค๋_์๋์๊ฐ์ผ๋ก_๊ณ์ฐ๋๋ค`() {
+ // ํ์ฌ: 2024-07-22 (์) 10:00:00
+ // ์๋: ์ค๋ 14:00:00, ๋น๋ฐ๋ณต
+ // ๊ธฐ๋: 2024-07-22 (์) 14:00:00
+
+ // given
+ val calculator = AlarmTimeCalculator(clockMonday2024_10am)
+ val alarmTime = LocalTime.of(14, 0)
+ val alarm = createTestAlarm(alarmTime.hour, alarmTime.minute) // repeatDays = 0 (๋น๋ฐ๋ณต)
+
+ // when
+ val actualMillis = calculator.calculateNonRepeatingTimeMillis(alarm, testZoneId)
+
+ // then
+ val expectedDateTime = MONDAY_2024_07_22_10AM.with(alarmTime)
+ val expectedMillis = getExpectedMillis(expectedDateTime)
+ assertEquals(expectedMillis, actualMillis)
+ }
+
+ @Test
+ fun `๋น๋ฐ๋ณต_์๋์๊ฐ์ด_์ค๋_๊ณผ๊ฑฐ์ด๋ฉด_๋ด์ผ_์๋์๊ฐ์ผ๋ก_๊ณ์ฐ๋๋ค`() {
+ // ํ์ฌ: 2024-07-22 (์) 10:00:00
+ // ์๋: ์ค๋ 08:00:00 (์ด๋ฏธ ์ง๋จ), ๋น๋ฐ๋ณต
+ // ๊ธฐ๋: 2024-07-23 (ํ) 08:00:00
+
+ // given
+ val calculator = AlarmTimeCalculator(clockMonday2024_10am)
+ val alarmTime = LocalTime.of(8, 0)
+ val alarm = createTestAlarm(alarmTime.hour, alarmTime.minute) // repeatDays = 0 (๋น๋ฐ๋ณต)
+
+ // when
+ val actualMillis = calculator.calculateNonRepeatingTimeMillis(alarm, testZoneId)
+
+ // then
+ val expectedDateTime = MONDAY_2024_07_22_10AM.plusDays(1).with(alarmTime)
+ val expectedMillis = getExpectedMillis(expectedDateTime)
+ assertEquals(expectedMillis, actualMillis)
+ }
+
+ // --- ๋ค์ ๋ฐ๋ณต ์์ผ ์๋ ์๊ฐ ๊ณ์ฐ (calculateNextRepeatingTimeMillis) ํ
์คํธ ---
+ @Test
+ fun `๋ฐ๋ณต์์ผ์๋_์ค๋์ด_๋์์์ผ์ด๊ณ _์๋์๊ฐ์ด_๋ฏธ๋์ด๋ฉฐ_๊ณตํด์ผ๊ฑด๋๋ฐ๊ธฐ_๋นํ์ฑ์_์ค๋๋ก_๊ณ์ฐ๋๋ค`() {
+ // ํ์ฌ: 2024-07-22 (์) 10:00:00
+ // ์๋: ๋งค์ฃผ ์์์ผ 14:00:00, ๊ณตํด์ผ ๊ฑด๋๋ฐ๊ธฐ ๋นํ์ฑ
+ // ๊ธฐ๋: 2024-07-22 (์) 14:00:00
+
+ // given
+ val calculator = AlarmTimeCalculator(clockMonday2024_10am)
+ val alarmTime = LocalTime.of(14, 0)
+ val alarm = createTestAlarm(
+ hour = alarmTime.hour,
+ minute = alarmTime.minute,
+ isHolidayAlarmOff = false,
+ repeatDays = AlarmDay.MON.bitValue // ์์์ผ ๋ฐ๋ณต
+ )
+
+ // when
+ val actualMillis =
+ calculator.calculateNextRepeatingTimeMillis(alarm, AlarmDay.MON, testZoneId)
+
+ // then
+ val expectedDateTime = MONDAY_2024_07_22_10AM.with(alarmTime)
+ val expectedMillis = getExpectedMillis(expectedDateTime)
+ assertEquals(expectedMillis, actualMillis)
+ }
+
+ @Test
+ fun `๋ฐ๋ณต์์ผ์๋_์ค๋์ด_๊ณตํด์ผ์ธ_์ธ๋ฆด์์ผ์ด๊ณ _์๋์๊ฐ์ด_๋ฏธ๋์ด๋ฉฐ_๊ณตํด์ผ๊ฑด๋๋ฐ๊ธฐ_๋นํ์ฑ์_์ค๋_๊ณตํด์ผ๋ก_๊ณ์ฐ๋๋ค`() {
+ // ํ์ฌ: 2025-01-27 (์, ๊ณตํด์ผ) 10:00:00
+ // ์๋: ๋งค์ฃผ ์์์ผ 14:00:00, ๊ณตํด์ผ ๊ฑด๋๋ฐ๊ธฐ ๋นํ์ฑ
+ // ๊ธฐ๋: 2025-01-27 (์, ๊ณตํด์ผ) 14:00:00 (๊ฑด๋๋ฐ๊ธฐ ๋นํ์ฑ์ด๋ฏ๋ก ์ค๋ ๊ณตํด์ผ์ด์ด๋ ์ธ๋ฆผ)
+
+ // given
+ val calculator = AlarmTimeCalculator(clockMondayHoliday2025_10am)
+ val alarmTime = LocalTime.of(14, 0)
+ val alarm = createTestAlarm(
+ hour = alarmTime.hour,
+ minute = alarmTime.minute,
+ isHolidayAlarmOff = false,
+ repeatDays = AlarmDay.MON.bitValue // ์์์ผ ๋ฐ๋ณต
+ )
+
+ // when
+ val actualMillis =
+ calculator.calculateNextRepeatingTimeMillis(alarm, AlarmDay.MON, testZoneId)
+
+ // then
+ val expectedDateTime = MONDAY_HOLIDAY_2025_01_27_10AM.with(alarmTime)
+ val expectedMillis = getExpectedMillis(expectedDateTime)
+ assertEquals(expectedMillis, actualMillis)
+ }
+
+ @Test
+ fun `๋ฐ๋ณต์์ผ์๋_๋ค์์ฃผ_์ธ๋ฆด์์ผ์ด_๊ณตํด์ผ์ด๊ณ _๊ณตํด์ผ๊ฑด๋๋ฐ๊ธฐ_๋นํ์ฑ์_๋ค์์ฃผ_๊ณตํด์ผ๋ก_๊ณ์ฐ๋๋ค`() {
+ // ํ์ฌ: 2025-01-20 (์) 10:00:00 (๊ณตํด์ผ ์๋ ์์์ผ)
+ // ์๋: ๋งค์ฃผ ์์์ผ 09:00:00, ๊ณตํด์ผ ๊ฑด๋๋ฐ๊ธฐ ๋นํ์ฑ
+ // ๋ค์ ์ฃผ ์์์ผ: 2025-01-27 (๊ณตํด์ผ)
+ // ๊ธฐ๋: 2025-01-27 (์, ๊ณตํด์ผ) 09:00:00 (๊ฑด๋๋ฐ๊ธฐ ๋นํ์ฑ์ด๋ฏ๋ก ๋ค์ ์ฃผ ๊ณตํด์ผ์ด์ด๋ ์ธ๋ฆผ)
+
+ // given
+ val calculator = AlarmTimeCalculator(clockMonday2025_01_20_10am)
+ val alarmTime = LocalTime.of(9, 0)
+ val alarm = createTestAlarm(
+ hour = alarmTime.hour,
+ minute = alarmTime.minute,
+ isHolidayAlarmOff = false,
+ repeatDays = AlarmDay.MON.bitValue // ์์์ผ ๋ฐ๋ณต
+ )
+
+ // when
+ val actualMillis =
+ calculator.calculateNextRepeatingTimeMillis(alarm, AlarmDay.MON, testZoneId)
+
+ // then
+ val expectedDateTime = LocalDateTime.of(2025, 1, 27, 9, 0, 0)
+ val expectedMillis = getExpectedMillis(expectedDateTime)
+ assertEquals(expectedMillis, actualMillis)
+ }
+
+ @Test
+ fun `๋ฐ๋ณต์์ผ์๋_๋์์์ผ์ด_์ด๋ฒ์ฃผ_๋ฏธ๋์์ผ์ด๊ณ _๊ณตํด์ผ๊ฑด๋๋ฐ๊ธฐ_๋นํ์ฑ์_ํด๋น์์ผ๋ก_๊ณ์ฐ๋๋ค`() {
+ // ํ์ฌ: 2024-07-22 (์) 10:00:00
+ // ์๋: ๋งค์ฃผ ์์์ผ 11:00:00, ๊ณตํด์ผ ๊ฑด๋๋ฐ๊ธฐ ๋นํ์ฑ
+ // ๊ธฐ๋: 2024-07-24 (์) 11:00:00
+
+ // given
+ val calculator = AlarmTimeCalculator(clockMonday2024_10am)
+ val alarmTime = LocalTime.of(11, 0)
+ val alarm = createTestAlarm(
+ hour = alarmTime.hour,
+ minute = alarmTime.minute,
+ isHolidayAlarmOff = false,
+ repeatDays = AlarmDay.WED.bitValue // ์์์ผ ๋ฐ๋ณต
+ )
+
+ // when
+ val actualMillis =
+ calculator.calculateNextRepeatingTimeMillis(alarm, AlarmDay.WED, testZoneId)
+
+ // then
+ val expectedDateTime =
+ MONDAY_2024_07_22_10AM.plusDays(2).with(alarmTime) // 2024-07-24 (์) 11:00
+ val expectedMillis = getExpectedMillis(expectedDateTime)
+ assertEquals(expectedMillis, actualMillis)
+ }
+
+ @Test
+ fun `๋ฐ๋ณต์์ผ์๋_์ค๋์ด_๋์์์ผ์ด๋_์๊ฐ์ด_์ง๋ฌ๊ณ _๋ค์์ฃผ_ํด๋น์์ผ์ด_๊ณตํด์ผ์ด๋ฉฐ_๊ณตํด์ผ_๋นํ์ฑ์_๋ค์์ฃผ_๊ณตํด์ผ๋ก_๊ณ์ฐ๋๋ค`() {
+ // ํ์ฌ: 2025-01-20 (์) 14:01 (์์์ผ 14:00 ์๋ ํ)
+ // ์๋: ๋งค์ฃผ ์์์ผ 14:00, isHolidayAlarmOff = false
+ // ๋ค์์ฃผ ์์์ผ: 2025-01-27 (๊ณตํด์ผ)
+ // ๊ธฐ๋: 2025-01-27 (์) 14:00 (์ต์
Off์ด๋ฏ๋ก ๊ณตํด์ผ์ด์ด๋ ์ธ๋ฆผ)
+
+ // given
+ val calculator = AlarmTimeCalculator(clockMonday2025_PrevHoliday_2_01pm)
+ val alarmTime = LocalTime.of(14, 0)
+ val alarm = createTestAlarm(
+ hour = alarmTime.hour,
+ minute = alarmTime.minute,
+ isHolidayAlarmOff = false,
+ repeatDays = AlarmDay.MON.bitValue // ์์์ผ ๋ฐ๋ณต
+ )
+
+ // when
+ val actualMillis =
+ calculator.calculateNextRepeatingTimeMillis(alarm, AlarmDay.MON, testZoneId)
+
+ // then
+ val expectedDateTime = LocalDateTime.of(2025, 1, 27, 14, 0, 0)
+ val expectedMillis = getExpectedMillis(expectedDateTime)
+ assertEquals(expectedMillis, actualMillis)
+ }
+
+ @Test
+ fun `๋ฐ๋ณต์์ผ์๋_์ค๋์ด_๋์์์ผ์ด๋_์๊ฐ์ด_์ง๋ฌ๊ณ _๋ค์์ฃผ_ํด๋น์์ผ์ด_๊ณตํด์ผ์ด๋ฉฐ_๊ณตํด์ผ๊ฑด๋๋ฐ๊ธฐ_ํ์ฑ์_๋ค๋ค์์ฃผ_ํด๋น์์ผ๋ก_๊ณ์ฐ๋๋ค`() {
+ // ํ์ฌ: 2025-01-20 (์) 14:01 (์์์ผ 14:00 ์๋ ํ)
+ // ์๋: ๋งค์ฃผ ์์์ผ 14:00, isHolidayAlarmOff = true
+ // ๋ค์์ฃผ ์์์ผ: 2025-01-27 (๊ณตํด์ผ)
+ // ๊ธฐ๋: ๋ค๋ค์์ฃผ ์์์ผ 2025-02-03 (์) 14:00
+
+ // given
+ val calculator = AlarmTimeCalculator(clockMonday2025_PrevHoliday_2_01pm)
+ val alarmTime = LocalTime.of(14, 0)
+ val alarm = createTestAlarm(
+ hour = alarmTime.hour,
+ minute = alarmTime.minute,
+ isHolidayAlarmOff = true,
+ repeatDays = AlarmDay.MON.bitValue // ์์์ผ ๋ฐ๋ณต
+ )
+
+ // when
+ val actualMillis =
+ calculator.calculateNextRepeatingTimeMillis(alarm, AlarmDay.MON, testZoneId)
+
+ // then
+ val expectedDateTime = LocalDateTime.of(2025, 2, 3, 14, 0, 0)
+ val expectedMillis = getExpectedMillis(expectedDateTime)
+ assertEquals(expectedMillis, actualMillis)
+ }
+
+ @Test
+ fun `๋ฐ๋ณต์์ผ์๋_์ค๋์ด_๊ณตํด์ผ์ธ_๋์์์ผ์ด๊ณ _์๋์๊ฐ์ด_๋ฏธ๋์ด๋ฉฐ_๊ณตํด์ผ๊ฑด๋๋ฐ๊ธฐ_ํ์ฑ์_๋ค์์ฃผ_ํด๋น์์ผ๋ก_๊ณ์ฐ๋๋ค`() {
+ // ํ์ฌ: 2025-01-27 (์, ๊ณตํด์ผ) 10:00
+ // ์๋: ๋งค์ฃผ ์์์ผ 14:00, isHolidayAlarmOff = true
+ // ๊ธฐ๋: ๋ค์์ฃผ ์์์ผ 2025-02-03 (์) 14:00
+
+ // given
+ val calculator = AlarmTimeCalculator(clockMondayHoliday2025_10am)
+ val alarmTime = LocalTime.of(14, 0)
+ val alarm = createTestAlarm(
+ hour = alarmTime.hour,
+ minute = alarmTime.minute,
+ isHolidayAlarmOff = true,
+ repeatDays = AlarmDay.MON.bitValue // ์์์ผ ๋ฐ๋ณต
+ )
+
+ // when
+ val actualMillis =
+ calculator.calculateNextRepeatingTimeMillis(alarm, AlarmDay.MON, testZoneId)
+
+ // then
+ val expectedDateTime = LocalDateTime.of(2025, 2, 3, 14, 0, 0)
+ val expectedMillis = getExpectedMillis(expectedDateTime)
+ assertEquals(expectedMillis, actualMillis)
+ }
+
+ @Test
+ fun `๋ฐ๋ณต์์ผ์๋_์ค๋์ด_๊ณตํด์ผ์ธ_๋์์์ผ์ด๊ณ _์๋์๊ฐ์ด_๋ฏธ๋์ด๋ฉฐ_๊ณตํด์ผ๊ฑด๋๋ฐ๊ธฐ_๋นํ์ฑ์_์ค๋_๊ณตํด์ผ๋ก_๊ณ์ฐ๋๋ค`() {
+ // ํ์ฌ: 2025-01-27 (์, ๊ณตํด์ผ) 10:00
+ // ์๋: ๋งค์ฃผ ์์์ผ 14:00, isHolidayAlarmOff = false
+ // ๊ธฐ๋: ์ค๋ 2025-01-27 (์) 14:00
+
+ // given
+ val calculator = AlarmTimeCalculator(clockMondayHoliday2025_10am)
+ val alarmTime = LocalTime.of(14, 0)
+ val alarm = createTestAlarm(
+ hour = alarmTime.hour,
+ minute = alarmTime.minute,
+ isHolidayAlarmOff = false,
+ repeatDays = AlarmDay.MON.bitValue // ์์์ผ ๋ฐ๋ณต
+ )
+
+ // when
+ val actualMillis =
+ calculator.calculateNextRepeatingTimeMillis(alarm, AlarmDay.MON, testZoneId)
+
+ // then
+ val expectedDateTime = MONDAY_HOLIDAY_2025_01_27_10AM.with(alarmTime)
+ val expectedMillis = getExpectedMillis(expectedDateTime)
+ assertEquals(expectedMillis, actualMillis)
+ }
+
+
+ // --- ๋ค์ ์ฃผ๊ฐ ์ฌ์์ฝ ์๋ ์๊ฐ ๊ณ์ฐ (calculateNextWeeklyRescheduledTimeMillis) ํ
์คํธ ---
+ @Test
+ fun `์ฃผ๊ฐ์ฌ์์ฝ_ํ์ฌ_์์์ผ์ค์ _๋์๋_์์์ผ_๊ณตํด์ผ๊ฑด๋๋ฐ๊ธฐ_๋นํ์ฑ_๋ค์์ฃผ_์์์ผ์ด_๊ณตํด์ผ์๋๋_๋ค์์ฃผ_์์์ผ๋ก_๊ณ์ฐ๋๋ค`() {
+ // ํ์ฌ: 2024-07-22 (์) 10:00
+ // ์๋: ๋งค์ฃผ ์์์ผ 14:00, isHolidayAlarmOff = false
+ // ๋ค์์ฃผ ์์์ผ: 2024-07-29 (๊ณตํด์ผ ์๋)
+ // ๊ธฐ๋: 2024-07-29 (์) 14:00
+
+ // given
+ val calculator = AlarmTimeCalculator(clockMonday2024_10am)
+ val alarmTime = LocalTime.of(14, 0)
+ val alarm = createTestAlarm(
+ hour = alarmTime.hour,
+ minute = alarmTime.minute,
+ isHolidayAlarmOff = false,
+ repeatDays = AlarmDay.MON.bitValue // ์์์ผ ๋ฐ๋ณต
+ )
+
+ // when
+ val actualMillis =
+ calculator.calculateNextWeeklyRescheduledTimeMillis(alarm, AlarmDay.MON, testZoneId)
+
+ // then
+ val expectedDateTime = LocalDateTime.of(2024, 7, 29, 14, 0, 0)
+ val expectedMillis = getExpectedMillis(expectedDateTime)
+ assertEquals(expectedMillis, actualMillis)
+ }
+
+ @Test
+ fun `์ฃผ๊ฐ์ฌ์์ฝ_๋ค์์ฃผ_์ธ๋ฆด์์ผ์ด_๊ณตํด์ผ์ด๊ณ _๊ณตํด์ผ๊ฑด๋๋ฐ๊ธฐ_๋นํ์ฑ์_๋ค์์ฃผ_๊ณตํด์ผ๋ก_๊ณ์ฐ๋๋ค`() {
+ // ํ์ฌ: 2025-01-20 (์) 10:00:00 (์ค ์ฐํด ์ ์ฃผ ์์์ผ ์ค์ )
+ // ์๋: ๋งค์ฃผ ์์์ผ 14:00:00, ๊ณตํด์ผ ๊ฑด๋๋ฐ๊ธฐ ๋นํ์ฑ
+ // ๋ค์์ฃผ ์์์ผ: 2025-01-27 (๊ณตํด์ผ)
+ // ๊ธฐ๋: 2025-01-27 (์, ๊ณตํด์ผ) 14:00:00 (๊ฑด๋๋ฐ๊ธฐ ๋นํ์ฑ์ด๋ฏ๋ก ๋ค์์ฃผ ๊ณตํด์ผ์ด์ด๋ ์ธ๋ฆผ)
+
+ // given
+ val calculator = AlarmTimeCalculator(clockMonday2025_01_20_10am)
+ val alarmTime = LocalTime.of(14, 0)
+ val alarm = createTestAlarm(
+ hour = alarmTime.hour,
+ minute = alarmTime.minute,
+ isHolidayAlarmOff = false,
+ repeatDays = AlarmDay.MON.bitValue // ์์์ผ ๋ฐ๋ณต
+ )
+
+ // when
+ val actualMillis =
+ calculator.calculateNextWeeklyRescheduledTimeMillis(alarm, AlarmDay.MON, testZoneId)
+
+ // then
+ val expectedDateTime = LocalDateTime.of(2025, 1, 27, 14, 0, 0)
+ val expectedMillis = getExpectedMillis(expectedDateTime)
+ assertEquals(expectedMillis, actualMillis)
+ }
+
+ @Test
+ fun `์ฃผ๊ฐ์ฌ์์ฝ_ํ์ฌ_๊ธ์์ผ์คํ_๋์์_์์์ผ_๊ณตํด์ผ๊ฑด๋๋ฐ๊ธฐ_๋นํ์ฑ์_๋ค๋ค์์ฃผ_์์์ผ๋ก_๊ณ์ฐ๋๋ค`() {
+ // ํ์ฌ: 2024-07-26 (๊ธ) 15:00
+ // ์๋: ๋งค์ฃผ ์์์ผ 14:00, isHolidayAlarmOff = false
+ // ๋ก์ง: ๊ฐ์ฅ ๊ฐ๊น์ด ๋ค์ ์์์ผ(29์ผ)์ ๊ทธ ๋ค์ ์ฃผ ์์์ผ(5์ผ)
+ // ๊ธฐ๋: 2024-08-05 (์) 14:00
+
+ // given
+ val calculator = AlarmTimeCalculator(clockFriday2024_3pm)
+ val alarmTime = LocalTime.of(14, 0)
+ val alarm = createTestAlarm(
+ hour = alarmTime.hour,
+ minute = alarmTime.minute,
+ isHolidayAlarmOff = false,
+ repeatDays = AlarmDay.MON.bitValue // ์์์ผ ๋ฐ๋ณต
+ )
+
+ // when
+ val actualMillis =
+ calculator.calculateNextWeeklyRescheduledTimeMillis(alarm, AlarmDay.MON, testZoneId)
+
+ // then
+ val expectedDateTime = LocalDateTime.of(2024, 8, 5, 14, 0, 0)
+ val expectedMillis = getExpectedMillis(expectedDateTime)
+ assertEquals(expectedMillis, actualMillis)
+ }
+
+ @Test
+ fun `์ฃผ๊ฐ์ฌ์์ฝ_ํ์ฌ_์์์ผ_๋์๋_์์์ผ_๊ณตํด์ผ๊ฑด๋๋ฐ๊ธฐ_ํ์ฑ_๋ค์์ฃผ_์์์ผ์ด_๊ณตํด์ผ์ผ๋_๋ค๋ค์์ฃผ_์์์ผ๋ก_๊ณ์ฐ๋๋ค`() {
+ // ํ์ฌ: 2025-01-20 (์) 10:00 (์ค ์ฐํด ์ ์ฃผ ์์์ผ ์ค์ )
+ // ์๋: ๋งค์ฃผ ์์์ผ 14:00, isHolidayAlarmOff = true
+ // ๋ค์์ฃผ ์์์ผ: 2025-01-27 (๊ณตํด์ผ)
+ // ๊ธฐ๋: ๋ค๋ค์์ฃผ ์์์ผ 2025-02-03 (์) 14:00
+
+ // given
+ val calculator = AlarmTimeCalculator(clockMonday2025_01_20_10am)
+ val alarmTime = LocalTime.of(14, 0)
+ val alarm = createTestAlarm(
+ hour = alarmTime.hour,
+ minute = alarmTime.minute,
+ isHolidayAlarmOff = true,
+ repeatDays = AlarmDay.MON.bitValue // ์์์ผ ๋ฐ๋ณต
+ )
+
+ // when
+ val actualMillis =
+ calculator.calculateNextWeeklyRescheduledTimeMillis(alarm, AlarmDay.MON, testZoneId)
+
+ // then
+ val expectedDateTime = LocalDateTime.of(2025, 2, 3, 14, 0, 0)
+ val expectedMillis = getExpectedMillis(expectedDateTime)
+ assertEquals(expectedMillis, actualMillis)
+ }
+
+ @Test
+ fun `์ฃผ๊ฐ์ฌ์์ฝ_ํ์ฌ_์์์ผ_๋์๋_์์์ผ_๊ณตํด์ผ๊ฑด๋๋ฐ๊ธฐ_ํ์ฑ_๋ค์์ฃผ_์์์ผ์ด_๊ณตํด์ผ์๋๋_๋ค์์ฃผ_์์์ผ๋ก_๊ณ์ฐ๋๋ค`() {
+ // ํ์ฌ: 2025-02-17 (์) 10:00 (์ผ์ผ์ ์ฐํด ์ ์ ์ฃผ ์์์ผ)
+ // ์๋: ๋งค์ฃผ ์์์ผ 14:00, isHolidayAlarmOff = true
+ // ๋ค์์ฃผ ์์์ผ: 2025-02-24 (๊ณตํด์ผ ์๋)
+ // ๊ธฐ๋: 2025-02-24 (์) 14:00
+
+ // given
+ val calculator = AlarmTimeCalculator(clockMonday2025_02_17_10am)
+ val alarmTime = LocalTime.of(14, 0)
+ val alarm = createTestAlarm(
+ hour = alarmTime.hour,
+ minute = alarmTime.minute,
+ isHolidayAlarmOff = true,
+ repeatDays = AlarmDay.MON.bitValue // ์์์ผ ๋ฐ๋ณต
+ )
+
+ // when
+ val actualMillis =
+ calculator.calculateNextWeeklyRescheduledTimeMillis(alarm, AlarmDay.MON, testZoneId)
+
+ // then
+ val expectedDateTime = LocalDateTime.of(2025, 2, 24, 14, 0, 0)
+ val expectedMillis = getExpectedMillis(expectedDateTime)
+ assertEquals(expectedMillis, actualMillis)
+ }
+}
diff --git a/core/common/src/main/java/com/yapp/common/di/ClockModule.kt b/core/common/src/main/java/com/yapp/common/di/ClockModule.kt
new file mode 100644
index 00000000..50158f3a
--- /dev/null
+++ b/core/common/src/main/java/com/yapp/common/di/ClockModule.kt
@@ -0,0 +1,17 @@
+package com.yapp.common.di
+
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import java.time.Clock
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object ClockModule {
+
+ @Provides
+ @Singleton
+ fun provideClock(): Clock = Clock.systemDefaultZone()
+}
diff --git a/core/common/src/main/java/com/yapp/common/di/LocaleModule.kt b/core/common/src/main/java/com/yapp/common/di/LocaleModule.kt
new file mode 100644
index 00000000..839a5a8f
--- /dev/null
+++ b/core/common/src/main/java/com/yapp/common/di/LocaleModule.kt
@@ -0,0 +1,17 @@
+package com.yapp.common.di
+
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import java.util.Locale
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object LocaleModule {
+
+ @Provides
+ @Singleton
+ fun provideLocale(): Locale = Locale.getDefault()
+}
diff --git a/core/common/src/main/java/com/yapp/common/navigation/OrbitNavigator.kt b/core/common/src/main/java/com/yapp/common/navigation/OrbitNavigator.kt
index 34ceed6f..ec884f97 100644
--- a/core/common/src/main/java/com/yapp/common/navigation/OrbitNavigator.kt
+++ b/core/common/src/main/java/com/yapp/common/navigation/OrbitNavigator.kt
@@ -11,6 +11,7 @@ import com.yapp.common.navigation.route.FortuneBaseRoute
import com.yapp.common.navigation.route.FortuneDestination
import com.yapp.common.navigation.route.HomeBaseRoute
import com.yapp.common.navigation.route.HomeDestination
+import com.yapp.common.navigation.route.MissionRoute
import com.yapp.common.navigation.route.OnboardingBaseRoute
import com.yapp.common.navigation.route.OnboardingDestination
import com.yapp.common.navigation.route.SettingBaseRoute
@@ -57,6 +58,21 @@ class OrbitNavigator(
navController.navigate(AlarmInteractionDestination.AlarmSnoozeTimer(alarm), navOptions)
}
+ fun navigateToMissionPreview(
+ missionType: Int,
+ missionCount: Int,
+ navOptions: NavOptions? = null,
+ ) {
+ navController.navigate(
+ MissionRoute(
+ missionType = "$missionType",
+ missionCount = "$missionCount",
+ missionMode = "PREVIEW",
+ ),
+ navOptions,
+ )
+ }
+
fun navigateToFortune(navOptions: NavOptions? = null) {
navController.navigate(FortuneBaseRoute, navOptions)
}
diff --git a/core/common/src/main/java/com/yapp/common/navigation/route/MissionRoute.kt b/core/common/src/main/java/com/yapp/common/navigation/route/MissionRoute.kt
index d5a04949..111c7788 100644
--- a/core/common/src/main/java/com/yapp/common/navigation/route/MissionRoute.kt
+++ b/core/common/src/main/java/com/yapp/common/navigation/route/MissionRoute.kt
@@ -1,6 +1,11 @@
package com.yapp.common.navigation.route
+import com.yapp.domain.MissionMode
import kotlinx.serialization.Serializable
@Serializable
-data object MissionRoute
+data class MissionRoute(
+ val missionType: String,
+ val missionCount: String,
+ val missionMode: String = MissionMode.REAL.name,
+)
diff --git a/core/common/src/main/java/com/yapp/common/security/CryptoManager.kt b/core/common/src/main/java/com/yapp/common/security/CryptoManager.kt
deleted file mode 100644
index d3995727..00000000
--- a/core/common/src/main/java/com/yapp/common/security/CryptoManager.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.yapp.common.security
-
-interface CryptoManager {
- fun encryptData(keyAlias: String, text: String): Pair
-
- fun decryptData(keyAlias: String, encryptedData: ByteArray, iv: ByteArray): ByteArray
-}
diff --git a/core/security/.gitignore b/core/database/.gitignore
similarity index 100%
rename from core/security/.gitignore
rename to core/database/.gitignore
diff --git a/core/database/build.gradle.kts b/core/database/build.gradle.kts
new file mode 100644
index 00000000..5796214b
--- /dev/null
+++ b/core/database/build.gradle.kts
@@ -0,0 +1,26 @@
+import com.yapp.convention.setNamespace
+
+plugins {
+ id("orbit.android.library")
+ id("androidx.room")
+}
+
+android {
+ setNamespace("core.database")
+
+ sourceSets { getByName("androidTest").assets.srcDir("$projectDir/schemas") }
+}
+
+room {
+ schemaDirectory("$projectDir/schemas")
+}
+
+dependencies {
+ implementation(projects.domain)
+
+ ksp(libs.androidx.room.compiler)
+ implementation(libs.androidx.room.ktx)
+ implementation(libs.androidx.room.runtime)
+
+ androidTestImplementation(libs.androidx.room.testing)
+}
diff --git a/core/security/consumer-rules.pro b/core/database/consumer-rules.pro
similarity index 100%
rename from core/security/consumer-rules.pro
rename to core/database/consumer-rules.pro
diff --git a/core/security/proguard-rules.pro b/core/database/proguard-rules.pro
similarity index 100%
rename from core/security/proguard-rules.pro
rename to core/database/proguard-rules.pro
diff --git a/core/database/schemas/com.yapp.database.AlarmDatabase/1.json b/core/database/schemas/com.yapp.database.AlarmDatabase/1.json
new file mode 100644
index 00000000..f700d6a5
--- /dev/null
+++ b/core/database/schemas/com.yapp.database.AlarmDatabase/1.json
@@ -0,0 +1,118 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 1,
+ "identityHash": "d9643e982a8885da158bcd94c55931ff",
+ "entities": [
+ {
+ "tableName": "alarm_database",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `isAm` INTEGER NOT NULL, `hour` INTEGER NOT NULL, `minute` INTEGER NOT NULL, `second` INTEGER NOT NULL, `repeatDays` INTEGER NOT NULL, `isHolidayAlarmOff` INTEGER NOT NULL, `isSnoozeEnabled` INTEGER NOT NULL, `snoozeInterval` INTEGER NOT NULL, `snoozeCount` INTEGER NOT NULL, `isVibrationEnabled` INTEGER NOT NULL, `isSoundEnabled` INTEGER NOT NULL, `soundUri` TEXT NOT NULL, `soundVolume` INTEGER NOT NULL, `isAlarmActive` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isAm",
+ "columnName": "isAm",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hour",
+ "columnName": "hour",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "minute",
+ "columnName": "minute",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "second",
+ "columnName": "second",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "repeatDays",
+ "columnName": "repeatDays",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isHolidayAlarmOff",
+ "columnName": "isHolidayAlarmOff",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isSnoozeEnabled",
+ "columnName": "isSnoozeEnabled",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "snoozeInterval",
+ "columnName": "snoozeInterval",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "snoozeCount",
+ "columnName": "snoozeCount",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isVibrationEnabled",
+ "columnName": "isVibrationEnabled",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isSoundEnabled",
+ "columnName": "isSoundEnabled",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "soundUri",
+ "columnName": "soundUri",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "soundVolume",
+ "columnName": "soundVolume",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isAlarmActive",
+ "columnName": "isAlarmActive",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd9643e982a8885da158bcd94c55931ff')"
+ ]
+ }
+}
diff --git a/core/database/schemas/com.yapp.database.AlarmDatabase/2.json b/core/database/schemas/com.yapp.database.AlarmDatabase/2.json
new file mode 100644
index 00000000..9d84a14a
--- /dev/null
+++ b/core/database/schemas/com.yapp.database.AlarmDatabase/2.json
@@ -0,0 +1,123 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 2,
+ "identityHash": "3d2a568f32fed54188f8a57463eddcf1",
+ "entities": [
+ {
+ "tableName": "alarm_database",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `hour` INTEGER NOT NULL, `minute` INTEGER NOT NULL, `second` INTEGER NOT NULL, `repeatDays` INTEGER NOT NULL, `isHolidayAlarmOff` INTEGER NOT NULL, `isSnoozeEnabled` INTEGER NOT NULL, `snoozeInterval` INTEGER NOT NULL, `snoozeCount` INTEGER NOT NULL, `isVibrationEnabled` INTEGER NOT NULL, `isSoundEnabled` INTEGER NOT NULL, `soundUri` TEXT NOT NULL, `soundVolume` INTEGER NOT NULL, `isAlarmActive` INTEGER NOT NULL, `missionType` INTEGER NOT NULL DEFAULT 1, `missionCount` INTEGER NOT NULL DEFAULT 10)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hour",
+ "columnName": "hour",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "minute",
+ "columnName": "minute",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "second",
+ "columnName": "second",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "repeatDays",
+ "columnName": "repeatDays",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isHolidayAlarmOff",
+ "columnName": "isHolidayAlarmOff",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isSnoozeEnabled",
+ "columnName": "isSnoozeEnabled",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "snoozeInterval",
+ "columnName": "snoozeInterval",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "snoozeCount",
+ "columnName": "snoozeCount",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isVibrationEnabled",
+ "columnName": "isVibrationEnabled",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isSoundEnabled",
+ "columnName": "isSoundEnabled",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "soundUri",
+ "columnName": "soundUri",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "soundVolume",
+ "columnName": "soundVolume",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isAlarmActive",
+ "columnName": "isAlarmActive",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "missionType",
+ "columnName": "missionType",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "1"
+ },
+ {
+ "fieldPath": "missionCount",
+ "columnName": "missionCount",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "10"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ }
+ }
+ ],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3d2a568f32fed54188f8a57463eddcf1')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/core/database/src/androidTest/java/com/yapp/database/MigrationTest.kt b/core/database/src/androidTest/java/com/yapp/database/MigrationTest.kt
new file mode 100644
index 00000000..3798f783
--- /dev/null
+++ b/core/database/src/androidTest/java/com/yapp/database/MigrationTest.kt
@@ -0,0 +1,133 @@
+package com.yapp.database
+
+import androidx.room.testing.MigrationTestHelper
+import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.io.IOException
+
+@RunWith(AndroidJUnit4::class)
+class MigrationTest {
+
+ private val testDbName = "test_alarm_database"
+
+ @get:Rule
+ val helper: MigrationTestHelper = MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ AlarmDatabase::class.java,
+ emptyList(),
+ FrameworkSQLiteOpenHelperFactory(),
+ )
+
+ @After
+ fun tearDown() {
+ InstrumentationRegistry.getInstrumentation()
+ .targetContext.deleteDatabase(testDbName)
+ }
+
+ @Test
+ @Throws(IOException::class)
+ fun `๋ฒ์ 1์์_๋ฒ์ 2๋ก_๋ง์ด๊ทธ๋ ์ด์
์_์_์ปฌ๋ผ์ด_๊ธฐ๋ณธ๊ฐ์ผ๋ก_์ฑ์์ง`() {
+ helper.createDatabase(testDbName, 1).apply {
+ execSQL(
+ """
+ INSERT INTO alarm_database (
+ id,
+ isAm,
+ hour,
+ minute,
+ second,
+ repeatDays,
+ isHolidayAlarmOff,
+ isSnoozeEnabled,
+ snoozeInterval,
+ snoozeCount,
+ isVibrationEnabled,
+ isSoundEnabled,
+ soundUri,
+ soundVolume,
+ isAlarmActive
+ ) VALUES (
+ null, -- id (autoGenerate)
+ 0, -- isAm = false
+ 11, -- hour
+ 30, -- minute
+ 0, -- second
+ 0, -- repeatDays
+ 0, -- isHolidayAlarmOff = false
+ 1, -- isSnoozeEnabled = true
+ 5, -- snoozeInterval
+ 3, -- snoozeCount
+ 1, -- isVibrationEnabled = true
+ 1, -- isSoundEnabled = true
+ 'alarm.mp3', -- soundUri
+ 70, -- soundVolume
+ 1 -- isAlarmActive = true
+ )
+ """.trimIndent(),
+ )
+ close()
+ }
+
+ val db = helper.runMigrationsAndValidate(testDbName, 2, true, DatabaseMigrations.MIGRATION_1_2)
+
+ val cursor = db.query("SELECT hour, missionType, missionCount FROM ${AlarmDatabase.DATABASE_NAME}")
+ cursor.use {
+ assertEquals(1, it.count)
+ it.moveToFirst()
+ assertEquals(1, it.getInt(it.getColumnIndexOrThrow("missionType")))
+ assertEquals(10, it.getInt(it.getColumnIndexOrThrow("missionCount")))
+ }
+
+ db.close()
+ }
+
+ @Test
+ @Throws(IOException::class)
+ fun `๋ฒ์ 1์์_๋ฒ์ 2๋ก_๋ง์ด๊ทธ๋ ์ด์
์_12์๊ฐ_ํฌ๋งท์ด_24์๊ฐ_ํฌ๋งท์ผ๋ก_์ ํํ_๋ณํ๋๋์ง_ํ์ธ`() {
+ helper.createDatabase(testDbName, 1).apply {
+ // 4๊ฐ์ง ์ผ์ด์ค ์ฝ์
+ listOf(
+ Triple(1, 12, 0), // ์ค์ 12์ โ 0์
+ Triple(0, 12, 12), // ์คํ 12์ โ 12์
+ Triple(1, 7, 7), // ์ค์ 7์ โ 7์
+ Triple(0, 7, 19), // ์คํ 7์ โ 19์
+ ).forEach { (isAm, hour12, _) ->
+ execSQL(
+ """
+ INSERT INTO alarm_database (
+ id, isAm, hour, minute, second, repeatDays, isHolidayAlarmOff,
+ isSnoozeEnabled, snoozeInterval, snoozeCount, isVibrationEnabled,
+ isSoundEnabled, soundUri, soundVolume, isAlarmActive
+ ) VALUES (
+ null, $isAm, $hour12, 0, 0, 0, 0, 1, 5, 3, 1, 1, 'alarm.mp3', 70, 1
+ )
+ """.trimIndent(),
+ )
+ }
+ close()
+ }
+
+ val db =
+ helper.runMigrationsAndValidate(testDbName, 2, true, DatabaseMigrations.MIGRATION_1_2)
+
+ val expected = listOf(0, 12, 7, 19) // ๊ธฐ๋ ๊ฒฐ๊ณผ: ๋ณํ๋ hour ์์
+ val cursor = db.query("SELECT hour FROM ${AlarmDatabase.DATABASE_NAME}")
+ cursor.use {
+ assertEquals(4, it.count)
+ var idx = 0
+ while (it.moveToNext()) {
+ val actual = it.getInt(it.getColumnIndexOrThrow("hour"))
+ assertEquals(expected[idx], actual)
+ idx++
+ }
+ }
+
+ db.close()
+ }
+}
diff --git a/feature/navigator/src/main/AndroidManifest.xml b/core/database/src/main/AndroidManifest.xml
similarity index 98%
rename from feature/navigator/src/main/AndroidManifest.xml
rename to core/database/src/main/AndroidManifest.xml
index 76073216..e1000761 100644
--- a/feature/navigator/src/main/AndroidManifest.xml
+++ b/core/database/src/main/AndroidManifest.xml
@@ -1,3 +1,4 @@
+
diff --git a/data/src/main/java/com/yapp/data/local/AlarmDao.kt b/core/database/src/main/java/com/yapp/database/AlarmDao.kt
similarity index 66%
rename from data/src/main/java/com/yapp/data/local/AlarmDao.kt
rename to core/database/src/main/java/com/yapp/database/AlarmDao.kt
index 41f0291a..9d2b4e9f 100644
--- a/data/src/main/java/com/yapp/data/local/AlarmDao.kt
+++ b/core/database/src/main/java/com/yapp/database/AlarmDao.kt
@@ -1,4 +1,4 @@
-package com.yapp.data.local
+package com.yapp.database
import androidx.room.Dao
import androidx.room.Insert
@@ -22,17 +22,11 @@ interface AlarmDao {
@Query("SELECT * FROM ${AlarmDatabase.DATABASE_NAME} WHERE id = :id")
suspend fun getAlarm(id: Long): AlarmEntity?
- @Query("SELECT * FROM ${AlarmDatabase.DATABASE_NAME} ORDER BY isAm DESC, hour ASC, minute ASC LIMIT :limit OFFSET :offset")
- fun getPagedAlarms(limit: Int, offset: Int): Flow>
-
- @Query("SELECT * FROM ${AlarmDatabase.DATABASE_NAME} ORDER BY isAm DESC, hour ASC, minute ASC")
+ @Query("SELECT * FROM ${AlarmDatabase.DATABASE_NAME} ORDER BY hour ASC, minute ASC")
fun getAllAlarms(): Flow>
- @Query("SELECT * FROM ${AlarmDatabase.DATABASE_NAME} WHERE hour = :hour AND minute = :minute AND isAm = :isAm")
- fun getAlarmsByTime(hour: Int, minute: Int, isAm: Boolean): Flow>
-
- @Query("SELECT COUNT(*) FROM ${AlarmDatabase.DATABASE_NAME}")
- fun getAlarmCount(): Flow
+ @Query("SELECT * FROM ${AlarmDatabase.DATABASE_NAME} WHERE hour = :hour AND minute = :minute")
+ fun getAlarmsByTime(hour: Int, minute: Int): Flow>
@Query("DELETE FROM ${AlarmDatabase.DATABASE_NAME} WHERE id = :id")
suspend fun deleteAlarm(id: Long): Int
diff --git a/data/src/main/java/com/yapp/data/local/AlarmDatabase.kt b/core/database/src/main/java/com/yapp/database/AlarmDatabase.kt
similarity index 56%
rename from data/src/main/java/com/yapp/data/local/AlarmDatabase.kt
rename to core/database/src/main/java/com/yapp/database/AlarmDatabase.kt
index 027f62d3..988912e2 100644
--- a/data/src/main/java/com/yapp/data/local/AlarmDatabase.kt
+++ b/core/database/src/main/java/com/yapp/database/AlarmDatabase.kt
@@ -1,9 +1,11 @@
-package com.yapp.data.local
+package com.yapp.database
import androidx.room.Database
import androidx.room.RoomDatabase
+import androidx.room.TypeConverters
-@Database(entities = [AlarmEntity::class], version = 1, exportSchema = false)
+@Database(entities = [AlarmEntity::class], version = 2, exportSchema = true)
+@TypeConverters(MissionTypeConverter::class)
abstract class AlarmDatabase : RoomDatabase() {
abstract fun alarmDao(): AlarmDao
diff --git a/data/src/main/java/com/yapp/data/local/AlarmEntity.kt b/core/database/src/main/java/com/yapp/database/AlarmEntity.kt
similarity index 81%
rename from data/src/main/java/com/yapp/data/local/AlarmEntity.kt
rename to core/database/src/main/java/com/yapp/database/AlarmEntity.kt
index 56ce2472..72320fbd 100644
--- a/data/src/main/java/com/yapp/data/local/AlarmEntity.kt
+++ b/core/database/src/main/java/com/yapp/database/AlarmEntity.kt
@@ -1,16 +1,16 @@
-package com.yapp.data.local
+package com.yapp.database
+import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.yapp.domain.model.Alarm
+import com.yapp.domain.model.MissionType
@Entity(tableName = AlarmDatabase.DATABASE_NAME)
data class AlarmEntity(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
- val isAm: Boolean = true,
-
val hour: Int = 6,
val minute: Int = 0,
val second: Int = 0,
@@ -31,11 +31,15 @@ data class AlarmEntity(
val soundVolume: Int = 70,
val isAlarmActive: Boolean = true,
+
+ @ColumnInfo(defaultValue = "1")
+ val missionType: MissionType = MissionType.TAP,
+ @ColumnInfo(defaultValue = "10")
+ val missionCount: Int = 10,
)
fun AlarmEntity.toDomain() = Alarm(
id = id,
- isAm = isAm,
hour = hour,
minute = minute,
second = second,
@@ -49,11 +53,12 @@ fun AlarmEntity.toDomain() = Alarm(
soundUri = soundUri,
soundVolume = soundVolume,
isAlarmActive = isAlarmActive,
+ missionType = missionType,
+ missionCount = missionCount,
)
fun Alarm.toEntity() = AlarmEntity(
id = id,
- isAm = isAm,
hour = hour,
minute = minute,
second = second,
@@ -67,4 +72,6 @@ fun Alarm.toEntity() = AlarmEntity(
soundUri = soundUri,
soundVolume = soundVolume,
isAlarmActive = isAlarmActive,
+ missionType = missionType,
+ missionCount = missionCount,
)
diff --git a/core/database/src/main/java/com/yapp/database/DatabaseMigrations.kt b/core/database/src/main/java/com/yapp/database/DatabaseMigrations.kt
new file mode 100644
index 00000000..8d163fb2
--- /dev/null
+++ b/core/database/src/main/java/com/yapp/database/DatabaseMigrations.kt
@@ -0,0 +1,86 @@
+package com.yapp.database
+
+import androidx.room.migration.Migration
+import androidx.sqlite.db.SupportSQLiteDatabase
+import com.yapp.database.AlarmDatabase.Companion.DATABASE_NAME
+
+internal object DatabaseMigrations {
+
+ val MIGRATION_1_2 = object : Migration(1, 2) {
+ override fun migrate(database: SupportSQLiteDatabase) {
+ database.beginTransaction()
+ try {
+ // 1. ์ ์คํค๋ง๋ก ์์ ํ
์ด๋ธ ์์ฑ (isAm ์ปฌ๋ผ ์ ์ธ, missionType, missionCount ์ถ๊ฐ ๋ฐ ๊ธฐ๋ณธ๊ฐ ๋ณ๊ฒฝ)
+ database.execSQL(
+ """
+ CREATE TABLE ${DATABASE_NAME}_new (
+ id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+ hour INTEGER NOT NULL,
+ minute INTEGER NOT NULL,
+ second INTEGER NOT NULL,
+ repeatDays INTEGER NOT NULL,
+ isHolidayAlarmOff INTEGER NOT NULL,
+ isSnoozeEnabled INTEGER NOT NULL,
+ snoozeInterval INTEGER NOT NULL,
+ snoozeCount INTEGER NOT NULL,
+ isVibrationEnabled INTEGER NOT NULL,
+ isSoundEnabled INTEGER NOT NULL,
+ soundUri TEXT NOT NULL,
+ soundVolume INTEGER NOT NULL,
+ isAlarmActive INTEGER NOT NULL,
+ missionType INTEGER NOT NULL DEFAULT 1, -- ํ์
INTEGER, ๊ธฐ๋ณธ๊ฐ 1
+ missionCount INTEGER NOT NULL DEFAULT 10 -- ํ์
INTEGER, ๊ธฐ๋ณธ๊ฐ 10
+ )
+ """.trimIndent(),
+ )
+
+ // 2. ๊ธฐ์กด ํ
์ด๋ธ์์ ์ ์์ ํ
์ด๋ธ๋ก ๋ฐ์ดํฐ ๋ณต์ฌ (isAm ์ปฌ๋ผ์ ๋ณต์ฌํ์ง ์์)
+ database.execSQL(
+ """
+ INSERT INTO ${DATABASE_NAME}_new (
+ id, hour, minute, second, repeatDays, isHolidayAlarmOff,
+ isSnoozeEnabled, snoozeInterval, snoozeCount, isVibrationEnabled,
+ isSoundEnabled, soundUri, soundVolume, isAlarmActive
+ -- missionType, missionCount๋ CREATE TABLE์์ ์ ์๋ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ์๋ ์ฑ์์ง
+ )
+ SELECT
+ id,
+ -- hour๋ฅผ 24์๊ฐ ํ์์ผ๋ก ๋ณํํฉ๋๋ค.
+ -- ์์: isAm ์ปฌ๋ผ์ด 0 (PM)์ด๊ณ hour๊ฐ 12๊ฐ ์๋๋ฉด hour + 12
+ -- ์์: isAm ์ปฌ๋ผ์ด 1 (AM)์ด๊ณ hour๊ฐ 12 (์์ )์ด๋ฉด 0์ผ๋ก ๋ณํ
+ -- ์ค์ isAm ์ปฌ๋ผ์ ์๋ฏธ์ ๊ฐ์ ๋ฐ๋ผ ์๋ ๋ก์ง์ ์กฐ์ ํด์ผ ํฉ๋๋ค.
+ CASE
+ WHEN isAm = 0 AND hour != 12 THEN hour + 12 -- ์คํ 1์ ~ 11์ -> 13 ~ 23์
+ WHEN isAm = 1 AND hour = 12 THEN 0 -- ์ค์ 12์ (์์ ) -> 0์
+ ELSE hour -- ๊ทธ ์ธ (์ค์ 1์ ~ 11์, ์คํ 12์(์ ์ค))
+ END AS hour_24,
+ minute,
+ second,
+ repeatDays,
+ isHolidayAlarmOff,
+ isSnoozeEnabled,
+ snoozeInterval,
+ snoozeCount,
+ isVibrationEnabled,
+ isSoundEnabled,
+ soundUri,
+ soundVolume,
+ isAlarmActive
+ FROM $DATABASE_NAME
+ """.trimIndent(),
+ )
+
+ // 3. ๊ธฐ์กด ํ
์ด๋ธ ์ญ์
+ database.execSQL("DROP TABLE $DATABASE_NAME")
+
+ // 4. ์์ ํ
์ด๋ธ์ ์ด๋ฆ์ ๊ธฐ์กด ํ
์ด๋ธ ์ด๋ฆ์ผ๋ก ๋ณ๊ฒฝ
+ database.execSQL("ALTER TABLE ${DATABASE_NAME}_new RENAME TO $DATABASE_NAME")
+
+ // 5. ์ปค๋ฐ
+ database.setTransactionSuccessful()
+ } finally {
+ database.endTransaction()
+ }
+ }
+ }
+}
diff --git a/core/database/src/main/java/com/yapp/database/MissionTypeConverter.kt b/core/database/src/main/java/com/yapp/database/MissionTypeConverter.kt
new file mode 100644
index 00000000..aeb59503
--- /dev/null
+++ b/core/database/src/main/java/com/yapp/database/MissionTypeConverter.kt
@@ -0,0 +1,17 @@
+package com.yapp.database
+
+import androidx.room.TypeConverter
+import com.yapp.domain.model.MissionType
+
+class MissionTypeConverter {
+
+ @TypeConverter
+ fun fromInt(value: Int): MissionType {
+ return MissionType.fromInt(value)
+ }
+
+ @TypeConverter
+ fun toInt(missionType: MissionType): Int {
+ return missionType.value
+ }
+}
diff --git a/data/src/main/java/com/yapp/data/local/di/DatabaseModule.kt b/core/database/src/main/java/com/yapp/database/di/DatabaseModule.kt
similarity index 76%
rename from data/src/main/java/com/yapp/data/local/di/DatabaseModule.kt
rename to core/database/src/main/java/com/yapp/database/di/DatabaseModule.kt
index 80bcd808..7c6339f2 100644
--- a/data/src/main/java/com/yapp/data/local/di/DatabaseModule.kt
+++ b/core/database/src/main/java/com/yapp/database/di/DatabaseModule.kt
@@ -1,9 +1,10 @@
-package com.yapp.data.local.di
+package com.yapp.database.di
import android.content.Context
import androidx.room.Room
-import com.yapp.data.local.AlarmDao
-import com.yapp.data.local.AlarmDatabase
+import com.yapp.database.AlarmDao
+import com.yapp.database.AlarmDatabase
+import com.yapp.database.DatabaseMigrations
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@@ -24,7 +25,9 @@ class DatabaseModule {
context.applicationContext,
AlarmDatabase::class.java,
AlarmDatabase.DATABASE_NAME,
- ).build()
+ )
+ .addMigrations(DatabaseMigrations.MIGRATION_1_2)
+ .build()
}
@Provides
diff --git a/core/database/src/test/java/com/yapp/database/ExampleUnitTest.kt b/core/database/src/test/java/com/yapp/database/ExampleUnitTest.kt
new file mode 100644
index 00000000..47e4e45c
--- /dev/null
+++ b/core/database/src/test/java/com/yapp/database/ExampleUnitTest.kt
@@ -0,0 +1,16 @@
+package com.yapp.database
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt b/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt
index 35298556..c9325870 100644
--- a/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt
+++ b/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt
@@ -1,6 +1,5 @@
package com.yapp.datastore
-import android.util.Log
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
@@ -14,7 +13,6 @@ import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import java.time.LocalDate
-import java.time.format.DateTimeFormatter
import javax.inject.Inject
import javax.inject.Singleton
@@ -26,15 +24,25 @@ class UserPreferences @Inject constructor(
val USER_ID = longPreferencesKey("user_id")
val USER_NAME = stringPreferencesKey("user_name")
val ONBOARDING_COMPLETED = booleanPreferencesKey("onboarding_completed")
+
val FORTUNE_ID = longPreferencesKey("fortune_id")
- val FORTUNE_DATE = stringPreferencesKey("fortune_date")
+ val FORTUNE_DATE_EPOCH = longPreferencesKey("fortune_date_epoch")
val FORTUNE_IMAGE_ID = intPreferencesKey("fortune_image_id")
val FORTUNE_SCORE = intPreferencesKey("fortune_score")
- val FORTUNE_CHECKED = booleanPreferencesKey("fortune_checked")
- val FIRST_DISMISSED_ALARM_ID = longPreferencesKey("first_dismissed_alarm_id")
- val DISMISSED_DATE = stringPreferencesKey("dismissed_date")
+ val FORTUNE_SEEN = booleanPreferencesKey("fortune_seen")
+ val FORTUNE_TOOLTIP_SHOWN = booleanPreferencesKey("fortune_tooltip_shown")
+ val FORTUNE_CREATING = booleanPreferencesKey("fortune_creating")
+ val FORTUNE_FAILED = booleanPreferencesKey("fortune_failed")
+
+ val FIRST_ALARM_DISMISSED_TODAY = booleanPreferencesKey("first_alarm_dismissed_today")
+ val FIRST_ALARM_DISMISSED_DATE_EPOCH = longPreferencesKey("first_alarm_dismissed_date_epoch")
+
+ val UPDATE_NOTICE_DONT_SHOW_VERSION = stringPreferencesKey("update_notice_dont_show_version")
+ val UPDATE_NOTICE_LAST_SHOWN_DATE_EPOCH = longPreferencesKey("update_notice_last_shown_date_epoch")
}
+ private fun todayEpoch(): Long = LocalDate.now().toEpochDay()
+
val userIdFlow: Flow = dataStore.data
.catch { emit(emptyPreferences()) }
.map { it[Keys.USER_ID] }
@@ -55,9 +63,9 @@ class UserPreferences @Inject constructor(
.map { it[Keys.FORTUNE_ID] }
.distinctUntilChanged()
- val fortuneDateFlow: Flow = dataStore.data
+ val fortuneDateEpochFlow: Flow = dataStore.data
.catch { emit(emptyPreferences()) }
- .map { it[Keys.FORTUNE_DATE] }
+ .map { it[Keys.FORTUNE_DATE_EPOCH] }
.distinctUntilChanged()
val fortuneImageIdFlow: Flow = dataStore.data
@@ -70,108 +78,143 @@ class UserPreferences @Inject constructor(
.map { it[Keys.FORTUNE_SCORE] }
.distinctUntilChanged()
- val hasNewFortuneFlow: Flow = dataStore.data
+ val hasUnseenFortuneFlow: Flow = dataStore.data
.catch { emit(emptyPreferences()) }
- .map { preferences ->
- val savedDate = preferences[Keys.FORTUNE_DATE]
- val isChecked = preferences[Keys.FORTUNE_CHECKED] ?: true
- val todayDate = LocalDate.now().format(DateTimeFormatter.ISO_DATE)
- savedDate == todayDate && !isChecked
+ .map { pref ->
+ val isToday = pref[Keys.FORTUNE_DATE_EPOCH] == todayEpoch()
+ isToday && (pref[Keys.FORTUNE_ID] != null) && (pref[Keys.FORTUNE_SEEN] != true)
}
.distinctUntilChanged()
- val firstDismissedAlarmIdFlow: Flow = dataStore.data
+ val shouldShowFortuneToolTipFlow: Flow = dataStore.data
.catch { emit(emptyPreferences()) }
- .map { preferences ->
- val savedDate = preferences[Keys.DISMISSED_DATE]
- val todayDate = LocalDate.now().format(DateTimeFormatter.ISO_DATE)
-
- if (savedDate == todayDate) {
- preferences[Keys.FIRST_DISMISSED_ALARM_ID]
- } else {
- null
- }
+ .map { pref ->
+ val hasTodayFortune = (pref[Keys.FORTUNE_DATE_EPOCH] == todayEpoch()) && (pref[Keys.FORTUNE_ID] != null)
+ val tooltipShown = pref[Keys.FORTUNE_TOOLTIP_SHOWN] ?: false
+ hasTodayFortune && !tooltipShown
}
.distinctUntilChanged()
- suspend fun saveUserId(userId: Long) {
- dataStore.edit { preferences ->
- preferences[Keys.USER_ID] = userId
+ val isFortuneCreatingFlow: Flow = dataStore.data
+ .catch { emit(emptyPreferences()) }
+ .map { it[Keys.FORTUNE_CREATING] ?: false }
+ .distinctUntilChanged()
+
+ val isFortuneFailedFlow: Flow = dataStore.data
+ .catch { emit(emptyPreferences()) }
+ .map { it[Keys.FORTUNE_FAILED] ?: false }
+ .distinctUntilChanged()
+
+ val isFirstAlarmDismissedTodayFlow: Flow = dataStore.data
+ .catch { emit(emptyPreferences()) }
+ .map { pref ->
+ val flag = pref[Keys.FIRST_ALARM_DISMISSED_TODAY] ?: false
+ val isToday = pref[Keys.FIRST_ALARM_DISMISSED_DATE_EPOCH] == todayEpoch()
+ flag && isToday
}
+ .distinctUntilChanged()
+
+ val updateNoticeDontShowVersionFlow: Flow = dataStore.data
+ .catch { emit(emptyPreferences()) }
+ .map { it[Keys.UPDATE_NOTICE_DONT_SHOW_VERSION] }
+ .distinctUntilChanged()
+
+ val updateNoticeLastShownDateEpochFlow: Flow = dataStore.data
+ .catch { emit(emptyPreferences()) }
+ .map { it[Keys.UPDATE_NOTICE_LAST_SHOWN_DATE_EPOCH] }
+ .distinctUntilChanged()
+
+ suspend fun saveUserId(userId: Long) {
+ dataStore.edit { it[Keys.USER_ID] = userId }
}
suspend fun saveUserName(userName: String) {
- dataStore.edit { preferences ->
- preferences[Keys.USER_NAME] = userName
+ dataStore.edit { it[Keys.USER_NAME] = userName }
+ }
+
+ suspend fun setOnboardingCompleted() {
+ dataStore.edit { it[Keys.ONBOARDING_COMPLETED] = true }
+ }
+
+ suspend fun markFortuneCreating() {
+ dataStore.edit { pref ->
+ pref[Keys.FORTUNE_CREATING] = true
+ pref[Keys.FORTUNE_FAILED] = false
}
}
- suspend fun saveFortuneId(fortuneId: Long) {
- val currentDate = LocalDate.now().format(DateTimeFormatter.ISO_DATE)
- dataStore.edit { preferences ->
- preferences[Keys.FORTUNE_ID] = fortuneId
- preferences[Keys.FORTUNE_DATE] = currentDate
- preferences[Keys.FORTUNE_CHECKED] = false
+ suspend fun markFortuneCreated(fortuneId: Long) {
+ dataStore.edit { pref ->
+ val today = todayEpoch()
+ val prevDate = pref[Keys.FORTUNE_DATE_EPOCH]
+ val isNewForToday = (pref[Keys.FORTUNE_ID] != fortuneId) || (prevDate != today)
+
+ pref[Keys.FORTUNE_ID] = fortuneId
+ pref[Keys.FORTUNE_DATE_EPOCH] = today
+ pref[Keys.FORTUNE_CREATING] = false
+ pref[Keys.FORTUNE_FAILED] = false
+
+ if (isNewForToday) {
+ pref[Keys.FORTUNE_SEEN] = false
+ pref[Keys.FORTUNE_TOOLTIP_SHOWN] = false
+ }
}
}
- suspend fun markFortuneAsChecked() {
- dataStore.edit { preferences ->
- preferences[Keys.FORTUNE_CHECKED] = true
+ suspend fun markFortuneFailed() {
+ dataStore.edit { pref ->
+ pref[Keys.FORTUNE_CREATING] = false
+ pref[Keys.FORTUNE_FAILED] = true
}
}
+ suspend fun markFortuneSeen() {
+ dataStore.edit { it[Keys.FORTUNE_SEEN] = true }
+ }
+
+ suspend fun markFortuneTooltipShown() {
+ dataStore.edit { it[Keys.FORTUNE_TOOLTIP_SHOWN] = true }
+ }
+
suspend fun saveFortuneImageId(imageResId: Int) {
- dataStore.edit { preferences ->
- preferences[Keys.FORTUNE_IMAGE_ID] = imageResId
- }
+ dataStore.edit { it[Keys.FORTUNE_IMAGE_ID] = imageResId }
}
suspend fun saveFortuneScore(score: Int) {
- dataStore.edit { preferences ->
- preferences[Keys.FORTUNE_SCORE] = score
- }
+ dataStore.edit { it[Keys.FORTUNE_SCORE] = score }
}
- suspend fun saveFirstDismissedAlarmId(alarmId: Long) {
- dataStore.edit { preferences ->
- val todayDate = LocalDate.now().format(DateTimeFormatter.ISO_DATE)
- if (preferences[Keys.FIRST_DISMISSED_ALARM_ID] == null) {
- preferences[Keys.FIRST_DISMISSED_ALARM_ID] = alarmId
- preferences[Keys.DISMISSED_DATE] = todayDate
- Log.d("UserPreferences", "์ฒซ ํด์ ๋ ์๋ ID ์ ์ฅ ์๋ฃ: $alarmId (๋ ์ง: $todayDate)")
- } else {
- Log.d("UserPreferences", "์ด๋ฏธ ์ฒซ ์๋ ํด์ ID๊ฐ ์ ์ฅ๋์ด ์์)")
- }
+ suspend fun markFirstAlarmDismissedToday() {
+ dataStore.edit { pref ->
+ pref[Keys.FIRST_ALARM_DISMISSED_TODAY] = true
+ pref[Keys.FIRST_ALARM_DISMISSED_DATE_EPOCH] = todayEpoch()
}
}
- suspend fun setOnboardingCompleted() {
- dataStore.edit { preferences ->
- preferences[Keys.ONBOARDING_COMPLETED] = true
- }
+ suspend fun markUpdateNoticeDontShow(version: String) {
+ dataStore.edit { it[Keys.UPDATE_NOTICE_DONT_SHOW_VERSION] = version }
}
- suspend fun clearDismissedAlarmId() {
- dataStore.edit { preferences ->
- preferences.remove(Keys.FIRST_DISMISSED_ALARM_ID)
- preferences.remove(Keys.DISMISSED_DATE)
+ suspend fun markUpdateNoticeShownToday() {
+ dataStore.edit { pref ->
+ pref[Keys.UPDATE_NOTICE_LAST_SHOWN_DATE_EPOCH] = todayEpoch()
}
}
suspend fun clearUserData() {
- dataStore.edit { preferences ->
- preferences.clear()
- }
+ dataStore.edit { it.clear() }
}
- suspend fun clearFortuneId() {
- dataStore.edit { preferences ->
- preferences.remove(Keys.FORTUNE_ID)
- preferences.remove(Keys.FORTUNE_DATE)
- preferences.remove(Keys.FORTUNE_IMAGE_ID)
- preferences.remove(Keys.FORTUNE_SCORE)
- preferences.remove(Keys.FORTUNE_CHECKED)
+ suspend fun clearFortuneData() {
+ dataStore.edit { pref ->
+ pref.remove(Keys.FORTUNE_ID)
+ pref.remove(Keys.FORTUNE_DATE_EPOCH)
+ pref.remove(Keys.FORTUNE_IMAGE_ID)
+ pref.remove(Keys.FORTUNE_SCORE)
+ pref.remove(Keys.FORTUNE_SEEN)
+ pref.remove(Keys.FORTUNE_TOOLTIP_SHOWN)
+ pref.remove(Keys.FORTUNE_CREATING)
+ pref.remove(Keys.FORTUNE_FAILED)
}
}
}
diff --git a/core/datastore/src/main/java/com/yapp/datastore/di/DataStoreModule.kt b/core/datastore/src/main/java/com/yapp/datastore/di/DataStoreModule.kt
index 41447bb8..895621d9 100644
--- a/core/datastore/src/main/java/com/yapp/datastore/di/DataStoreModule.kt
+++ b/core/datastore/src/main/java/com/yapp/datastore/di/DataStoreModule.kt
@@ -2,12 +2,9 @@ package com.yapp.datastore.di
import android.content.Context
import androidx.datastore.core.DataStore
-import androidx.datastore.core.DataStoreFactory
import androidx.datastore.dataStoreFile
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import androidx.datastore.preferences.core.Preferences
-import com.yapp.datastore.token.AuthToken
-import com.yapp.datastore.token.TokenDataSerializer
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@@ -18,18 +15,6 @@ import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object DataStoreModule {
- @Provides
- @Singleton
- fun providesTokenDataStore(
- @ApplicationContext context: Context,
- tokenDataSerializer: TokenDataSerializer,
- ): DataStore =
- DataStoreFactory.create(
- serializer = tokenDataSerializer,
- ) {
- context.dataStoreFile("token.json")
- }
-
@Provides
@Singleton
fun providesPreferencesDataStore(
diff --git a/core/datastore/src/main/java/com/yapp/datastore/token/AuthToken.kt b/core/datastore/src/main/java/com/yapp/datastore/token/AuthToken.kt
deleted file mode 100644
index ec40212a..00000000
--- a/core/datastore/src/main/java/com/yapp/datastore/token/AuthToken.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.yapp.datastore.token
-
-import kotlinx.serialization.Serializable
-
-@Serializable
-data class AuthToken(
- val accessToken: String = "",
- val refreshToken: String = "",
- val isSigned: Boolean = false,
-)
diff --git a/core/datastore/src/main/java/com/yapp/datastore/token/TokenDataSerializer.kt b/core/datastore/src/main/java/com/yapp/datastore/token/TokenDataSerializer.kt
deleted file mode 100644
index ebe7d713..00000000
--- a/core/datastore/src/main/java/com/yapp/datastore/token/TokenDataSerializer.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-package com.yapp.datastore.token
-
-import androidx.datastore.core.Serializer
-import com.yapp.common.security.CryptoManager
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
-import kotlinx.serialization.json.Json
-import java.io.InputStream
-import java.io.OutputStream
-import javax.inject.Inject
-
-class TokenDataSerializer @Inject constructor(
- private val cryptoManager: CryptoManager,
-) : Serializer {
-
- private val securityKeyAlias = "data-store"
-
- override val defaultValue: AuthToken
- get() = AuthToken()
-
- override suspend fun readFrom(input: InputStream): AuthToken {
- val encryptedDataWithIv = input.readBytes()
-
- if (encryptedDataWithIv.size < 12) return defaultValue
-
- val (iv, encryptedData) = encryptedDataWithIv.splitIvAndData()
- return runCatching {
- val decryptedBytes = cryptoManager.decryptData(securityKeyAlias, encryptedData, iv)
- Json.decodeFromString(AuthToken.serializer(), decryptedBytes.decodeToString())
- }.getOrElse {
- it.printStackTrace()
- defaultValue // ๋ณตํธํ ์คํจ ์ defaultValue
- }
- }
-
- override suspend fun writeTo(t: AuthToken, output: OutputStream) {
- val encryptedResult = cryptoManager.encryptData(
- securityKeyAlias,
- Json.encodeToString(AuthToken.serializer(), t),
- )
- withContext(Dispatchers.IO) {
- output.write(encryptedResult.toCombinedByteArray())
- }
- }
-
- private fun ByteArray.splitIvAndData(): Pair {
- val iv = this.copyOfRange(0, 12)
- val encryptedData = this.copyOfRange(12, this.size)
- return iv to encryptedData
- }
-
- private fun Pair.toCombinedByteArray(): ByteArray {
- return second + first
- }
-}
diff --git a/core/datastore/src/main/java/com/yapp/datastore/token/TokenDataStore.kt b/core/datastore/src/main/java/com/yapp/datastore/token/TokenDataStore.kt
deleted file mode 100644
index 75d9ee5d..00000000
--- a/core/datastore/src/main/java/com/yapp/datastore/token/TokenDataStore.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-package com.yapp.datastore.token
-
-import android.util.Log
-import androidx.datastore.core.DataStore
-import java.io.IOException
-import javax.inject.Inject
-
-class TokenDataStore @Inject constructor(
- private val tokenPreferences: DataStore,
-) {
-
- val token = tokenPreferences.data
-
- suspend fun setAuthToken(authToken: AuthToken) {
- updateDataSafely { copy(authToken.accessToken, authToken.refreshToken, authToken.isSigned) }
- }
-
- suspend fun setAutoLogin(isSigned: Boolean) {
- updateDataSafely { copy(isSigned = isSigned) }
- }
-
- suspend fun setAccessToken(accessToken: String) {
- updateDataSafely { copy(accessToken = accessToken) }
- }
-
- suspend fun setRefreshToken(refreshToken: String) {
- updateDataSafely { copy(refreshToken = refreshToken) }
- }
-
- private suspend fun updateDataSafely(transform: AuthToken.() -> AuthToken) {
- runCatching {
- tokenPreferences.updateData { it.transform() }
- }.onFailure { exception ->
- if (exception is IOException) {
- Log.e("TokenDataStore", "๋ฐ์ดํฐ ์
๋ฐ์ดํธ ์คํจ: ${exception.message}")
- }
- }
- }
-}
diff --git a/core/designsystem/src/main/res/drawable-xhdpi/ic_100_buble.png b/core/designsystem/src/main/res/drawable-xhdpi/ic_100_buble.png
deleted file mode 100644
index d30beec6..00000000
Binary files a/core/designsystem/src/main/res/drawable-xhdpi/ic_100_buble.png and /dev/null differ
diff --git a/core/designsystem/src/main/res/drawable-xhdpi/ic_fortune_delivering_speech_bubble.png b/core/designsystem/src/main/res/drawable-xhdpi/ic_fortune_delivering_speech_bubble.png
new file mode 100644
index 00000000..c75d2db3
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xhdpi/ic_fortune_delivering_speech_bubble.png differ
diff --git a/core/designsystem/src/main/res/drawable-xhdpi/ic_fortune_waiting_speech_bubble.png b/core/designsystem/src/main/res/drawable-xhdpi/ic_fortune_waiting_speech_bubble.png
new file mode 100644
index 00000000..a119387d
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xhdpi/ic_fortune_waiting_speech_bubble.png differ
diff --git a/core/designsystem/src/main/res/drawable-xxhdpi/ic_100_buble.png b/core/designsystem/src/main/res/drawable-xxhdpi/ic_100_buble.png
deleted file mode 100644
index 469a7b3f..00000000
Binary files a/core/designsystem/src/main/res/drawable-xxhdpi/ic_100_buble.png and /dev/null differ
diff --git a/core/designsystem/src/main/res/drawable-xxhdpi/ic_fortune_delivering_speech_bubble.png b/core/designsystem/src/main/res/drawable-xxhdpi/ic_fortune_delivering_speech_bubble.png
new file mode 100644
index 00000000..318d11d6
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xxhdpi/ic_fortune_delivering_speech_bubble.png differ
diff --git a/core/designsystem/src/main/res/drawable-xxhdpi/ic_fortune_waiting_speech_bubble.png b/core/designsystem/src/main/res/drawable-xxhdpi/ic_fortune_waiting_speech_bubble.png
new file mode 100644
index 00000000..83b665f4
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xxhdpi/ic_fortune_waiting_speech_bubble.png differ
diff --git a/core/designsystem/src/main/res/drawable/ic_100_buble.png b/core/designsystem/src/main/res/drawable/ic_100_buble.png
deleted file mode 100644
index e69978c4..00000000
Binary files a/core/designsystem/src/main/res/drawable/ic_100_buble.png and /dev/null differ
diff --git a/core/designsystem/src/main/res/drawable/ic_delete.xml b/core/designsystem/src/main/res/drawable/ic_delete.xml
new file mode 100644
index 00000000..d2ed0eb5
--- /dev/null
+++ b/core/designsystem/src/main/res/drawable/ic_delete.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/core/designsystem/src/main/res/drawable/ic_mission_shake.xml b/core/designsystem/src/main/res/drawable/ic_mission_shake.xml
new file mode 100644
index 00000000..210b620a
--- /dev/null
+++ b/core/designsystem/src/main/res/drawable/ic_mission_shake.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
diff --git a/core/designsystem/src/main/res/drawable/ic_mission_tap.xml b/core/designsystem/src/main/res/drawable/ic_mission_tap.xml
new file mode 100644
index 00000000..0e202de6
--- /dev/null
+++ b/core/designsystem/src/main/res/drawable/ic_mission_tap.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
diff --git a/core/designsystem/src/main/res/raw/fortune_loading.json b/core/designsystem/src/main/res/raw/fortune_loading.json
new file mode 100644
index 00000000..cb570812
--- /dev/null
+++ b/core/designsystem/src/main/res/raw/fortune_loading.json
@@ -0,0 +1 @@
+{"nm":"์ปดํฌ์ง์
2","h":500,"w":700,"meta":{"g":"@lottiefiles/toolkit-js 0.66.4","tc":"#202f44"},"layers":[{"ty":2,"nm":"Group 1948760243.png","sr":1,"st":1,"op":45,"ip":0,"ln":"2856","hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[151.5,137]},"s":{"a":0,"k":[61.6,61.6,101.65]},"p":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[130.5,159,0],"t":0},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[167.139,178.955,0],"t":15},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[233.8,193.529,0],"t":31},{"s":[285.485,192.267,0],"t":45}]},"r":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[17.4],"t":0},{"s":[32.4],"t":45}]},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}},"refId":"1","ind":1},{"ty":2,"nm":"Group 1948760248-1.png","sr":1,"st":0,"op":45,"ip":0,"ln":"2855","hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[22,121.064]},"s":{"a":0,"k":[70.27,70.27,89.655]},"p":{"a":0,"k":[386,148,0]},"r":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[-9.304],"t":0},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":45},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[10],"t":48},{"s":[0],"t":51}]},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}},"refId":"2","ind":2},{"ty":2,"nm":"Vector 27856.png","sr":1,"st":0,"op":45,"ip":0,"ln":"2854","hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[93.94,9.052]},"s":{"a":0,"k":[75.628,72.572,100]},"p":{"a":0,"k":[310.5,242.5,0]},"r":{"a":0,"k":2},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}},"refId":"3","ind":3},{"ty":2,"nm":"Group 1948760248.png","sr":1,"st":0,"op":45,"ip":0,"ln":"2853","hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[132.5,232]},"s":{"a":0,"k":[76.4,76.4,76.4]},"p":{"a":0,"k":[370,256,0]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}},"refId":"4","ind":4}],"v":"5.7.0","fr":30,"op":45,"ip":0,"assets":[{"id":"1","e":1,"w":303,"h":274,"p":"data:image/webp;base64,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","u":""},{"id":"2","e":1,"w":100,"h":148,"p":"data:image/webp;base64,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","u":""},{"id":"3","e":1,"w":154,"h":124,"p":"data:image/webp;base64,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","u":""},{"id":"4","e":1,"w":265,"h":464,"p":"data:image/webp;base64,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","u":""}]}
\ No newline at end of file
diff --git a/core/designsystem/src/main/res/raw/mission_shake.json b/core/designsystem/src/main/res/raw/mission_shake.json
new file mode 100644
index 00000000..a9598279
--- /dev/null
+++ b/core/designsystem/src/main/res/raw/mission_shake.json
@@ -0,0 +1 @@
+{"assets":[{"id":"el-5276-8N90","layers":[{"ddd":0,"ind":2,"ty":4,"nm":"Layer 1","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[22.791,16.319]},"o":{"a":0,"k":100},"p":{"a":0,"k":[22.791,16.319]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Path 1 (10) Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1 (10)","d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[-0.02,-0.005],[-0.154,-0.262],[-0.131,-0.066],[0.066,-0.569],[0.215,-0.288],[0.257,-0.923],[0.124,-0.17],[0.037,-0.455],[0.442,-0.937],[0.053,-0.047],[0.091,-0.015],[0.276,-0.05],[0.207,0.214],[-0.121,0.311],[0.046,0.294],[-0.169,0.421],[0.107,0.289],[0.72,0.185],[0.156,0.1],[0.117,0.252],[-0.111,0.274],[-0.303,-0.078],[-0.275,0.131],[-0.875,-0.205],[-0.129,0.269],[-0.144,0.246],[-0.006,0.18],[-0.654,0.033]],"o":[[0.147,-0.023],[0.175,0.025],[0.077,0.125],[0.18,0.087],[-0.041,0.554],[-0.124,0.17],[-0.252,0.903],[-0.143,0.165],[-0.069,0.587],[-0.087,0.18],[-0.083,0.04],[-0.147,0.023],[-0.341,0.073],[-0.184,-0.229],[0.097,-0.218],[-0.045,-0.293],[0.38,-0.932],[-0.083,-0.304],[-0.435,-0.112],[-0.137,-0.096],[-0.178,-0.409],[0.11,-0.275],[0.076,0.02],[0.4,-0.14],[0.289,0.053],[0.048,-0.109],[0.143,-0.245],[0.046,-0.493],[0,0]],"v":[[20.493,5.13],[20.743,5.103],[21.237,5.533],[21.556,5.826],[21.727,6.809],[21.342,8.072],[20.77,9.711],[20.206,11.321],[19.936,12.251],[19.169,14.536],[18.96,14.876],[18.697,14.959],[18.063,15.069],[17.24,14.858],[17.145,14.047],[17.221,13.279],[17.406,12.207],[17.816,10.375],[16.611,9.642],[15.725,9.324],[15.344,8.802],[15.244,7.778],[15.864,7.483],[16.391,7.316],[18.303,7.413],[18.931,7.09],[19.219,6.558],[19.443,5.92],[20.493,5.13]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (10)","d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[-0.292,-0.197],[-0.089,-0.205],[0.848,-0.63],[0.315,-0.363],[-0.079,-0.242],[-0.744,-0.716],[-0.149,-0.281],[-0.19,-0.049],[-0.079,-0.323],[0.125,-0.33],[0.257,-0.055],[0.233,-0.121],[0.353,0.433],[0.229,0.223],[0.15,0.276],[0.215,0.255],[0.008,0.123],[0.217,0.257],[0.156,0.1],[0.156,-0.061],[0.105,-0.124],[0.291,-0.187],[0.21,-0.188],[0.408,-0.177],[0.151,0.038],[0.202,0.314],[-0.043,0.171],[-0.258,0.296],[-0.205,0.088],[-0.263,0.235],[-0.523,0.309],[-0.339,0.295],[-0.166,0.018],[-0.163,0.321],[-0.391,0.263],[-0.147,0.023]],"o":[[0.185,-0.013],[0.312,0.2],[0.205,0.537],[-0.3,0.226],[-0.372,0.429],[0.08,0.242],[0.193,0.19],[0.15,0.28],[0.264,0.068],[0.078,0.323],[-0.107,0.337],[-0.147,0.023],[-0.413,0.197],[-0.218,-0.235],[-0.224,-0.221],[-0.145,-0.3],[-0.217,-0.258],[0.006,-0.1],[-0.217,-0.257],[-0.085,-0.063],[-0.145,0.072],[-0.123,0.17],[-0.29,0.188],[-0.229,0.184],[-0.404,0.158],[-0.171,-0.044],[-0.197,-0.333],[0.044,-0.171],[0.281,-0.312],[0.247,-0.098],[0.262,-0.236],[1.285,-0.76],[0.257,-0.217],[0.227,-0.022],[0.053,-0.128],[0.39,-0.263],[0,0]],"v":[[12.18,3.087],[12.896,3.362],[13.497,3.97],[12.533,5.72],[11.61,6.603],[11.17,7.61],[12.406,9.047],[12.92,9.754],[13.43,10.248],[13.945,10.834],[13.875,11.814],[13.33,12.402],[12.76,12.618],[11.61,12.263],[10.94,11.576],[10.375,10.826],[9.833,9.991],[9.495,9.42],[9.179,8.884],[8.62,8.348],[8.258,8.345],[7.878,8.642],[7.257,9.178],[6.507,9.742],[5.551,10.284],[4.718,10.464],[4.159,9.927],[3.929,9.171],[4.382,8.471],[5.111,7.871],[5.875,7.371],[7.053,6.553],[9.488,4.97],[10.123,4.618],[10.709,4.103],[11.374,3.517],[12.18,3.088]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (10)","d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[-0.965,-0.489],[-0.241,-0.243],[-0.185,-0.068],[-0.094,-0.024],[-0.267,-0.533],[0.002,-0.323],[-0.028,-0.129],[0.168,-0.579],[0.395,-0.282],[0.147,-0.252],[0.417,0.107],[0.266,-0.094],[0.209,-0.027],[0.044,-0.009],[0.763,0.175],[0.299,0.017],[0.26,0.167],[0.128,-0.028],[0.391,0.281],[0.203,0.234],[0.176,0.192],[0.125,0.537],[0.096,0.273],[-0.127,0.572],[-0.053,0.127],[-0.158,0.222],[-0.221,0.388],[-0.171,0.117],[-0.294,0.045],[-0.744,0.07],[-0.155,-0.02],[-0.601,-0.175],[-0.351,-0.051]],"o":[[0.605,0.075],[0.212,0.116],[0.24,0.243],[0.18,0.087],[0.213,0.115],[0.271,0.513],[-0.007,0.26],[0.147,0.585],[-0.17,0.582],[-0.24,0.167],[-0.296,0.449],[-0.189,-0.049],[-0.304,0.083],[-0.269,0.032],[-0.497,0.053],[-0.738,-0.189],[-0.309,0.002],[-0.221,-0.158],[-0.11,0.032],[-0.396,-0.263],[-0.168,-0.199],[-0.442,-0.477],[-0.042,-0.286],[-0.178,-0.409],[0.107,-0.498],[0.058,-0.147],[0.191,-0.273],[0.183,-0.316],[0.177,-0.137],[0.043,-0.01],[0.418,-0.055],[0.176,0.024],[0.809,0.227],[0,0]],"v":[[12.072,14.713],[14.427,15.559],[15.106,16.097],[15.743,16.563],[16.154,16.729],[16.874,17.701],[17.278,18.955],[17.31,19.538],[17.278,21.315],[16.43,22.611],[15.843,23.247],[14.773,23.76],[14.091,23.827],[13.321,23.992],[12.851,24.053],[10.961,23.871],[9.406,23.562],[8.533,23.308],[8.009,23.113],[7.257,22.739],[6.359,21.993],[5.843,21.407],[4.993,19.887],[4.785,19.047],[4.709,17.575],[4.949,16.638],[5.273,16.085],[5.89,15.093],[6.42,14.443],[7.126,14.17],[8.307,14.05],[9.167,13.998],[10.332,14.297],[12.072,14.713]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (10)","d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0.207,0.092],[0.459,0.017],[0.346,0.078],[0.348,-0.093],[0.049,-0.189],[0.205,-0.169],[0.001,-0.241],[-0.281,-0.477],[-0.094,-0.186],[-0.047,-0.053],[-0.246,-0.063],[-0.265,-0.149],[-0.763,-0.095],[-0.418,0.216],[-0.157,0.061],[-0.121,0.393],[0.169,0.287],[-0.034,0.133],[0.561,0.406],[0.488,-0.016]],"o":[[-0.226,-0.018],[-0.217,-0.096],[-0.354,-0.021],[-0.353,-0.074],[-0.342,0.073],[-0.024,0.095],[-0.276,0.212],[-0.001,0.241],[0.234,0.423],[0.093,0.185],[0.071,0.038],[0.34,0.087],[0.581,0.331],[0.787,0.081],[0.305,-0.163],[0.271,-0.111],[0.14,-0.387],[-0.141,-0.237],[0.077,-0.303],[-0.543,-0.402],[0,0]],"v":[[12.297,17.373],[11.643,17.206],[10.628,17.036],[9.576,16.887],[8.51,16.916],[7.924,17.31],[7.58,17.706],[7.164,18.386],[7.584,19.463],[8.076,20.376],[8.286,20.733],[8.762,20.885],[9.67,21.239],[11.685,21.877],[13.492,21.675],[14.184,21.338],[14.772,20.581],[14.729,19.571],[14.569,19.016],[13.843,17.952],[12.297,17.373]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (10)","d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[-0.52,-0.255],[-0.293,-0.116],[-0.193,-0.19],[-0.075,-0.02],[-0.233,-0.503],[0.063,-0.327],[-0.016,-0.219],[0.133,-0.753],[0.126,-0.411],[0.435,0.113],[0.247,-0.098],[0.208,0.135],[-0.092,0.359],[0.008,0.285],[-0.138,0.221],[-0.025,0.256],[-0.03,0.293],[3.451,-0.002],[0.328,0.077],[0.854,-0.023],[0.373,0.197],[-0.111,0.275],[0.051,0.187],[-0.395,0.363],[-0.322,-0.082],[-0.37,0.026],[-0.076,-0.02],[-0.174,-0.105],[-0.26,-0.007],[-0.453,-0.197],[-0.179,-0.006],[-0.749,-0.151],[-0.043,0.009]],"o":[[0.076,-0.061],[0.269,0.129],[0.217,0.097],[0.17,0.205],[0.285,0.073],[0.239,0.485],[-0.047,0.215],[0.013,0.185],[-0.113,0.758],[-0.185,0.72],[-0.113,-0.029],[-0.399,0.14],[-0.203,-0.153],[0.059,-0.227],[-0.014,-0.26],[0.125,-0.25],[0.059,-0.289],[0.2,-1.724],[-0.337,0.007],[-0.606,-0.155],[-0.644,-0.004],[-0.354,-0.192],[0.085,-0.174],[-0.055,-0.337],[0.396,-0.362],[0.132,0.034],[0.265,-0.013],[0.095,0.025],[0.26,0.167],[0.28,0.012],[0.46,0.179],[0.185,-0.013],[0.728,0.147],[0,0]],"v":[[33.33,7.608],[34.224,7.898],[35.068,8.266],[35.684,8.696],[36.051,9.033],[36.828,9.898],[37.091,11.115],[37.044,11.769],[36.864,13.176],[36.505,14.93],[35.575,15.841],[35.034,15.945],[34.124,15.953],[33.958,15.184],[34.034,14.417],[34.224,13.678],[34.449,12.919],[34.582,12.045],[29.706,9.462],[28.705,9.356],[26.515,9.158],[24.989,8.857],[24.624,8.157],[24.677,7.597],[25.188,6.547],[26.265,6.127],[27.018,6.139],[27.53,6.149],[27.934,6.344],[28.714,6.604],[29.814,6.917],[30.773,7.194],[32.173,7.401],[33.33,7.608]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (10)","d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[-0.264,-0.229],[-0.227,-0.139],[0.438,-1.159],[-0.032,-0.109],[-0.293,-0.035],[-1.335,-0.383],[-0.753,-0.133],[-0.26,-0.168],[-0.246,-0.063],[-0.047,-0.133],[0.073,-0.284],[0.134,-0.128],[0.298,0.016],[0.351,-0.031],[0.094,0.024],[0.138,0.081],[0.928,0.237],[0.714,0.203],[0.09,-0.037],[0.866,0.242],[0.455,0.036],[0.213,0.042],[0.302,0.159],[0.247,0.063],[0.109,0.129],[0.064,0.299],[-0.029,0.113],[-0.148,0.184],[-0.166,0.017],[-0.856,-0.2],[-0.223,0.003],[-0.159,0.302],[-0.125,0.169],[-0.021,0.237],[-0.475,0.121]],"o":[[0.323,-0.078],[0.147,0.139],[0.471,0.283],[-0.149,0.426],[0.033,0.11],[0.298,0.016],[0.36,0.092],[0.747,0.151],[0.222,0.158],[0.227,0.058],[0.071,0.119],[-0.087,0.34],[-0.129,0.108],[-0.124,0.009],[-0.228,0.022],[-0.15,-0.053],[-0.283,-0.154],[-1.633,-0.399],[-0.412,-0.126],[-0.205,0.089],[-0.644,-0.165],[-0.217,-0.007],[-0.114,-0.029],[-0.392,-0.201],[-0.227,-0.058],[-0.089,-0.124],[-0.065,-0.3],[0.034,-0.132],[0.248,-0.26],[0.167,-0.018],[1.141,0.273],[0.228,-0.023],[0.114,-0.213],[0.129,-0.188],[0.045,-0.412],[0,0]],"v":[[27.945,12.551],[28.825,12.777],[29.385,13.193],[29.435,15.355],[29.259,16.157],[29.749,16.374],[32.198,16.972],[33.867,17.31],[35.378,17.788],[36.08,18.12],[36.49,18.407],[36.487,19.011],[36.155,19.713],[35.515,19.851],[34.803,19.911],[34.32,19.908],[33.887,19.706],[32.07,19.119],[28.55,18.215],[27.797,18.082],[26.191,17.852],[24.543,17.55],[23.897,17.475],[23.273,17.193],[22.315,16.796],[21.812,16.516],[21.582,15.882],[21.529,15.262],[21.802,14.788],[22.423,14.372],[23.957,14.645],[26.002,15.049],[26.582,14.562],[26.94,13.988],[27.165,13.35],[27.945,12.551]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (10)","d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[-0.515,-0.273],[-0.303,-0.078],[-0.707,-0.706],[-0.274,-0.11],[-0.08,-0.081],[-0.146,-0.219],[-0.151,-0.12],[0.017,-0.54],[-0.061,-0.157],[0.17,-0.582],[0.433,-0.353],[0.117,-0.176],[0.568,0.146],[0.493,-0.116],[0.356,0.03],[0.611,0.137],[0.413,-0.035],[0.076,0.02],[0.107,0.076],[0.245,0.043],[0.391,0.282],[0.179,0.247],[0.207,0.19],[0.189,0.755],[-0.103,0.478],[0.013,0.104],[-0.749,0.796],[-0.233,0.122],[-0.337,-0.026],[-0.413,0.196],[-0.413,-0.046]],"o":[[0.581,0.048],[0.288,0.135],[0.909,0.233],[0.24,0.243],[0.218,0.097],[0.085,0.062],[0.098,0.167],[0.283,0.233],[-0.012,0.28],[0.243,0.466],[-0.13,0.543],[-0.164,0.134],[-0.325,0.482],[-0.151,-0.04],[-0.493,0.115],[-0.341,-0.007],[-0.624,-0.16],[-0.308,0.022],[-0.124,-0.043],[-0.21,-0.133],[-0.256,-0.025],[-0.373,-0.277],[-0.144,-0.241],[-0.306,-0.301],[-0.185,-0.774],[0.092,-0.36],[-0.055,-0.257],[0.368,-0.41],[0.233,-0.122],[0.398,0.021],[0.28,-0.151],[0,0]],"v":[[26.162,19.025],[27.823,19.511],[28.71,19.83],[31.133,21.239],[31.904,21.769],[32.351,22.036],[32.697,22.457],[33.071,22.887],[33.469,24.047],[33.543,24.702],[33.653,26.274],[32.785,27.655],[32.362,28.122],[31.022,28.626],[30.055,28.74],[28.781,28.868],[27.353,28.652],[25.797,28.465],[25.221,28.468],[24.874,28.288],[24.186,28.021],[23.215,27.56],[22.388,26.773],[21.858,26.123],[21.115,24.539],[20.992,22.661],[21.11,21.965],[22.15,20.386],[23.052,19.588],[23.906,19.444],[25.123,19.182],[26.163,19.025]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (10)","d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0.147,-0.023],[0.549,0.061],[0.257,-0.055],[0.053,-0.208],[0.295,-0.207],[-0.018,-0.165],[-0.417,-0.893],[-0.1,-0.086],[-0.303,-0.078],[-0.349,-0.211],[-0.914,-0.053],[-0.509,0.253],[-0.107,0.337],[0.173,0.347],[0.048,0.25],[0.327,0.225],[0.515,0.111],[0.213,0.116]],"o":[[-0.264,-0.148],[-0.123,0.009],[-0.63,-0.06],[-0.232,0.041],[-0.025,0.095],[-0.29,0.188],[0.037,0.172],[0.153,0.342],[0.099,0.086],[0.284,0.073],[0.482,0.325],[0.92,0.034],[0.371,-0.187],[0.111,-0.355],[-0.133,-0.217],[-0.04,-0.313],[-0.302,-0.24],[-0.554,-0.121],[0,0]],"v":[[27.693,22.141],[27.076,21.953],[26.067,21.875],[24.737,21.867],[24.309,22.241],[23.829,22.693],[23.421,23.223],[24.101,24.821],[24.481,25.463],[25.083,25.709],[26.033,26.134],[28.127,26.702],[30.27,26.374],[30.986,25.589],[30.893,24.536],[30.62,23.831],[30.07,23.024],[28.844,22.497],[27.694,22.141]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (10)","d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[-0.259,-0.167],[-0.315,-0.424],[0.331,-1.287],[0.221,-0.387],[0.031,-0.355],[0.126,-0.617],[-0.004,-0.222],[0.162,-0.24],[0.078,-0.279],[0.057,-0.066],[0.233,-0.121],[0.143,-0.004],[0.274,0.191],[0.054,0.417],[-0.239,0.928],[0,0],[0,0],[-0.246,0.724],[0.041,0.151],[-0.23,0.425],[0.051,0.194],[-0.211,0.35],[-0.129,0.27],[-0.26,-0.007]],"o":[[0.37,-0.026],[0.265,0.149],[0.094,0.105],[-0.325,1.268],[-0.11,0.194],[-0.053,0.627],[-0.097,0.379],[-0.012,0.28],[-0.157,0.244],[-0.044,0.17],[-0.058,0.067],[-0.304,0.164],[-0.118,-0.01],[-0.383,-0.24],[-0.05,-0.436],[0,0],[0,0],[0.064,-0.327],[0.169,-0.582],[-0.05,-0.276],[0.172,-0.279],[-0.069,-0.36],[0.096,-0.137],[0.255,-0.52],[0,0]],"v":[[39.82,11.362],[40.764,11.574],[41.634,12.433],[41.279,14.521],[40.46,17.004],[40.249,17.828],[39.981,19.696],[39.841,20.598],[39.58,21.378],[39.226,22.166],[39.075,22.52],[38.639,22.802],[37.969,23.054],[37.381,22.752],[36.726,21.766],[37.009,19.72],[37.242,18.812],[37.388,18.244],[37.853,16.668],[38.045,15.568],[38.315,14.517],[38.497,13.807],[38.709,12.742],[39.047,12.132],[39.82,11.362]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (10)","d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[-0.094,-0.024],[-0.345,-0.23],[-0.033,-0.19],[0.116,-0.374],[0.252,-0.117],[0.544,0.161],[0.103,0.147],[-0.027,0.417],[-0.313,0.121],[-0.257,0.136]],"o":[[0.253,-0.117],[0.119,0.01],[0.273,0.191],[0.055,0.175],[-0.174,0.52],[-0.252,0.117],[-0.43,-0.131],[-0.09,-0.124],[0.041,-0.555],[0.152,-0.041],[0,0]],"v":[[36.224,26.904],[36.744,26.765],[37.439,27.125],[37.898,27.697],[37.807,28.521],[37.168,29.477],[35.974,29.412],[35.174,28.995],[35.079,28.184],[35.611,27.17],[36.224,26.904]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[1,1,1]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-5253-8N90","layers":[{"ddd":0,"ind":5,"ty":4,"nm":"Layer 1","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[12.737,11.607]},"o":{"a":0,"k":100},"p":{"a":0,"k":[12.737,11.607]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Path 1 (3) Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1 (3)","d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[-0.057,-0.015],[-0.212,-0.116],[-0.253,-0.428],[-0.008,-0.124],[0.145,-0.326],[0.079,-0.545],[-0.021,-0.308],[0.169,-0.421],[-0.013,-0.185],[0.244,-0.321],[0.213,-0.753],[0.093,-0.689],[0.096,-0.217],[0.105,-0.359],[0.237,-0.405],[0.025,-0.094],[-0.009,-0.204],[0.063,-0.166],[0.258,-0.297],[0.138,-0.065],[0.245,0.063],[0.179,0.167],[-0.048,0.189],[0.079,0.323],[-0.048,0.109],[-0.165,0.483],[-0.068,0.265],[-0.207,0.411],[-0.026,0.255],[-0.203,0.634],[-0.072,0.122],[-0.002,0.322],[-0.193,0.677],[0.004,0.143],[0.203,0.073],[0.157,0.485],[-0.211,0.269],[-0.414,0.277],[-0.351,0.111]],"o":[[0.418,-0.135],[0.062,-0.005],[0.208,0.134],[0.207,0.375],[0.009,0.123],[-0.125,0.25],[-0.073,0.525],[0.027,0.29],[-0.168,0.42],[0.036,0.332],[-0.124,0.169],[-0.176,0.673],[-0.055,0.37],[-0.154,0.34],[-0.162,0.44],[-0.153,0.283],[-0.019,0.076],[0.017,0.327],[-0.057,0.147],[-0.262,0.316],[-0.115,0.052],[-0.304,-0.078],[-0.155,-0.181],[0.015,-0.057],[-0.078,-0.322],[0.095,-0.137],[0.164,-0.482],[0.365,-1.582],[0.133,-0.208],[0.005,-0.18],[0.203,-0.635],[0.063,-0.085],[0.002,-0.323],[0.156,-0.605],[-0.004,-0.142],[-0.327,-0.144],[-0.135,-0.498],[0.181,-0.237],[0.433,-0.273],[0,0]],"v":[[6.798,1.742],[7.51,1.562],[7.921,1.728],[8.613,2.571],[8.935,3.32],[8.732,3.994],[8.426,5.187],[8.347,6.437],[8.134,7.503],[7.901,8.411],[7.589,9.391],[7.083,10.774],[6.679,12.819],[6.453,13.699],[6.063,14.749],[5.464,16.019],[5.197,16.585],[5.181,17.005],[5.111,17.744],[4.638,18.409],[4.038,18.981],[3.498,18.964],[2.774,18.597],[2.614,18.041],[2.518,17.471],[2.473,16.824],[2.863,15.895],[3.211,14.775],[4.069,11.786],[4.308,11.091],[4.621,9.87],[5.033,8.735],[5.13,8.124],[5.423,6.625],[5.651,5.503],[5.341,5.181],[4.614,4.238],[4.728,3.088],[5.621,2.318],[6.798,1.742]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (3)","d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[-0.321,-0.243],[-0.696,-0.199],[-0.147,-0.057],[-0.236,-0.183],[-0.151,-0.12],[-0.319,-0.485],[-0.051,-0.195],[-0.11,-0.593],[0.01,-0.118],[-0.089,-0.139],[-0.07,-0.118],[-0.069,-0.522],[0.107,-0.174],[-0.083,-0.304],[0.072,-0.123],[0.18,-0.619],[0.196,-0.213],[0.102,-0.226],[0.339,-0.377],[0.106,-0.219],[0.148,-0.104],[0.628,-0.323],[0.341,0.087],[0.404,-0.078],[0.185,-0.094],[0.969,0.309],[0.631,0.687],[0.249,0.133],[0.367,0.376],[-0.043,0.17],[0.201,0.396],[-0.012,0.361],[0.088,0.366],[-0.034,0.328],[0.047,0.261],[-0.076,0.209],[0.021,0.228],[-0.13,0.192],[0.011,0.35],[-0.345,0.637],[-0.243,0.159],[-0.2,0.231],[-0.367,0.329],[-0.167,0.098],[-0.249,0.259],[-0.559,0.287],[-0.341,-0.087],[-0.11,0.033]],"o":[[0.251,-0.036],[0.217,0.177],[0.492,0.127],[0.147,0.057],[0.387,0.3],[0.189,0.13],[0.325,0.467],[0.018,0.167],[0.123,0.617],[0.012,0.165],[0.146,0.22],[0.14,0.238],[0.073,0.503],[-0.23,0.425],[0.037,0.171],[-0.071,0.123],[-0.16,0.625],[-0.164,0.187],[-0.198,0.466],[-0.174,0.17],[-0.1,0.155],[-0.124,0.089],[-0.718,0.36],[-0.113,-0.029],[-0.384,0.083],[-0.6,0.29],[-0.95,-0.305],[-0.181,-0.217],[-0.36,-0.173],[-0.343,-0.391],[0.01,-0.037],[-0.281,-0.555],[0.001,-0.161],[-0.067,-0.323],[0.04,-0.262],[-0.045,-0.218],[0.1,-0.236],[-0.022,-0.23],[0.196,-0.291],[-0.017,-0.247],[0.369,-0.652],[0.1,-0.075],[0.225,-0.245],[0.387,-0.325],[0.161,-0.08],[0.45,-0.438],[0.552,-0.262],[0.34,0.088],[0,0]],"v":[[15.941,3.877],[16.799,4.188],[18.168,4.751],[19.126,5.027],[19.7,5.387],[20.506,6.017],[21.268,6.939],[21.831,7.931],[22.023,9.07],[22.193,10.173],[22.347,10.636],[22.671,11.143],[22.984,12.283],[22.934,13.299],[22.714,14.393],[22.662,14.833],[22.285,15.947],[21.751,17.203],[21.349,17.826],[20.538,19.1],[20.115,19.688],[19.743,20.077],[18.615,20.695],[17.027,21.105],[16.251,21.178],[15.397,21.443],[13.043,21.414],[10.671,19.927],[10.02,19.397],[8.93,18.573],[8.48,17.731],[8.193,17.081],[7.789,15.707],[7.659,14.917],[7.609,13.935],[7.599,13.146],[7.646,12.492],[7.764,11.796],[7.931,11.143],[8.215,10.157],[8.707,8.83],[9.625,7.613],[10.075,7.153],[10.962,6.292],[11.792,5.657],[12.406,5.149],[13.927,4.056],[15.266,3.794],[15.941,3.877]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (3)","d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0.91,-0.008],[0.529,-0.41],[0.35,-0.078],[0.083,-0.322],[0.191,-0.273],[0.22,-0.575],[-0.011,-0.427],[0.075,-0.607],[-0.182,-0.47],[-0.343,-0.391],[-0.104,-0.067],[-0.397,-0.102],[-0.283,-0.153],[-0.602,-0.013],[-0.418,0.215],[-0.261,0.075],[-0.086,0.099],[-0.059,0.227],[-0.239,0.222],[-0.101,0.236],[-0.136,0.254],[-0.005,0.1],[-0.145,0.407],[0.195,0.575],[-0.001,0.208],[0.409,0.69],[0.235,0.424],[0.227,0.139],[0.17,0.124]],"o":[[-0.486,-0.387],[-0.91,0.008],[-0.284,0.218],[-0.47,0.101],[-0.033,0.133],[-0.357,0.502],[-0.217,0.61],[0.001,0.706],[-0.045,0.331],[0.187,0.452],[0.466,0.543],[0.123,0.071],[0.479,0.103],[0.439,0.254],[0.602,0.013],[0.252,-0.117],[0.218,-0.065],[0.11,-0.113],[0.058,-0.227],[0.238,-0.221],[0.099,-0.27],[0.153,-0.283],[0.015,-0.056],[0.293,-0.832],[-0.078,-0.192],[-0.031,-0.351],[-0.225,-0.38],[-0.163,-0.305],[-0.203,-0.153],[0,0]],"v":[[17.788,7.529],[15.694,6.961],[13.535,7.588],[12.573,8.037],[11.743,8.671],[11.406,9.281],[10.536,10.903],[10.226,12.459],[10.115,14.428],[10.321,15.63],[11.116,16.894],[11.971,17.81],[12.751,18.07],[13.894,18.454],[15.456,18.855],[16.986,18.552],[17.756,18.265],[18.212,18.019],[18.465,17.509],[18.91,16.836],[19.419,16.15],[19.772,15.363],[20.01,14.788],[20.25,14.093],[20.398,11.983],[20.281,11.377],[19.622,9.816],[18.932,8.61],[18.347,7.945],[17.787,7.529]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[1,1,1]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-5242-8N90","layers":[{"ddd":0,"ind":8,"ty":4,"nm":"Layer 1","hd":true,"sr":1,"ks":{"a":{"a":0,"k":[9.566,11.387]},"o":{"a":0,"k":100},"p":{"a":0,"k":[9.566,11.387]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":true,"nm":"Path 1 (2) Group","bm":0,"it":[{"ty":"sh","hd":true,"nm":"Path 1 (2)","d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[-0.545,-0.624],[0.003,-0.483],[0.186,-0.255],[-0.016,-0.327],[0.195,-0.839],[0.25,-0.501],[0.022,-0.245],[-0.061,-0.157],[-0.98,-0.271],[-0.508,-0.381],[0.068,-0.265],[0.285,-0.249],[0.109,-0.033],[0.332,0.045],[0.132,0.114],[0.445,0.073],[0.219,-0.065],[0.051,-0.43],[0.126,-0.411],[-0.041,-0.231],[0.029,-0.114],[0.162,-0.241],[0.063,-0.244],[0.291,-0.269],[0.171,-0.037],[0.213,0.035],[0.157,0.084],[-0.057,0.692],[-0.363,0.471],[-0.136,0.611],[0.007,0.204],[0.595,0.274],[0.217,0.122],[0.526,0.074],[0.458,0.178],[0.123,-0.009],[0.387,0.22],[0.095,0.104],[0.093,0.265],[0.088,0.181],[-0.185,0.638],[-0.273,0.434],[-0.11,0.113],[-0.242,0.079],[-0.32,0.383],[-0.223,0.085],[-0.257,0.217],[-0.247,0.098],[-0.086,0.099],[-0.485,0.157],[-0.167,0.099],[-0.138,0.147],[-0.181,0.075],[-0.138,0.065],[-0.36,-0.013]],"o":[[0.853,-0.023],[0.184,0.229],[-0.003,0.483],[-0.32,0.462],[0.017,0.407],[-0.195,0.837],[-0.098,0.225],[-0.007,0.26],[0.065,0.138],[0.607,0.186],[0.472,0.363],[-0.02,0.076],[-0.267,0.253],[-0.237,0.06],[-0.327,-0.064],[-0.184,-0.149],[-0.445,-0.074],[-0.441,0.15],[0.009,0.204],[-0.097,0.38],[0.051,0.195],[-0.025,0.094],[-0.152,0.201],[-0.039,0.151],[-0.267,0.254],[-0.086,0.018],[-0.173,-0.04],[-0.279,-0.172],[0.08,-0.706],[0.091,-0.118],[0.136,-0.611],[-0.008,-0.284],[-0.231,-0.094],[-0.175,-0.106],[-0.489,-0.045],[-0.44,-0.173],[-0.1,-0.006],[-0.382,-0.24],[-0.08,-0.082],[-0.08,-0.185],[-0.094,-0.185],[0.203,-0.635],[0.292,-0.429],[0.129,-0.108],[0.371,-0.107],[0.119,-0.15],[0.247,-0.098],[0.252,-0.197],[0.228,-0.103],[0.11,-0.113],[0.465,-0.163],[0.185,-0.093],[0.125,-0.169],[0.223,-0.084],[0.204,-0.09],[0,0]],"v":[[12.21,2.568],[14.308,3.47],[14.579,4.538],[14.295,5.646],[13.839,6.83],[13.572,8.699],[12.905,10.707],[12.723,11.417],[12.804,12.043],[14.372,12.657],[16.058,13.514],[16.664,14.456],[16.206,14.944],[15.642,15.374],[14.788,15.397],[14.1,15.13],[13.157,14.797],[12.161,14.783],[11.423,15.653],[11.247,16.576],[11.163,17.493],[11.196,17.956],[10.916,18.459],[10.591,19.132],[10.096,19.762],[9.44,20.199],[8.992,20.174],[8.495,19.986],[8.162,18.69],[8.827,16.924],[9.168,15.831],[9.361,14.609],[8.456,13.772],[7.783,13.448],[6.732,13.178],[5.305,12.842],[4.46,12.595],[3.73,12.256],[3.015,11.74],[2.755,11.219],[2.503,10.669],[2.639,9.434],[3.352,7.831],[3.955,7.017],[4.511,6.737],[5.547,6.003],[6.061,5.651],[6.818,5.179],[7.567,4.736],[8.038,4.433],[8.93,4.027],[9.878,3.634],[10.363,3.274],[10.821,2.908],[11.363,2.684],[12.209,2.568]]}}},{"ty":"sh","hd":true,"nm":"Path 1 (2)","d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0.461,-0.144],[0.281,-0.069],[0.162,-0.159],[0.229,-0.086],[0.144,-0.327],[0.271,-0.112],[0.119,-0.151],[0.181,-0.076],[0.145,-0.407],[-0.184,-0.148],[-0.862,-0.181],[-0.378,-0.258],[-0.093,-0.025],[-0.294,0.045],[-0.081,0.16],[-0.243,0.946],[-0.158,0.221],[0.095,0.73]],"o":[[-0.017,-0.247],[-0.27,0.112],[-0.28,0.07],[-0.176,0.169],[-0.351,0.111],[-0.067,0.104],[-0.295,0.126],[-0.135,0.143],[-0.399,0.14],[-0.121,0.392],[0.207,0.134],[0.289,0.054],[0.34,0.249],[0.115,0.029],[0.337,-0.055],[0.106,-0.175],[0.277,-1.08],[0.244,-0.321],[0,0]],"v":[[11.555,5.821],[10.838,5.667],[10.011,5.939],[9.348,6.283],[8.734,6.67],[7.991,7.327],[7.484,7.651],[6.863,8.066],[6.384,8.398],[5.568,9.218],[5.663,10.028],[7.267,10.501],[8.267,10.969],[8.917,11.379],[9.53,11.354],[10.157,11.031],[10.68,9.349],[11.332,7.398],[11.555,5.821]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[1,1,1]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-5200-8N90","layers":[{"ddd":0,"ind":11,"ty":4,"nm":"Layer 1","hd":true,"sr":1,"ks":{"a":{"a":0,"k":[8.354,10.301]},"o":{"a":0,"k":100},"p":{"a":0,"k":[8.354,10.301]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":true,"nm":"Path 1 Group","bm":0,"it":[{"ty":"sh","hd":true,"nm":"Path 1","d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[-0.557,-0.264],[-0.311,-0.282],[-0.278,-0.253],[-0.201,-0.475],[-0.003,-0.304],[-0.033,-0.19],[0.025,-0.175],[-0.035,-0.332],[0.049,-0.109],[0.188,-0.416],[0.12,-0.151],[0.215,-0.127],[0.082,-0.241],[-0.207,-0.214],[-0.042,-0.232],[-0.111,-0.271],[0.035,-0.213],[-0.028,-0.251],[0.165,-0.563],[0.259,-0.378],[0.269,-0.269],[0.077,0.019],[0.347,-0.173],[0.209,-0.027],[0.197,-0.06],[0.292,0.029],[0.503,0.008],[0.239,0.122],[0.412,0.132],[0.358,0.253],[0.263,0.471],[0.235,0.343],[0.055,0.257],[-0.229,0.264],[-0.09,0.038],[-0.331,-0.046],[0,0],[0,0],[0,0],[-0.203,-0.072],[-0.368,-0.215],[-0.251,-0.044],[-0.537,0.125],[-0.113,-0.029],[-0.26,1.406],[0.188,0.29],[0.95,0.385],[0.216,0.173],[0.18,0.131],[-0.097,0.46],[-0.399,0.14],[-0.113,0.051],[-0.317,-0.021],[-0.514,0.433],[-0.051,0.673],[0.493,0.591],[0.74,-0.053],[0.294,0.008],[0.367,-0.249],[0.234,1.211],[-0.267,0.416],[-0.275,0.051],[-0.209,0.107],[-0.193,-0.03],[-0.369,0.025],[-0.171,-0.044],[-0.118,-0.01]],"o":[[0.323,0.002],[0.577,0.269],[0.08,0.081],[0.236,0.182],[0.22,0.48],[-0.007,0.18],[0.036,0.171],[-0.005,0.18],[0.018,0.247],[-0.029,0.113],[-0.273,0.596],[-0.1,0.156],[-0.328,0.178],[-0.063,0.246],[0.141,0.158],[0.023,0.228],[0.093,0.267],[-0.036,0.25],[0.055,0.417],[-0.16,0.544],[-0.222,0.31],[-0.295,0.287],[-0.019,-0.005],[-0.342,0.155],[-0.205,0.018],[-0.282,0.082],[-0.123,0.009],[-0.268,0.012],[-0.396,-0.176],[-0.672,-0.213],[-0.335,-0.267],[-0.201,-0.313],[-0.385,-0.543],[-0.037,-0.251],[0.21,-0.269],[0.11,-0.033],[0,0],[0,0],[0,0],[0.094,0.105],[0.27,0.13],[0.401,0.245],[0.255,0.025],[0.323,-0.079],[0.625,0.16],[0.157,-0.928],[-0.164,-0.305],[-0.255,-0.108],[-0.173,-0.141],[-0.307,-0.22],[0.103,-0.477],[0.248,-0.098],[0.138,-0.066],[0.773,0.057],[0.539,-0.447],[0.056,-0.691],[-0.4,-0.487],[-0.294,0.018],[-0.18,-0.006],[-1.223,0.836],[-0.05,-0.275],[0.273,-0.433],[0.176,-0.056],[0.233,-0.122],[0.2,0.01],[0.413,-0.036],[0.232,0.04],[0,0]],"v":[[10.524,1.566],[11.844,1.965],[13.176,2.791],[13.713,3.292],[14.368,4.277],[14.702,5.453],[14.742,6.008],[14.759,6.527],[14.804,7.295],[14.758,7.828],[14.433,8.622],[13.843,9.742],[13.371,10.166],[12.756,10.795],[12.972,11.485],[13.246,12.07],[13.447,12.818],[13.535,13.537],[13.523,14.291],[13.358,15.761],[12.73,17.144],[11.992,18.014],[11.435,18.416],[10.886,18.668],[10.06,18.94],[9.455,19.057],[8.587,19.137],[7.648,19.139],[6.874,18.97],[5.661,18.507],[4.116,17.808],[3.219,16.7],[2.564,15.715],[1.904,14.516],[2.193,13.743],[2.643,13.283],[3.305,13.303],[3.915,13.398],[4.513,14.49],[5.083,15.453],[5.529,15.719],[6.486,16.237],[7.465,16.67],[8.653,16.521],[9.307,16.447],[10.635,14.578],[10.589,12.751],[8.918,11.716],[8.209,11.292],[7.679,10.884],[7.365,9.864],[8.117,8.938],[8.659,8.714],[9.342,8.647],[11.272,8.083],[12.158,6.403],[11.502,4.48],[9.792,3.83],[8.91,3.845],[8.09,4.21],[5.904,3.648],[6.23,2.612],[7.052,1.885],[7.63,1.64],[8.27,1.502],[9.124,1.479],[9.999,1.491],[10.524,1.566]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[1,1,1]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-4715-8N90","layers":[{"ddd":0,"ind":14,"ty":4,"nm":"Layer 1","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[151.899,164.177]},"o":{"a":0,"k":100},"p":{"a":0,"k":[151.899,164.177]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Path 1 Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1","d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[-0.644,-0.871],[-0.212,-0.196],[-0.084,-0.223],[-0.103,-0.228],[0.083,-0.483],[-0.012,-0.346],[0.13,-0.269],[0.144,-0.326],[0.167,-0.18],[0.481,-0.3],[0.124,-0.169],[0.43,-0.013],[0.538,-0.447],[0.313,-0.057],[0.128,-0.109],[0.49,-0.257],[0.347,-0.093],[-0.012,-0.347],[-0.602,-0.013],[-0.345,-0.149],[-0.194,-0.029],[-0.26,-0.169],[-0.426,-0.069],[-0.175,-0.105],[-0.417,-0.026],[-0.299,0.065],[-0.089,-0.043],[-0.302,-0.239],[-0.108,-0.048],[0.056,-0.531],[0.233,-0.202],[0.465,-0.002],[0.265,0.148],[0.479,-0.038],[0.213,0.115],[0.209,0.06],[0.154,0.133],[0.099,0.005],[0.507,-0.01],[0.663,0.17],[0.499,0.174],[0.259,0.329],[0.193,0.08],[0.075,0.1],[0.041,0.231],[-0.054,0.369],[-0.11,0.274],[-0.076,-0.02],[-0.152,0.123],[-0.076,0.142],[-0.152,0.042],[-0.357,0.211],[-0.176,0.056],[-0.186,0.174],[-0.171,0.037],[-0.162,0.16],[-0.18,-0.006],[-0.21,0.187],[0,0],[-0.224,0.165],[-0.171,0.036],[-0.367,0.249],[-0.233,0.122],[-0.121,0.392],[0.087,0.527],[0.061,0.076],[0.231,0.2],[1.917,-1.182],[0.204,-0.008],[0.254,0.267],[-0.011,0.28],[-0.297,0.448],[-0.334,0.278],[-0.209,0.027],[-0.427,0.173],[-0.123,0.009],[-0.293,-0.116],[-0.104,0.014],[-0.25,-0.125],[-0.294,0.046]],"o":[[0.869,-0.16],[0.334,0.428],[0.226,0.22],[0.117,0.252],[0.21,0.437],[-0.03,0.275],[0.036,0.332],[-0.13,0.269],[-0.11,0.273],[-0.163,0.16],[-0.367,0.25],[-0.255,0.346],[-0.456,0.044],[-0.25,0.196],[-0.299,0.064],[-0.191,0.193],[-0.49,0.257],[-0.826,0.231],[-0.007,0.26],[0.36,0.012],[0.331,0.125],[0.199,0.01],[0.265,0.148],[0.44,0.093],[0.218,0.097],[0.436,0.031],[0.323,-0.078],[0.014,0.024],[0.213,0.196],[0.171,0.124],[-0.051,0.512],[-0.157,0.141],[-0.465,0.001],[-0.412,-0.248],[-0.332,0.036],[-0.192,-0.103],[-0.196,-0.056],[-0.147,-0.139],[-0.095,-0.023],[-0.18,-0.005],[-0.513,-0.126],[-0.511,-0.213],[-0.148,-0.147],[-0.198,-0.091],[-0.071,-0.12],[-0.051,-0.195],[0.055,-0.37],[0.111,-0.275],[0.076,0.02],[0.158,-0.141],[0.111,-0.193],[0.156,-0.06],[0.314,-0.202],[0.152,-0.042],[0.181,-0.155],[0.152,-0.042],[0.162,-0.16],[0.161,0],[0,0],[0.119,-0.07],[0.229,-0.184],[0.731,-0.176],[0.3,-0.226],[0.323,-0.159],[0.14,-0.388],[-0.056,-0.257],[-0.037,-0.091],[-1.698,-1.404],[-0.429,0.253],[-0.18,-0.005],[-0.269,-0.291],[0.011,-0.28],[0.186,-0.254],[0.353,-0.273],[0.152,-0.042],[0.566,-0.238],[0.128,-0.028],[0.321,0.163],[0.1,0.005],[0.255,0.106],[0,0]],"v":[[172.281,77.704],[174.55,78.771],[175.369,79.707],[175.834,80.372],[176.164,81.092],[176.354,82.472],[176.327,83.404],[176.186,84.306],[175.775,85.199],[175.359,85.879],[174.394,86.569],[173.658,87.198],[172.574,87.768],[171.083,88.504],[170.227,88.89],[169.586,89.15],[168.565,89.825],[167.31,90.351],[166.089,91.218],[166.982,91.628],[168.04,91.87],[168.828,92.102],[169.516,92.37],[170.552,92.696],[171.474,92.993],[172.426,93.177],[173.528,93.127],[174.147,93.074],[174.621,93.468],[175.102,93.834],[175.274,94.816],[174.848,95.887],[173.915,96.102],[172.821,95.882],[171.451,95.56],[170.634,95.441],[170.031,95.196],[169.5,94.908],[169.132,94.692],[168.229,94.672],[166.965,94.409],[165.446,93.959],[164.292,93.147],[163.775,92.803],[163.365,92.516],[163.197,91.989],[163.202,91.143],[163.45,90.177],[163.73,89.795],[164.072,89.641],[164.423,89.216],[164.817,88.863],[165.587,88.456],[166.322,88.069],[166.829,87.745],[167.357,87.457],[167.828,87.154],[168.341,86.923],[168.898,86.642],[169.355,86.275],[169.869,85.923],[170.469,85.593],[172.115,84.956],[172.915,84.434],[173.581,83.607],[173.661,82.235],[173.486,81.736],[173.084,81.3],[167.661,80.967],[166.712,81.359],[166.061,80.95],[165.675,80.094],[166.137,79.002],[166.917,78.204],[167.759,77.754],[168.628,77.432],[169.662,77.062],[170.294,77.194],[170.932,77.418],[171.457,77.614],[172.281,77.704]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[1,1,1]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":1,"k":[{"t":0,"s":[100],"i":{"x":0.88,"y":0.77},"o":{"x":0.5,"y":0}},{"t":33.3,"s":[0],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]},{"ddd":0,"ind":16,"ty":4,"nm":"Layer 3","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[191.642,235.5]},"o":{"a":0,"k":100},"p":{"a":0,"k":[191.642,235.5]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":20,"ty":0,"nm":"Mask Group","td":1,"parent":16,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[100000,100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":200000,"refId":"el-4801-8N90-mask","w":200000},{"ddd":0,"ind":24,"ty":0,"nm":"Mask Group","tt":1,"tp":20,"parent":16,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[100000,100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":200000,"refId":"el-4801-8N90-masked","w":200000},{"ddd":0,"ind":25,"ty":4,"nm":"แแ
ฅแซแแ
ฆ","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Layer 4 Group","bm":0,"it":[{"ty":"tr","nm":"Transform","a":{"a":0,"k":[191.642,235.5]},"o":{"a":0,"k":100},"p":{"a":0,"k":[191.642,235.5]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]},{"ddd":0,"ind":27,"ty":4,"nm":"Layer 4","parent":25,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[191.642,235.5]},"o":{"a":0,"k":100},"p":{"a":0,"k":[191.642,235.5]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":31,"ty":0,"nm":"Mask Group","td":1,"parent":27,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[100000,100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":200000,"refId":"el-4797-8N90-mask","w":200000},{"ddd":0,"ind":35,"ty":0,"nm":"Mask Group","tt":1,"tp":31,"parent":27,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[100000,100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":200000,"refId":"el-4797-8N90-masked","w":200000},{"ddd":0,"ind":36,"ty":4,"nm":"แแ
ฅแซแแ
ฆ","parent":25,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Layer 6 Group","bm":0,"it":[{"ty":"tr","nm":"Transform","a":{"a":0,"k":[143.486,189.189]},"o":{"a":0,"k":60},"p":{"a":0,"k":[143.486,189.189]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]},{"ddd":0,"ind":38,"ty":4,"nm":"Layer 6","parent":25,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[143.486,189.189]},"o":{"a":0,"k":60},"p":{"a":0,"k":[143.486,189.189]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":42,"ty":0,"nm":"Mask Group","td":1,"parent":38,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[100000,100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":200000,"refId":"el-4789-8N90-mask","w":200000},{"ddd":0,"ind":46,"ty":0,"nm":"Mask Group","tt":1,"tp":42,"parent":38,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[100000,100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":200000,"refId":"el-4789-8N90-masked","w":200000},{"ddd":0,"ind":47,"ty":4,"nm":"แแ
ฅแซแแ
ฆ","parent":25,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Layer 7 Group","bm":0,"it":[{"ty":"tr","nm":"Transform","a":{"a":0,"k":[143.486,189.189]},"o":{"a":0,"k":60},"p":{"a":0,"k":[143.486,189.189]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]},{"ddd":0,"ind":49,"ty":4,"nm":"Layer 7","parent":25,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[143.486,189.189]},"o":{"a":0,"k":60},"p":{"a":0,"k":[143.486,189.189]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":53,"ty":0,"nm":"Mask Group","td":1,"parent":49,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[100000,100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":200000,"refId":"el-4785-8N90-mask","w":200000},{"ddd":0,"ind":57,"ty":0,"nm":"Mask Group","tt":1,"tp":53,"parent":49,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[100000,100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":200000,"refId":"el-4785-8N90-masked","w":200000},{"ddd":0,"ind":58,"ty":4,"nm":"แแ
ฅแซแแ
ฆ","parent":25,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Layer 8 Group","bm":0,"it":[{"ty":"tr","nm":"Transform","a":{"a":0,"k":[143.486,189.189]},"o":{"a":0,"k":60},"p":{"a":0,"k":[143.486,189.189]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]},{"ddd":0,"ind":60,"ty":4,"nm":"Layer 8","parent":25,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[143.486,189.189]},"o":{"a":0,"k":60},"p":{"a":0,"k":[143.486,189.189]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":64,"ty":0,"nm":"Mask Group","td":1,"parent":60,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[100000,100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":200000,"refId":"el-4781-8N90-mask","w":200000},{"ddd":0,"ind":68,"ty":0,"nm":"Mask Group","tt":1,"tp":64,"parent":60,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[100000,100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":200000,"refId":"el-4781-8N90-masked","w":200000},{"ddd":0,"ind":69,"ty":4,"nm":"แแ
ฅแซแแ
ฆ","parent":25,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Layer 9 Group","bm":0,"it":[{"ty":"tr","nm":"Transform","a":{"a":0,"k":[143.486,189.189]},"o":{"a":0,"k":60},"p":{"a":0,"k":[143.486,189.189]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]},{"ddd":0,"ind":71,"ty":4,"nm":"Layer 9","parent":25,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[143.486,189.189]},"o":{"a":0,"k":60},"p":{"a":0,"k":[143.486,189.189]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":75,"ty":0,"nm":"Mask Group","td":1,"parent":71,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[100000,100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":200000,"refId":"el-4777-8N90-mask","w":200000},{"ddd":0,"ind":79,"ty":0,"nm":"Mask Group","tt":1,"tp":75,"parent":71,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[100000,100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":200000,"refId":"el-4777-8N90-masked","w":200000},{"ddd":0,"ind":80,"ty":4,"nm":"แแ
ฅแซแแ
ฆ","parent":25,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Layer 10 Group","bm":0,"it":[{"ty":"tr","nm":"Transform","a":{"a":0,"k":[143.486,189.189]},"o":{"a":0,"k":60},"p":{"a":0,"k":[143.486,189.189]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]},{"ddd":0,"ind":82,"ty":4,"nm":"Layer 10","parent":25,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[143.486,189.189]},"o":{"a":0,"k":60},"p":{"a":0,"k":[143.486,189.189]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":86,"ty":0,"nm":"Mask Group","td":1,"parent":82,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[100000,100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":200000,"refId":"el-4773-8N90-mask","w":200000},{"ddd":0,"ind":90,"ty":0,"nm":"Mask Group","tt":1,"tp":86,"parent":82,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[100000,100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":200000,"refId":"el-4773-8N90-masked","w":200000},{"ddd":0,"ind":91,"ty":4,"nm":"แแ
ฅแซแแ
ฆ","parent":25,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Layer 11 Group","bm":0,"it":[{"ty":"tr","nm":"Transform","a":{"a":0,"k":[143.486,189.189]},"o":{"a":0,"k":60},"p":{"a":0,"k":[143.486,189.189]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]},{"ddd":0,"ind":93,"ty":4,"nm":"Layer 11","parent":25,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[143.486,189.189]},"o":{"a":0,"k":60},"p":{"a":0,"k":[143.486,189.189]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":97,"ty":0,"nm":"Mask Group","td":1,"parent":93,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[100000,100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":200000,"refId":"el-4769-8N90-mask","w":200000},{"ddd":0,"ind":101,"ty":0,"nm":"Mask Group","tt":1,"tp":97,"parent":93,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[100000,100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":200000,"refId":"el-4769-8N90-masked","w":200000},{"ddd":0,"ind":102,"ty":4,"nm":"แแ
ฅแซแแ
ฆ","parent":25,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Layer 12 Group","bm":0,"it":[{"ty":"tr","nm":"Transform","a":{"a":0,"k":[143.486,189.189]},"o":{"a":0,"k":60},"p":{"a":0,"k":[143.486,189.189]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]},{"ddd":0,"ind":104,"ty":4,"nm":"Layer 12","parent":25,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[143.486,189.189]},"o":{"a":0,"k":60},"p":{"a":0,"k":[143.486,189.189]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":108,"ty":0,"nm":"Mask Group","td":1,"parent":104,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[100000,100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":200000,"refId":"el-4765-8N90-mask","w":200000},{"ddd":0,"ind":112,"ty":0,"nm":"Mask Group","tt":1,"tp":108,"parent":104,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[100000,100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":200000,"refId":"el-4765-8N90-masked","w":200000},{"ddd":0,"ind":113,"ty":4,"nm":"แแ
ฅแซแแ
ฆ","parent":25,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Layer 13 Group","bm":0,"it":[{"ty":"tr","nm":"Transform","a":{"a":0,"k":[143.486,189.189]},"o":{"a":0,"k":60},"p":{"a":0,"k":[143.486,189.189]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]},{"ddd":0,"ind":115,"ty":4,"nm":"Layer 13","parent":25,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[143.486,189.189]},"o":{"a":0,"k":60},"p":{"a":0,"k":[143.486,189.189]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":119,"ty":0,"nm":"Mask Group","td":1,"parent":115,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[100000,100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":200000,"refId":"el-4761-8N90-mask","w":200000},{"ddd":0,"ind":123,"ty":0,"nm":"Mask Group","tt":1,"tp":119,"parent":115,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[100000,100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":200000,"refId":"el-4761-8N90-masked","w":200000},{"ddd":0,"ind":124,"ty":4,"nm":"แแ
ฅแซแแ
ฆ","parent":25,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Layer 14 Group","bm":0,"it":[{"ty":"tr","nm":"Transform","a":{"a":0,"k":[143.486,189.189]},"o":{"a":0,"k":60},"p":{"a":0,"k":[143.486,189.189]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]},{"ddd":0,"ind":126,"ty":4,"nm":"Layer 14","parent":25,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[143.486,189.189]},"o":{"a":0,"k":60},"p":{"a":0,"k":[143.486,189.189]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":130,"ty":0,"nm":"Mask Group","td":1,"parent":126,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[100000,100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":200000,"refId":"el-4757-8N90-mask","w":200000},{"ddd":0,"ind":134,"ty":0,"nm":"Mask Group","tt":1,"tp":130,"parent":126,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[100000,100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":200000,"refId":"el-4757-8N90-masked","w":200000},{"ddd":0,"ind":135,"ty":4,"nm":"แแ
ฅแซแแ
ฆ","parent":25,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Layer 15 Group","bm":0,"it":[{"ty":"tr","nm":"Transform","a":{"a":0,"k":[143.486,189.189]},"o":{"a":0,"k":60},"p":{"a":0,"k":[143.486,189.189]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]},{"ddd":0,"ind":137,"ty":4,"nm":"Layer 15","parent":25,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[143.486,189.189]},"o":{"a":0,"k":60},"p":{"a":0,"k":[143.486,189.189]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":141,"ty":0,"nm":"Mask Group","td":1,"parent":137,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[100000,100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":200000,"refId":"el-4753-8N90-mask","w":200000},{"ddd":0,"ind":145,"ty":0,"nm":"Mask Group","tt":1,"tp":141,"parent":137,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[100000,100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":200000,"refId":"el-4753-8N90-masked","w":200000},{"ddd":0,"ind":146,"ty":4,"nm":"แแ
ฅแซแแ
ฆ","parent":25,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Layer 16 Group","bm":0,"it":[{"ty":"tr","nm":"Transform","a":{"a":0,"k":[143.486,189.189]},"o":{"a":0,"k":60},"p":{"a":0,"k":[143.486,189.189]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]},{"ddd":0,"ind":148,"ty":4,"nm":"Layer 16","parent":25,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[143.486,189.189]},"o":{"a":0,"k":60},"p":{"a":0,"k":[143.486,189.189]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":152,"ty":0,"nm":"Mask Group","td":1,"parent":148,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[100000,100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":200000,"refId":"el-4749-8N90-mask","w":200000},{"ddd":0,"ind":156,"ty":0,"nm":"Mask Group","tt":1,"tp":152,"parent":148,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[100000,100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":200000,"refId":"el-4749-8N90-masked","w":200000},{"ddd":0,"ind":157,"ty":4,"nm":"แแ
ฅแซแแ
ฆ","parent":25,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":162,"ty":0,"nm":"Mask Group","td":1,"parent":25,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[100000,100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":200000,"refId":"el-4725-8N90-mask","w":200000},{"ddd":0,"ind":185,"ty":0,"nm":"Mask Group","tt":1,"tp":162,"parent":25,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[100000,100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":200000,"refId":"el-4725-8N90-masked","w":200000},{"ddd":0,"ind":186,"ty":4,"nm":"แแ
ฅแซแแ
ฆ","parent":25,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Layer 19 Group","bm":0,"it":[{"ty":"tr","nm":"Transform","a":{"a":0,"k":[191.642,235.5]},"o":{"a":0,"k":60},"p":{"a":0,"k":[191.642,235.5]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]},{"ddd":0,"ind":188,"ty":4,"nm":"Layer 19","parent":25,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[191.642,235.5]},"o":{"a":0,"k":60},"p":{"a":0,"k":[191.642,235.5]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":192,"ty":0,"nm":"Mask Group","td":1,"parent":188,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[100000,100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":200000,"refId":"el-4722-8N90-mask","w":200000},{"ddd":0,"ind":196,"ty":0,"nm":"Mask Group","tt":1,"tp":192,"parent":188,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[100000,100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":200000,"refId":"el-4722-8N90-masked","w":200000},{"ddd":0,"ind":197,"ty":4,"nm":"แแ
ฅแซแแ
ฆ","parent":25,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Layer 20 Group","bm":0,"it":[{"ty":"tr","nm":"Transform","a":{"a":0,"k":[191.642,235.5]},"o":{"a":0,"k":100},"p":{"a":0,"k":[191.642,235.5]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]},{"ddd":0,"ind":199,"ty":4,"nm":"Layer 20","parent":25,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[191.642,235.5]},"o":{"a":0,"k":100},"p":{"a":0,"k":[191.642,235.5]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":203,"ty":0,"nm":"Mask Group","td":1,"parent":199,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[100000,100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":200000,"refId":"el-4717-8N90-mask","w":200000},{"ddd":0,"ind":208,"ty":0,"nm":"Mask Group","tt":1,"tp":203,"parent":199,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[100000,100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":200000,"refId":"el-4717-8N90-masked","w":200000}]},{"id":"el-4801-8N90-mask","layers":[{"ddd":0,"ind":17,"ty":4,"nm":"center","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[-100000,-100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":18,"ty":4,"nm":"Mask Group","parent":17,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Mask Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Mask","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[11.142,0],[372.142,0],[372.142,471],[11.142,471]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.85,0.85,0.85]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-4801-8N90-masked","layers":[{"ddd":0,"ind":21,"ty":4,"nm":"center","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[-100000,-100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":22,"ty":4,"nm":"Mask Group","parent":21,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Path 1 Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1","d":1,"ks":{"a":1,"k":[{"t":0,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[143.004,54.134],[141.025,53.052],[138.731,53.179],[136.775,52.007],[134.515,52.004],[132.492,51.091],[130.282,50.891],[128.228,50.088],[126.149,49.364],[124.845,48.459],[124.318,46.782],[125.153,45.519],[125.83,43.983],[127.509,44.066],[129.573,44.833],[131.902,44.551],[134.005,45.167],[136.013,46.153],[138.275,46.133],[140.155,47.618],[142.45,47.484],[144.543,48.139],[145.549,49.416],[146.06,50.886],[145.851,52.309],[144.554,53.323]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":53.1,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[225.279,73.984],[223.3,72.902],[221.006,73.029],[219.05,71.857],[216.79,71.854],[132.492,51.091],[130.282,50.891],[128.228,50.088],[126.149,49.364],[124.845,48.459],[124.318,46.782],[125.153,45.519],[125.83,43.983],[127.509,44.066],[129.573,44.833],[131.902,44.551],[134.005,45.167],[218.288,66.003],[220.55,65.983],[222.43,67.468],[224.725,67.334],[226.818,67.989],[227.824,69.266],[228.335,70.736],[228.126,72.159],[226.829,73.173]]}],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[1,0.969,0.6]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-4797-8N90-mask","layers":[{"ddd":0,"ind":28,"ty":4,"nm":"center","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[-100000,-100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":29,"ty":4,"nm":"Mask Group","parent":28,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Mask Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Mask","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[11.142,0],[372.142,0],[372.142,471],[11.142,471]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.85,0.85,0.85]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-4797-8N90-masked","layers":[{"ddd":0,"ind":32,"ty":4,"nm":"center","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[-100000,-100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":33,"ty":4,"nm":"Mask Group","parent":32,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Path 1 Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[1.045,0.323],[0,0],[0,0],[0,0],[0,0],[0.807,0.706],[0,0],[0.011,0.004],[-0.074,0.02],[0,0],[-0.144,-0.026],[0,0],[0,0],[0,0],[0,0],[-0.275,-0.41],[0,0],[0.535,-0.036],[0.071,0.018],[0.173,-0.151]],"o":[[-0.821,0.722],[0,0],[0,0],[0,0],[0,0],[-1.014,-0.35],[0,0],[-0.009,-0.007],[-0.074,-0.019],[0,0],[0.141,-0.037],[0,0],[0,0],[0,0],[0,0],[0.486,0.086],[0,0],[0.299,0.446],[-0.074,0.005],[-0.223,-0.057],[0,0]],"v":[[226.746,73.751],[223.743,74.393],[201.093,67.353],[176.287,61.641],[151.268,56.741],[128.658,48.968],[125.901,47.369],[124.434,46.082],[124.404,46.066],[124.403,45.922],[127.8,45.022],[128.232,45.005],[153.114,49.549],[177.555,56.687],[201.909,64.174],[226.282,68.478],[227.473,69.254],[229.1,71.684],[228.567,72.767],[228.348,72.747],[227.715,72.898]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[1,1,1]},"r":1,"o":{"a":0,"k":10}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-4789-8N90-mask","layers":[{"ddd":0,"ind":39,"ty":4,"nm":"center","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[-100000,-100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":40,"ty":4,"nm":"Mask Group","parent":39,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Mask Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Mask","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[113.144,114.238],[197.071,128.633],[173.828,264.141],[89.902,249.746]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.85,0.85,0.85]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-4789-8N90-masked","layers":[{"ddd":0,"ind":43,"ty":4,"nm":"center","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[-100000,-100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":44,"ty":4,"nm":"Mask Group","parent":43,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Path 1 (2) Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1 (2)","d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[126.766,251.395],[129.038,251.603],[131.121,250.607],[133.641,248.124],[135.411,251.146],[137.062,252.556],[139.007,253.649],[141.458,253.758],[143.852,254.199],[146.272,254.742],[146.271,253.836],[144.161,253.308],[141.906,252.96],[139.704,252.306],[137.85,251.278],[136.169,249.535],[133.993,247.848],[137.237,245.576],[138.115,247.782],[139.061,249.602],[139.717,248.443],[138.937,245.154],[138.123,243.408],[137.047,241.838],[135.844,240.427],[135.521,239.408],[135.096,239.634],[134.816,239.38],[134.154,240.181],[132.338,240.795],[130.76,241.954],[129.592,243.528],[128.311,246.572],[127.96,247.533],[129.557,246.329],[130.791,244.847],[132.896,247.721],[130.203,248.757],[127.621,250.325],[127.862,250.242]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (2)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[131.116,244.563],[132.904,243.341],[134.62,242.415],[136.554,243.472],[137.249,245.648],[135.556,246.608],[133.806,247.152],[132.353,246.039],[131.115,244.564]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.573,0.702,0.859]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-4785-8N90-mask","layers":[{"ddd":0,"ind":50,"ty":4,"nm":"center","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[-100000,-100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":51,"ty":4,"nm":"Mask Group","parent":50,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Mask Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Mask","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[113.144,114.238],[197.071,128.633],[173.828,264.141],[89.902,249.746]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.85,0.85,0.85]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-4785-8N90-masked","layers":[{"ddd":0,"ind":54,"ty":4,"nm":"center","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[-100000,-100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":55,"ty":4,"nm":"Mask Group","parent":54,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Path 1 (2) Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1 (2)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[138.981,253.8],[137.035,252.599],[135.117,251.356],[133.529,248.774],[130.979,250.405],[129.074,251.76],[126.685,251.87],[126.636,251.35],[126.363,250.895],[127.267,250.018],[127.589,250.19],[129.925,248.556],[132.615,247.634],[130.697,245.436],[129.309,246.935],[128.177,248.044],[127.958,248.067],[127.639,247.981],[127.943,246.454],[129.382,243.382],[130.879,242.081],[132.387,240.871],[133.399,240.163],[133.93,239.806],[134.687,239.106],[134.831,239.22],[134.946,239.025],[135.246,238.755],[135.221,238.901],[135.316,239.2],[135.719,239.055],[135.563,239.369],[136.045,240.26],[136.755,240.481],[137.698,241.368],[138.45,243.251],[139.52,244.989],[139.469,248.445],[139.259,249.882],[139.04,249.825],[138.962,249.712],[137.901,248.536],[137.097,245.942],[135.068,248.081],[136.461,249.5],[137.889,251.242],[139.687,252.398],[141.98,252.523],[144.274,252.641],[146.42,253.624],[146.994,253.715],[146.205,254.635],[146.253,254.594],[143.827,254.339],[141.342,254.468],[138.979,253.801]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (2)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[133.845,246.932],[137.09,245.786],[135.884,243.842],[134.643,242.275],[132.741,243.077],[131.638,244.479],[133.844,246.933]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.573,0.702,0.859]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-4781-8N90-mask","layers":[{"ddd":0,"ind":61,"ty":4,"nm":"center","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[-100000,-100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":62,"ty":4,"nm":"Mask Group","parent":61,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Mask Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Mask","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[113.144,114.238],[197.071,128.633],[173.828,264.141],[89.902,249.746]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.85,0.85,0.85]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-4781-8N90-masked","layers":[{"ddd":0,"ind":65,"ty":4,"nm":"center","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[-100000,-100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":66,"ty":4,"nm":"Mask Group","parent":65,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Path 1 (3) Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1 (3)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[167.788,256.147],[168.887,254.332],[169.252,251.468],[170.166,248.69],[170.843,245.868],[170.623,245.636],[171.221,243.153],[171.473,240.61],[172.16,238.141],[172.008,235.529],[172.473,233.022],[173.524,230.617],[173.578,228.04],[174.441,225.601],[174.142,222.963],[174.708,220.474],[175.147,217.963],[175.499,215.436],[176.11,212.961],[176.901,210.518],[176.861,207.931],[177.51,205.463],[177.699,202.917],[178.2,200.424],[178.883,197.962],[179.185,195.458],[178.601,195.267],[178.474,195.528],[178.365,195.093],[178.282,195.393],[178.232,195.622],[178.114,195.25],[178.128,195.277],[177.617,197.768],[177.519,200.331],[176.864,202.797],[176.364,205.29],[176.387,207.873],[175.414,210.286],[175.394,212.861],[174.677,215.318],[174.75,217.889],[173.803,220.285],[173.583,222.806],[173.497,225.35],[173.009,227.824],[172.216,230.247],[172.204,232.803],[171.531,235.246],[171.019,237.717],[170.227,240.139],[169.879,242.637],[169.79,245.179],[169.063,247.183],[168.025,249.688],[166.65,252.132],[164.176,253.456],[161.922,255.148],[159.101,255.135],[156.415,255.152],[154.014,254.693],[151.529,254.677],[149.093,254.391],[146.714,253.794],[143.963,253.47],[142.62,253.136],[141.332,253.453],[141.12,253.351],[143.826,254.215],[146.544,254.781],[149.05,254.803],[151.407,255.723],[153.887,255.897],[156.294,256.518],[158.767,256.744],[161.257,256.873],[163.695,257.339],[166.714,257.535],[168.444,258.144],[168.404,258.207],[167.788,256.144]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (3)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[169.065,250.414],[168.377,254.146],[168.165,254.837],[165.739,253.84],[168.215,251.709]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (3)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[163.939,256.884],[160.614,256.237],[162.905,255.036],[165.108,254.274],[164.791,256.517],[163.94,256.885]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.573,0.702,0.859]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-4777-8N90-mask","layers":[{"ddd":0,"ind":72,"ty":4,"nm":"center","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[-100000,-100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":73,"ty":4,"nm":"Mask Group","parent":72,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Mask Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Mask","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[113.144,114.238],[197.071,128.633],[173.828,264.141],[89.902,249.746]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.85,0.85,0.85]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-4777-8N90-masked","layers":[{"ddd":0,"ind":76,"ty":4,"nm":"center","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[-100000,-100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":77,"ty":4,"nm":"Mask Group","parent":76,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Path 1 (6) Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1 (6)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[122.141,249.677],[119.497,248.718],[117.089,248.494],[114.641,248.528],[112.362,247.533],[109.891,247.669],[108.069,246.733],[106.299,245.875],[104.296,244.38],[102.387,242.685],[101.431,240.315],[100.823,237.899],[100.073,235.418],[100.89,233.778],[100.914,231.488],[101.426,229.279],[101.656,227.022],[101.753,224.748],[102.765,222.631],[103.005,220.381],[102.865,218.068],[103.313,215.592],[104.027,213.163],[104.73,210.732],[104.914,208.211],[105.606,205.778],[105.995,203.293],[106.335,200.799],[106.497,198.275],[107.105,195.827],[107.534,193.349],[108.205,190.912],[108.332,188.382],[108.576,185.871],[108.837,183.48],[108.206,183.254],[107.531,185.781],[107.114,188.353],[107.186,191.008],[106.255,193.491],[106.294,196.141],[105.563,198.659],[105.028,201.212],[104.615,203.681],[103.916,206.101],[104.08,208.67],[103.508,211.113],[102.954,213.559],[102.866,216.085],[102.089,218.493],[101.935,221.008],[101.469,223.469],[100.979,225.926],[100.802,228.437],[99.7,230.788],[99.584,233.167],[99.479,233.521],[98.902,236.14],[98.959,238.868],[98.358,241.484],[97.96,243.842],[98.587,244.342],[97.53,246.071],[97.515,246.038],[99.608,245.488],[102.186,247.13],[104.669,247.277],[107.045,248.062],[109.552,248.097],[111.981,248.584],[114.336,249.483],[116.887,249.281],[119.31,249.789],[122.658,250.823],[124.695,250.168],[124.901,250.275],[122.138,249.676]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (6)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[101.748,242.944],[99.268,242.537],[99.231,241.631],[99.421,238.301],[100.212,240.868]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (6)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[101.054,245.881],[101.958,243.581],[103.472,245.181],[105.62,246.9],[102.404,245.84]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (6)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[146.504,254.984],[143.735,254.666],[143.737,254.136],[140.878,253.684],[140.762,253.206],[140.824,253.109],[141.129,252.958],[142.62,253.064],[143.778,253.228],[144.055,252.803],[146.803,253.237],[149.193,253.79],[151.648,253.972],[154.039,254.517],[156.465,254.848],[159.079,254.896],[161.685,254.473],[163.976,253.157],[166.136,251.637],[168.108,249.751],[169.138,247.215],[169.41,245.122],[169.525,242.583],[170.37,240.17],[170.768,237.68],[171.369,235.224],[171.613,232.708],[171.745,230.171],[172.648,227.766],[172.864,225.245],[173.042,222.717],[173.415,220.222],[173.807,217.731],[174.868,215.355],[174.64,212.737],[175.153,210.246],[176.116,207.831],[176.116,205.252],[176.949,202.815],[176.813,200.212],[177.475,197.747],[178.136,195.282],[178.132,195.258],[178.37,194.818],[179.289,195.124],[179.649,195.518],[179.117,198.006],[179.024,200.569],[178.083,202.986],[177.864,205.528],[177.469,208.039],[176.942,210.528],[176.598,213.049],[176.272,215.573],[175.512,217.004],[175.138,219.618],[174.846,222.248],[174.517,224.87],[174.403,227.53],[173.329,230.026],[172.812,232.617],[172.873,235.307],[172.289,237.886],[171.909,240.501],[171.208,243.06],[170.904,245.687],[170.899,245.882],[170.681,248.784],[170.261,251.65],[169.531,254.462],[169.306,254.445],[168.382,256.421],[169.142,257.882],[168.899,258.638],[168.274,258.781],[168.325,258.484],[168.106,258.504],[166.715,257.811],[163.87,257.655],[163.626,257.65],[160.482,257.379],[157.777,256.885],[155.123,256.077],[152.302,256.281],[149.622,255.631],[146.49,255.085],[146.486,255.097],[146.503,254.986]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (6)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[164.994,254.661],[162.29,256.146],[163.976,256.678],[164.083,256.153],[164.165,256.256],[164.752,256.36]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (6)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[166.141,253.844],[167.084,254.14],[167.948,254.565],[167.682,254],[168.182,251.671],[168.309,251.784],[166.142,253.845]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.573,0.702,0.859]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-4773-8N90-mask","layers":[{"ddd":0,"ind":83,"ty":4,"nm":"center","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[-100000,-100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":84,"ty":4,"nm":"Mask Group","parent":83,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Mask Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Mask","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[113.144,114.238],[197.071,128.633],[173.828,264.141],[89.902,249.746]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.85,0.85,0.85]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-4773-8N90-masked","layers":[{"ddd":0,"ind":87,"ty":4,"nm":"center","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[-100000,-100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":88,"ty":4,"nm":"Mask Group","parent":87,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Path 1 (6) Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1 (6)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[165.285,129.113],[167.929,130.081],[170.387,130.005],[172.69,130.851],[175.093,131.098],[177.466,131.512],[179.499,131.623],[181.242,132.648],[183.463,133.967],[184.742,136.228],[185.802,138.467],[186.857,140.746],[187.441,143.274],[187.237,145.026],[186.671,147.225],[185.913,149.392],[185.408,151.602],[185.353,153.883],[185.351,156.174],[184.53,158.324],[184.485,160.606],[183.891,163.056],[183.41,165.526],[183.182,168.039],[182.401,170.457],[181.952,172.931],[181.472,175.401],[181.499,177.958],[180.637,180.362],[180.344,182.863],[180.046,185.364],[179.719,187.86],[179.08,190.303],[178.83,192.813],[178.558,195.218],[178.9,195.377],[179.877,192.901],[179.878,190.258],[180.522,187.726],[180.772,185.126],[181.434,182.597],[182.221,180.088],[182.545,177.499],[182.701,174.986],[183.327,172.553],[183.441,170.032],[183.755,167.545],[184.395,165.113],[184.997,162.675],[185.358,160.196],[185.744,157.721],[186.114,155.244],[186.515,152.771],[187.011,150.315],[187.404,147.843],[188.063,145.558],[187.605,145.109],[188.245,142.5],[188.636,139.849],[189.186,137.225],[188.865,135.03],[189.008,134.361],[190.031,132.734],[189.871,132.531],[187.918,133.056],[185.222,131.79],[182.741,131.589],[180.308,131.123],[177.94,130.288],[175.536,129.664],[173.071,129.387],[170.613,129.066],[168.166,128.689],[164.753,128.069],[162.574,128.116],[162.524,128.435],[165.282,129.111]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (6)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[185.786,135.796],[188.095,136.225],[188.407,137.093],[187.505,139.926],[187.179,137.845],[185.787,135.796]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (6)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[186.305,132.86],[185.469,135.002],[183.843,133.651],[182.233,132.355],[185.052,132.79]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (6)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[116.271,249.606],[113.52,248.987],[110.637,249.107],[107.861,248.631],[105.152,247.768],[102.178,247.215],[101.901,247.464],[99.444,245.863],[97.804,246.681],[97.401,246.839],[97.338,246.133],[96.922,245.489],[98.176,244.346],[98.148,243.785],[97.731,241.363],[98.576,238.795],[98.966,236.146],[99.428,233.51],[99.288,233.112],[100.1,230.682],[100.524,228.186],[100.597,225.629],[100.968,223.123],[101.274,220.607],[101.807,218.129],[102.033,215.598],[102.626,213.131],[103.067,210.637],[103.317,208.111],[103.897,205.641],[104.527,203.18],[104.661,200.633],[105.336,198.18],[105.913,195.71],[106.391,193.222],[106.834,190.729],[107.027,188.193],[107.132,185.641],[108.003,183.22],[108.086,183.077],[108.22,183.208],[109.145,183.486],[109.498,183.476],[109.159,185.97],[108.252,188.366],[108.247,190.917],[107.557,193.351],[107.287,195.857],[106.975,198.355],[106.841,200.884],[106.278,203.339],[105.952,205.835],[105.251,208.267],[104.59,210.706],[104.187,213.189],[104.061,215.72],[103.166,218.119],[103.298,220.913],[102.419,223.532],[102.26,226.277],[102.325,227.137],[101.943,229.367],[100.942,231.492],[100.903,233.78],[100.636,235.43],[100.788,237.903],[101.849,240.132],[103.034,242.225],[104.286,244.386],[106.473,245.57],[108.225,246.313],[109.986,247.165],[112.353,247.617],[114.716,248.098],[117.159,248.081],[119.569,248.289],[122.232,249.217],[125.027,250.105],[125.475,250.471],[124.958,250.491],[124.852,250.568],[122.677,250.712],[119.179,250.593],[119.217,250.351],[116.271,249.607]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (6)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[101.345,245.627],[102.168,246.046],[102.439,245.456],[102.474,245.451],[104.188,246.051],[101.928,243.736],[101.344,245.628]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (6)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[99.239,242.614],[100.022,243.13],[100.774,242.716],[100.029,240.12],[99.413,241.662]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.573,0.702,0.859]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-4769-8N90-mask","layers":[{"ddd":0,"ind":94,"ty":4,"nm":"center","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[-100000,-100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":95,"ty":4,"nm":"Mask Group","parent":94,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Mask Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Mask","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[113.144,114.238],[197.071,128.633],[173.828,264.141],[89.902,249.746]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.85,0.85,0.85]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-4769-8N90-masked","layers":[{"ddd":0,"ind":98,"ty":4,"nm":"center","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[-100000,-100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":99,"ty":4,"nm":"Mask Group","parent":98,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Path 1 (6) Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1 (6)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[143.578,124.696],[140.906,123.897],[138.461,123.496],[135.977,123.348],[133.513,123.073],[131.111,122.42],[128.726,121.673],[126.254,121.442],[123.721,121.495],[120.802,121.521],[119.188,120.354],[118.557,120.706],[119.594,122.523],[118.478,124.351],[117.941,127.188],[117.3,130.016],[117.109,132.923],[116.79,133.061],[116.554,135.607],[115.717,138.049],[115.156,140.539],[115.016,143.101],[114.728,145.638],[113.998,148.098],[113.664,150.627],[113.117,153.12],[113.047,155.695],[112.676,158.218],[111.946,160.679],[111.72,163.227],[111.335,165.741],[111.108,168.281],[110.18,170.702],[110.191,173.283],[109.832,175.8],[109.102,178.254],[108.504,180.73],[108.18,183.266],[108.794,183.719],[108.876,183.716],[109.146,183.216],[109.175,183.228],[109.183,183.245],[108.897,183.375],[109.083,183.384],[109.404,180.861],[110.381,178.451],[110.334,175.865],[110.77,173.362],[111.251,170.866],[111.99,168.415],[112.002,165.839],[112.399,163.328],[112.926,160.86],[113.271,158.36],[114.208,155.961],[114.65,153.477],[114.579,150.906],[115.384,148.484],[115.723,145.983],[115.96,143.464],[116.743,141.04],[116.531,138.445],[117.224,136.005],[118.061,133.589],[117.881,131.356],[119.331,128.958],[120.985,126.758],[123.295,125.287],[125.757,124.234],[128.287,122.988],[131.046,123.44],[133.492,123.662],[135.937,123.901],[138.286,124.677],[140.736,124.875],[143.455,125.413],[144.805,126.042],[146.219,125.467],[146.238,125.466],[143.574,124.698]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (6)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[118.434,128.285],[119.378,124.613],[119.457,124.285],[121.925,124.865],[119.646,127.322],[118.432,128.285]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (6)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[122.211,124.648],[122.557,122.074],[123.445,122.156],[126.787,122.499],[124.511,123.597]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (6)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[178.284,195.313],[177.954,195.214],[178.067,192.681],[178.519,190.207],[179.588,187.838],[179.973,185.352],[180.012,182.807],[180.428,180.327],[180.987,177.871],[181.615,175.426],[182.055,172.95],[182.49,170.473],[182.4,167.905],[183.297,165.507],[183.481,162.986],[183.842,160.496],[184.739,157.839],[184.78,155.035],[185.248,152.305],[185.745,151.659],[186.02,149.411],[185.873,147.089],[186.774,144.947],[186.417,143.247],[186.456,140.825],[185.844,138.45],[184.727,136.241],[182.893,134.591],[181.092,132.911],[179.275,132.229],[177.466,131.493],[175.077,131.167],[172.734,130.565],[170.294,130.56],[167.996,129.709],[165.271,129.178],[162.502,128.469],[162.264,128.271],[162.683,128.234],[162.558,128.002],[164.827,127.557],[168.203,128.471],[171.198,128.991],[174.007,129.272],[176.79,129.692],[179.545,130.272],[182.324,130.713],[185.3,131.275],[185.565,131.106],[188.069,132.712],[189.814,132.398],[189.999,132.118],[190.104,132.559],[190.008,132.71],[188.932,134.3],[189.128,134.955],[189.54,137.294],[189.299,139.967],[188.792,142.595],[188.062,145.186],[187.847,145.522],[187.542,148.039],[187.289,150.565],[187.02,153.088],[186.518,155.571],[186.119,158.072],[185.433,160.524],[185.034,163.025],[184.74,165.544],[183.927,167.973],[184.028,170.56],[183.061,172.963],[183.264,175.567],[182.618,178.026],[181.738,180.444],[181.752,183.016],[181.575,185.555],[180.471,187.935],[180.266,190.469],[179.993,192.992],[179.767,195.524],[179.484,195.783],[179.131,195.858],[178.286,195.312]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (6)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[186.096,136.036],[187.643,138.751],[187.624,136.957],[187.766,136.327],[187.426,135.54],[186.095,136.037]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (6)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[183.071,132.724],[185.548,134.813],[185.632,133.318],[185.17,133.27],[185.081,132.596],[185.074,132.637]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.573,0.702,0.859]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-4765-8N90-mask","layers":[{"ddd":0,"ind":105,"ty":4,"nm":"center","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[-100000,-100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":106,"ty":4,"nm":"Mask Group","parent":105,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Mask Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Mask","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[113.144,114.238],[197.071,128.633],[173.828,264.141],[89.902,249.746]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.85,0.85,0.85]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-4765-8N90-masked","layers":[{"ddd":0,"ind":109,"ty":4,"nm":"center","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[-100000,-100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":110,"ty":4,"nm":"Mask Group","parent":109,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Path 1 (5) Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1 (5)","d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[138.617,252.38],[138.725,252.061],[136.316,250.213],[134.015,247.892],[137.265,245.631],[138.142,247.771],[138.85,249.832],[139.778,248.443],[138.915,245.16],[138.543,243.203],[137.042,241.842],[136.219,240.114],[135.235,239.703],[135.15,239.337],[134.921,239.597],[133.862,239.676],[132.529,241.066],[130.912,242.104],[129.327,243.333],[127.914,246.434],[128.183,248.045],[129.3,246.102],[130.89,244.747],[133.336,247.744],[130.864,248.762],[128.617,249.678],[126.527,250.079],[124.303,249.554],[121.957,249.734],[119.765,249.048],[119.673,249.928],[121.974,250.537],[124.427,250.638],[126.796,251.223],[128.908,251.018],[130.81,250.159],[133.624,248.231],[135.167,251.319],[136.987,252.675],[139.099,253.122]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (5)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[133.741,247.544],[132.596,245.8],[130.891,244.655],[132.479,242.803],[134.708,241.906],[136.239,243.663],[137.163,245.56],[135.362,246.223]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (5)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[108.15,183.612],[108.196,183.254],[108.164,180.669],[108.459,178.141],[109.362,175.717],[109.671,173.191],[109.864,170.645],[110.598,168.192],[111.176,165.711],[111.104,163.118],[111.367,161.747],[111.857,159.138],[112.673,156.586],[113.284,153.998],[113.093,151.272],[113.665,148.678],[113.944,146.033],[114.779,143.483],[114.872,140.806],[115.439,138.21],[115.885,135.594],[116.698,133.042],[116.778,132.861],[117.111,129.979],[117.702,127.143],[118.151,124.283],[118.115,124.25],[118.962,122.229],[118.524,120.722],[118.961,120.389],[119.189,119.818],[119.219,120.325],[120.711,120.797],[123.588,120.967],[123.854,120.852],[126.902,121.67],[129.683,121.747],[132.343,122.536],[135.067,122.944],[137.86,122.933],[140.932,123.744],[143.654,124.321],[143.778,124.205],[146.405,125.243],[146.698,125.499],[146.297,125.539],[146.35,125.862],[144.813,125.966],[143.641,125.622],[143.354,126.143],[140.686,125.205],[138.274,124.787],[135.849,124.442],[133.484,123.745],[130.97,123.924],[128.343,123.572],[125.703,124.064],[123.256,125.203],[121.189,126.948],[119.648,129.145],[118.6,131.585],[117.861,133.554],[117.314,136.019],[117.036,138.53],[116.418,140.983],[116.012,143.473],[116.075,146.042],[115.179,148.448],[114.838,150.949],[114.343,153.424],[114.399,155.993],[114.105,158.502],[113.406,160.942],[113.099,163.449],[112.475,165.919],[111.814,168.384],[111.725,170.947],[111.234,173.44],[110.728,175.933],[110.209,178.421],[110.249,181.007],[109.239,183.411],[109.321,183.449],[109.093,183.82]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (5)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[119.623,124.672],[118.868,126.963],[119.049,126.846],[121.007,124.715],[120.42,124.214],[119.624,124.399]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (5)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[122.682,122.33],[122.415,124.313],[125.155,122.555],[123.358,122.665],[123.433,122.19],[123.304,122.29]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.573,0.702,0.859]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-4761-8N90-mask","layers":[{"ddd":0,"ind":116,"ty":4,"nm":"center","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[-100000,-100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":117,"ty":4,"nm":"Mask Group","parent":116,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Mask Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Mask","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[113.144,114.238],[197.071,128.633],[173.828,264.141],[89.902,249.746]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.85,0.85,0.85]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-4761-8N90-masked","layers":[{"ddd":0,"ind":120,"ty":4,"nm":"center","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[-100000,-100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":121,"ty":4,"nm":"Mask Group","parent":120,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Path 1 (4) Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1 (4)","d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[149.116,126.719],[148.913,126.441],[151.166,128.442],[153.176,130.744],[149.996,132.639],[149.446,130.864],[148.374,129.099],[148.101,130.252],[148.102,133.654],[148.9,135.489],[149.951,137.176],[151.588,138.273],[151.832,139.375],[152.235,139.716],[152.598,139.249],[153.359,138.632],[155.222,138.067],[156.477,136.529],[158.052,135.309],[159.375,132.202],[159.465,131.116],[158.145,132.587],[156.3,134.218],[154.674,130.952],[156.53,129.82],[158.602,128.545],[160.927,128.567],[163.143,129.144],[165.472,129.058],[167.672,129.61],[167.809,128.708],[165.461,128.211],[163.008,128.113],[160.693,127.213],[158.467,127.364],[156.418,128.223],[153.91,129.953],[152.179,127.446],[150.282,126.3],[148.422,125.139]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (4)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[153.62,131.639],[155.042,132.702],[156.601,134.016],[154.804,135.685],[152.815,136.331],[151.336,134.947],[150.041,132.909],[151.889,132.081],[153.619,131.639]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (4)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[139.039,253.468],[136.784,252.995],[134.915,251.494],[133.488,249.018],[130.954,250.362],[129.046,251.626],[126.704,251.761],[124.393,250.832],[121.996,250.408],[119.49,250.623],[119.439,250.035],[119.446,248.98],[119.837,248.594],[122.034,249.278],[124.315,249.468],[126.605,249.613],[128.681,249.812],[130.702,248.84],[132.34,247.603],[130.859,245.226],[129.323,246.944],[128.283,248.284],[127.954,248.094],[127.475,248.071],[127.542,246.307],[129.129,243.192],[130.695,241.884],[132.215,240.612],[133.097,239.598],[133.698,239.401],[134.694,239.111],[134.864,239.315],[135.095,239.237],[135.178,239.153],[135.205,238.994],[135.613,238.976],[135.548,239.249],[135.489,239.449],[136.526,239.852],[136.614,240.609],[137.56,241.462],[138.78,243.084],[139.546,244.975],[139.665,248.44],[139.139,249.729],[139.03,249.889],[138.738,249.951],[138.031,248.474],[137.163,246.114],[134.782,247.997],[136.872,249.894],[138.799,251.948],[138.608,252.216],[139.364,253.253],[139.272,253.57],[139.037,253.465]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (4)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[133.791,247.255],[136.797,245.42],[135.677,243.97],[134.603,242.52],[132.715,243.037],[131.539,244.547]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.573,0.702,0.859]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-4757-8N90-mask","layers":[{"ddd":0,"ind":127,"ty":4,"nm":"center","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[-100000,-100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":128,"ty":4,"nm":"Mask Group","parent":127,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Mask Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Mask","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[113.144,114.238],[197.071,128.633],[173.828,264.141],[89.902,249.746]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.85,0.85,0.85]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-4757-8N90-masked","layers":[{"ddd":0,"ind":131,"ty":4,"nm":"center","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[-100000,-100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":132,"ty":4,"nm":"Mask Group","parent":131,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Path 1 (2) Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1 (2)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[152.23,139.746],[151.97,139.612],[151.767,139.597],[151.995,139.201],[151.233,138.574],[150.745,138.157],[149.98,137.158],[149.144,135.373],[148.142,133.645],[147.387,130.247],[148.087,128.696],[148.402,128.877],[148.579,128.879],[149.119,130.331],[150.314,132.669],[152.917,130.726],[150.826,128.549],[148.735,126.654],[148.706,126.536],[147.969,125.49],[148.005,124.993],[148.4,125.265],[150.428,126.069],[151.987,127.583],[153.991,129.47],[156.484,128.321],[158.371,126.942],[160.74,126.933],[163.154,127.262],[165.474,128.137],[167.968,127.992],[168.039,128.663],[167.907,129.696],[167.634,129.948],[165.445,129.216],[163.171,128.981],[160.849,129.028],[158.898,129.154],[156.821,130.016],[155.033,131.084],[156.48,133.601],[158.024,131.665],[159.173,130.435],[159.489,130.616],[159.522,130.888],[159.368,132.203],[158.162,135.39],[156.63,136.686],[155.387,138.309],[154.269,138.953],[153.592,139.029],[152.885,139.874],[152.573,139.354],[152.664,139.922]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (2)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[150.584,133.191],[151.539,134.867],[152.855,136.1],[154.713,135.634],[156.114,134.025],[153.623,131.624]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.573,0.702,0.859]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-4753-8N90-mask","layers":[{"ddd":0,"ind":138,"ty":4,"nm":"center","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[-100000,-100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":139,"ty":4,"nm":"Mask Group","parent":138,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Mask Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Mask","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[113.144,114.238],[197.071,128.633],[173.828,264.141],[89.902,249.746]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.85,0.85,0.85]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-4753-8N90-masked","layers":[{"ddd":0,"ind":142,"ty":4,"nm":"center","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[-100000,-100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":143,"ty":4,"nm":"Mask Group","parent":142,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Path 1 (2) Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1 (2)","d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[160.617,127.663],[158.53,127.644],[156.388,128.186],[153.906,129.979],[151.914,127.637],[150.303,126.271],[148.412,125.203],[145.943,125.199],[143.646,124.194],[141.146,123.845],[140.853,125.336],[143.252,125.577],[145.461,126.191],[147.72,126.51],[149.888,127.133],[151.268,129.17],[153.053,130.714],[150.038,132.74],[149.361,130.903],[148.616,128.842],[147.919,130.258],[148.381,133.581],[149.39,135.257],[150.246,136.97],[151.323,138.503],[152.195,139.01],[152.253,139.606],[152.644,139.358],[153.577,139.008],[154.938,137.662],[156.531,136.59],[157.555,134.958],[159.231,132.159],[159.225,130.563],[157.868,132.353],[156.628,133.875],[154.468,130.966],[157.114,129.687],[159.926,128.593],[160.059,128.114]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (2)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[156.661,133.996],[154.89,135.797],[152.805,136.399],[151.404,134.911],[150.087,132.955],[151.822,131.959],[153.664,131.398],[155.261,132.465],[156.662,133.996]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.573,0.702,0.859]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-4749-8N90-mask","layers":[{"ddd":0,"ind":149,"ty":4,"nm":"center","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[-100000,-100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":150,"ty":4,"nm":"Mask Group","parent":149,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Mask Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Mask","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[113.144,114.238],[197.071,128.633],[173.828,264.141],[89.902,249.746]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.85,0.85,0.85]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-4749-8N90-masked","layers":[{"ddd":0,"ind":153,"ty":4,"nm":"center","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[-100000,-100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":154,"ty":4,"nm":"Mask Group","parent":153,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Path 1 (2) Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1 (2)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[152.22,139.81],[152.222,139.43],[151.651,139.733],[151.964,139.245],[151.316,138.509],[150.784,138.136],[149.849,137.259],[149.03,135.433],[148.503,133.546],[147.37,130.252],[148.068,128.683],[148.476,128.446],[148.69,128.764],[149.371,130.234],[150.112,132.071],[152.764,130.724],[151.182,129.007],[149.411,127.601],[147.738,126.408],[145.479,126.085],[143.26,125.533],[140.93,125.62],[141.05,125.093],[140.544,123.951],[141.24,123.816],[143.671,124.04],[146.019,124.75],[148.475,124.832],[150.549,125.881],[152.479,127.239],[153.964,129.64],[156.499,128.345],[158.403,127.084],[160.71,127.121],[161.106,127.257],[160.643,127.383],[160.133,128.641],[159.881,128.566],[157.208,129.506],[154.563,131.011],[156.555,133.514],[158.377,131.976],[159.307,130.749],[159.513,130.494],[159.851,130.696],[159.856,132.37],[158.004,135.279],[156.954,137.019],[155.12,137.923],[154.094,138.628],[153.822,139.42],[152.844,139.787],[152.761,139.886],[152.477,139.65],[152.221,139.811]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (2)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[150.622,133.243],[151.273,135.037],[152.885,135.931],[154.682,135.598],[156.207,133.973],[153.629,131.581]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.573,0.702,0.859]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-4725-8N90-mask","layers":[{"ddd":0,"ind":158,"ty":4,"nm":"center","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[-100000,-100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":159,"ty":4,"nm":"Mask Group","parent":158,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[143.75,189.414]},"o":{"a":0,"k":100},"p":{"a":0,"k":[143.75,189.414]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Layer 17 Group","bm":0,"it":[{"ty":"gr","hd":false,"nm":"Path 1 Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1","d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0,0],[0.689,-4.021],[0,0],[-3.99,-0.684],[0,0],[-0.689,4.021],[0,0],[3.991,0.685]],"o":[[0,0],[-3.991,-0.684],[0,0],[-0.69,4.02],[0,0],[3.991,0.685],[0,0],[0.69,-4.021],[0,0]],"v":[[188.164,127.558],[122.487,116.293],[114.013,122.334],[93.359,242.75],[99.335,251.269],[165.013,262.534],[173.487,256.493],[194.141,136.078],[188.164,127.558]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[1,1,1]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[143.75,189.414]},"o":{"a":0,"k":100},"p":{"a":0,"k":[143.75,189.414]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-4725-8N90-masked","layers":[{"ddd":0,"ind":163,"ty":4,"nm":"center","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[-100000,-100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":164,"ty":4,"nm":"Mask Group","parent":163,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[143.75,189.414]},"o":{"a":0,"k":100},"p":{"a":0,"k":[143.75,189.414]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Layer 18 Group","bm":0,"it":[{"ty":"gr","hd":false,"nm":"Path 1 Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[175.769,254.417],[175.255,253.265],[174.477,252.236],[173.86,251.106],[173.133,250.045],[172.408,248.983],[171.961,247.746],[171.172,246.723],[170.526,245.612],[169.807,244.544],[169.224,243.484],[168.308,244.27],[167.196,244.937],[166.275,245.844],[165.267,246.64],[164.304,247.494],[163.342,248.35],[162.415,249.25],[161.414,250.056],[160.331,250.76],[159.36,251.602],[159.961,252.739],[160.609,253.85],[161.374,254.886],[161.928,256.056],[162.597,257.153],[163.32,258.217],[163.979,259.322],[164.585,260.459],[165.275,261.544],[165.914,262.784],[166.993,261.909],[167.99,261.099],[168.817,260.074],[169.883,259.349],[170.801,258.439],[171.825,257.662],[172.811,256.835],[173.825,256.046],[174.913,255.345]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.925,0.957,0.996]},"r":1,"o":{"a":0,"k":60}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"gr","hd":false,"nm":"Path 1 (2) Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1 (2)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[142.952,248.788],[142.353,247.733],[141.797,246.564],[141.146,245.456],[140.335,244.448],[139.837,243.244],[139.019,242.24],[138.568,241.005],[137.784,239.979],[137.039,238.929],[136.469,237.823],[135.541,238.644],[134.458,239.349],[133.509,240.219],[132.459,240.964],[131.624,241.978],[130.557,242.7],[129.499,243.437],[128.569,244.332],[127.539,245.103],[126.526,245.97],[127.164,247.142],[127.863,248.22],[128.478,249.352],[129.306,250.349],[129.819,251.544],[130.425,252.681],[131.245,253.684],[131.959,254.754],[132.607,255.866],[133.188,256.957],[134.168,256.21],[135.162,255.396],[136.075,254.479],[137.06,253.652],[138.049,252.833],[139.142,252.141],[140.097,251.278],[141.006,250.355],[142.052,249.606]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (2)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[159.556,251.636],[158.78,250.515],[158.194,249.366],[157.346,248.381],[156.817,247.196],[156.241,246.04],[155.506,244.983],[154.834,243.888],[154.097,242.833],[153.481,241.701],[152.873,240.505],[151.787,241.285],[150.895,242.227],[149.936,243.085],[149,243.972],[147.928,244.689],[146.966,245.544],[145.927,246.304],[144.929,247.115],[143.999,248.012],[142.961,248.79],[143.618,249.907],[144.376,250.949],[144.894,252.141],[145.619,253.203],[146.182,254.366],[146.826,255.479],[147.479,256.588],[148.306,257.585],[148.805,258.791],[149.548,259.889],[150.576,259.053],[151.562,258.227],[152.584,257.45],[153.573,256.629],[154.44,255.653],[155.414,254.814],[156.436,254.033],[157.536,253.352],[158.538,252.553]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.925,0.957,0.996]},"r":1,"o":{"a":0,"k":60}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"gr","hd":false,"nm":"Path 1 (4) Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1 (4)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[162.552,232.457],[162.011,231.31],[161.189,230.309],[160.611,229.154],[160.051,227.989],[159.208,227.001],[158.766,225.762],[158.048,224.693],[157.306,223.643],[156.695,222.507],[155.987,221.552],[155.041,222.263],[154.067,223.103],[152.958,223.773],[152.14,224.809],[151.072,225.53],[150.146,226.431],[149.008,227.065],[148.148,228.049],[147.177,228.896],[145.987,229.616],[146.75,230.764],[147.431,231.854],[148.18,232.901],[148.774,234.046],[149.41,235.163],[149.984,236.32],[150.775,237.343],[151.316,238.52],[152.152,239.513],[152.722,240.59],[153.656,239.815],[154.723,239.09],[155.608,238.14],[156.637,237.369],[157.581,236.492],[158.55,235.646],[159.714,235.043],[160.678,234.192],[161.679,233.38]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (4)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[179.246,235.116],[178.527,233.965],[177.844,232.877],[177.047,231.859],[176.579,230.635],[175.756,229.635],[175.151,228.497],[174.49,227.395],[173.766,226.332],[173.305,225.102],[172.575,223.958],[171.641,224.964],[170.547,225.652],[169.638,226.573],[168.621,227.359],[167.618,228.162],[166.634,228.989],[165.706,229.889],[164.577,230.533],[163.631,231.41],[162.692,232.277],[163.215,233.45],[164.108,234.408],[164.691,235.559],[165.379,236.644],[165.859,237.86],[166.629,238.894],[167.289,239.998],[167.859,241.157],[168.58,242.223],[169.231,243.454],[170.195,242.435],[171.227,241.669],[172.188,240.814],[173.15,239.959],[174.285,239.321],[175.224,238.438],[176.222,237.627],[177.149,236.727],[178.215,236.007]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (4)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[110.281,243.185],[109.659,242.072],[108.89,241.037],[108.374,239.843],[107.701,238.749],[106.892,237.74],[106.413,236.523],[105.78,235.402],[105.021,234.362],[104.315,233.286],[103.696,232.271],[102.747,232.985],[101.758,233.807],[100.727,234.577],[99.774,235.441],[98.845,236.338],[97.721,236.988],[96.839,237.945],[95.733,238.619],[94.851,239.577],[93.902,240.375],[94.427,241.508],[95.193,242.544],[95.786,243.689],[96.379,244.834],[97.147,245.869],[97.775,246.992],[98.521,248.042],[98.971,249.277],[99.754,250.304],[100.404,251.468],[101.395,250.578],[102.298,249.648],[103.327,248.878],[104.303,248.041],[105.392,247.346],[106.297,246.419],[107.319,245.638],[108.346,244.865],[109.257,243.945]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (4)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[126.545,245.974],[126.142,244.818],[125.418,243.755],[124.793,242.63],[124.123,241.533],[123.427,240.454],[122.641,239.429],[121.995,238.317],[121.327,237.219],[120.88,235.981],[120.108,234.9],[119.135,235.803],[118.116,236.587],[117.225,237.532],[116.166,238.264],[115.173,239.08],[114.116,239.816],[113.246,240.787],[112.249,241.599],[111.272,242.435],[110.292,243.187],[110.849,244.292],[111.589,245.345],[112.234,246.457],[112.837,247.596],[113.507,248.692],[114.156,249.802],[114.813,250.908],[115.468,252.014],[116.057,253.163],[116.777,254.322],[117.693,253.281],[118.812,252.625],[119.717,251.698],[120.786,250.978],[121.805,250.196],[122.732,249.295],[123.804,248.579],[124.622,247.541],[125.62,246.737]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.925,0.957,0.996]},"r":1,"o":{"a":0,"k":60}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"gr","hd":false,"nm":"Path 1 Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[146.28,229.461],[145.651,228.418],[145.095,227.249],[144.398,226.17],[143.708,225.086],[142.965,224.035],[142.484,222.82],[141.721,221.78],[141.15,220.621],[140.334,219.614],[139.782,218.522],[138.89,219.357],[137.801,220.053],[136.798,220.854],[135.899,221.789],[134.779,222.446],[133.85,223.342],[132.817,224.108],[131.807,224.903],[130.95,225.892],[129.824,226.639],[130.577,227.754],[131.348,228.787],[131.817,230.01],[132.47,231.117],[133.266,232.135],[133.891,233.259],[134.408,234.453],[135.124,235.522],[135.89,236.559],[136.477,237.796],[137.416,236.794],[138.552,236.16],[139.513,235.303],[140.498,234.478],[141.361,233.497],[142.407,232.749],[143.388,231.916],[144.476,231.219],[145.425,230.343]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.925,0.957,0.996]},"r":1,"o":{"a":0,"k":60}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"gr","hd":false,"nm":"Path 1 Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[129.914,226.654],[129.238,225.627],[128.658,224.475],[128.032,223.351],[127.44,222.205],[126.74,221.127],[126.111,220.005],[125.495,218.873],[124.746,217.825],[124.016,216.766],[123.41,215.662],[122.339,216.332],[121.388,217.202],[120.515,218.169],[119.488,218.942],[118.561,219.842],[117.398,220.442],[116.455,221.323],[115.508,222.197],[114.473,222.961],[113.513,223.841],[114.228,224.924],[114.804,226.08],[115.574,227.114],[116.109,228.295],[116.828,229.36],[117.577,230.408],[118.11,231.592],[118.753,232.706],[119.528,233.738],[120.109,234.902],[121.047,234],[122.017,233.155],[123.087,232.437],[124.063,231.599],[124.965,230.668],[126.108,230.041],[126.97,229.059],[128.092,228.406],[128.956,227.429]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.925,0.957,0.996]},"r":1,"o":{"a":0,"k":60}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"gr","hd":false,"nm":"Path 1 (2) Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1 (2)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[182.561,215.789],[181.754,214.693],[181.265,213.482],[180.506,212.441],[179.88,211.317],[179.101,210.289],[178.604,209.083],[177.962,207.969],[177.207,206.925],[176.497,205.853],[175.853,204.85],[174.81,205.451],[173.932,206.413],[172.979,207.278],[171.924,208.016],[170.885,208.774],[169.945,209.658],[168.922,210.437],[168.058,211.416],[167.004,212.157],[165.869,212.925],[166.706,214.013],[167.204,215.217],[168.076,216.187],[168.541,217.414],[169.404,218.388],[169.993,219.536],[170.675,220.625],[171.141,221.85],[171.808,222.951],[172.55,224.105],[173.62,223.248],[174.627,222.448],[175.538,221.529],[176.509,220.687],[177.587,219.978],[178.504,219.065],[179.566,218.336],[180.5,217.446],[181.391,216.493]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (2)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[113.646,223.864],[112.859,222.816],[112.285,221.659],[111.745,220.481],[111.099,219.37],[110.253,218.384],[109.686,217.222],[108.988,216.142],[108.327,215.04],[107.729,213.897],[107.042,212.779],[106.026,213.608],[105.054,214.452],[104.102,215.318],[103.152,216.189],[102.092,216.92],[101.175,217.833],[100.077,218.517],[99.092,219.343],[98.162,220.241],[97.167,221.037],[97.805,222.141],[98.475,223.237],[98.999,224.426],[99.829,225.421],[100.549,226.486],[101.095,227.662],[101.862,228.698],[102.461,229.84],[102.968,231.04],[103.737,232.044],[104.724,231.264],[105.759,230.5],[106.607,229.503],[107.774,228.906],[108.664,227.961],[109.713,227.214],[110.656,226.335],[111.725,225.614],[112.689,224.761]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.925,0.957,0.996]},"r":1,"o":{"a":0,"k":60}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"gr","hd":false,"nm":"Path 1 (2) Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1 (2)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[149.595,210.135],[148.922,209.117],[148.486,207.874],[147.742,206.824],[147.089,205.717],[146.462,204.594],[145.773,203.508],[145.149,202.382],[144.315,201.388],[143.864,200.152],[143.115,199.087],[142.073,199.863],[141.218,200.855],[140.236,201.683],[139.198,202.443],[138.153,203.194],[137.156,204.005],[136.115,204.76],[135.139,205.598],[134.175,206.453],[133.249,207.331],[133.967,208.38],[134.513,209.554],[135.174,210.656],[135.82,211.767],[136.617,212.785],[137.172,213.953],[137.847,215.048],[138.517,216.145],[139.073,217.315],[139.784,218.51],[140.724,217.461],[141.806,216.755],[142.794,215.933],[143.812,215.148],[144.713,214.217],[145.698,213.392],[146.801,212.712],[147.627,211.686],[148.681,210.946]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (2)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[165.965,212.942],[165.434,211.844],[164.702,210.787],[164.175,209.601],[163.328,208.615],[162.839,207.405],[162.104,206.349],[161.551,205.179],[160.702,204.193],[160.219,202.978],[159.518,201.768],[158.49,202.718],[157.496,203.533],[156.527,204.38],[155.615,205.297],[154.525,205.991],[153.623,206.922],[152.594,207.694],[151.667,208.593],[150.558,209.264],[149.658,210.145],[150.16,211.307],[150.905,212.357],[151.577,213.452],[152.147,214.611],[152.96,215.618],[153.645,216.706],[154.131,217.918],[154.873,218.97],[155.643,220.006],[156.214,221.033],[157.095,220.256],[158.193,219.571],[159.221,218.802],[160.191,217.958],[161.169,217.122],[162.149,216.29],[163.167,215.502],[164.187,214.721],[165.009,213.694]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.925,0.957,0.996]},"r":1,"o":{"a":0,"k":60}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"gr","hd":false,"nm":"Path 1 Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[133.259,207.332],[132.647,206.242],[131.986,205.139],[131.263,204.076],[130.667,202.932],[130.023,201.82],[129.301,200.756],[128.673,199.631],[128.167,198.431],[127.335,197.435],[126.744,196.223],[125.823,197.218],[124.786,197.979],[123.737,198.724],[122.863,199.689],[121.875,200.512],[120.821,201.251],[119.819,202.056],[118.749,202.775],[117.77,203.612],[116.8,204.509],[117.41,205.68],[118.285,206.648],[118.707,207.901],[119.353,209.011],[120.047,210.094],[120.686,211.21],[121.39,212.286],[122.163,213.318],[122.689,214.507],[123.405,215.687],[124.355,214.663],[125.423,213.941],[126.404,213.11],[127.455,212.369],[128.443,211.546],[129.275,210.527],[130.344,209.805],[131.357,209.015],[132.369,208.219]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.925,0.957,0.996]},"r":1,"o":{"a":0,"k":60}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"gr","hd":false,"nm":"Path 1 (3) Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1 (3)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[169.257,193.609],[168.83,192.464],[168.05,191.436],[167.288,190.397],[166.793,189.191],[166.134,188.088],[165.463,186.992],[164.823,185.875],[164.131,184.793],[163.504,183.667],[162.787,182.698],[161.869,183.473],[160.92,184.345],[159.839,185.048],[158.832,185.847],[157.901,186.741],[156.967,187.632],[155.923,188.384],[154.812,189.052],[153.869,189.933],[152.895,190.803],[153.637,191.875],[154.25,193.008],[154.989,194.06],[155.64,195.169],[156.247,196.306],[156.746,197.51],[157.407,198.613],[158.153,199.663],[158.766,200.796],[159.481,201.97],[160.452,200.981],[161.407,200.118],[162.528,199.465],[163.421,198.521],[164.425,197.72],[165.427,196.916],[166.398,196.07],[167.387,195.249],[168.342,194.389]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (3)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[185.844,196.454],[185.021,195.394],[184.514,194.195],[183.836,193.103],[183.182,191.996],[182.441,190.945],[181.947,189.737],[181.151,188.719],[180.468,187.631],[179.903,186.466],[179.171,185.496],[178.23,186.255],[177.157,186.969],[176.265,187.913],[175.288,188.75],[174.209,189.456],[173.297,190.375],[172.285,191.168],[171.298,191.992],[170.337,192.85],[169.377,193.63],[169.897,194.762],[170.694,195.779],[171.194,196.982],[172.042,197.966],[172.473,199.214],[173.315,200.202],[173.79,201.421],[174.62,202.419],[175.174,203.589],[175.88,204.685],[176.955,203.944],[177.856,203.013],[178.861,202.212],[179.809,201.339],[180.782,200.498],[181.796,199.709],[182.782,198.883],[183.73,198.009],[184.742,197.214]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (3)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[116.865,204.52],[116.247,203.443],[115.602,202.331],[114.942,201.228],[114.284,200.123],[113.699,198.974],[113.07,197.851],[112.277,196.831],[111.76,195.638],[111.014,194.587],[110.369,193.376],[109.286,194.213],[108.443,195.217],[107.461,196.048],[106.339,196.7],[105.447,197.643],[104.407,198.401],[103.428,199.234],[102.485,200.114],[101.542,200.996],[100.482,201.71],[101.22,202.749],[101.701,203.965],[102.478,204.995],[103.161,206.083],[103.664,207.285],[104.406,208.337],[104.973,209.498],[105.687,210.567],[106.27,211.721],[107.042,212.772],[107.978,211.86],[109.057,211.152],[110.049,210.334],[110.922,209.368],[111.985,208.64],[112.921,207.752],[113.953,206.984],[114.957,206.182],[116.019,205.447]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.925,0.957,0.996]},"r":1,"o":{"a":0,"k":60}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"gr","hd":false,"nm":"Path 1 Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[152.988,190.819],[152.3,189.749],[151.741,188.582],[150.924,187.578],[150.348,186.423],[149.783,185.26],[149.151,184.14],[148.505,183.026],[147.819,181.939],[147.006,180.932],[146.442,179.689],[145.401,180.552],[144.506,181.491],[143.417,182.186],[142.46,183.047],[141.439,183.828],[140.469,184.672],[139.518,185.543],[138.534,186.371],[137.603,187.265],[136.431,187.979],[137.318,189.027],[137.8,190.243],[138.51,191.314],[139.126,192.445],[139.792,193.543],[140.46,194.641],[141.268,195.652],[141.931,196.754],[142.464,197.937],[143.148,198.894],[144.192,198.324],[145.047,197.334],[146.079,196.567],[147.097,195.783],[148.003,194.857],[148.963,193.999],[150.103,193.368],[151.115,192.575],[152.019,191.645]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.925,0.957,0.996]},"r":1,"o":{"a":0,"k":60}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"gr","hd":false,"nm":"Path 1 (3) Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1 (3)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[189.185,177.131],[188.532,175.944],[187.776,174.901],[187.112,173.801],[186.38,172.743],[185.671,171.671],[185.113,170.503],[184.373,169.451],[183.896,168.232],[183.044,167.249],[182.505,166.067],[181.543,166.923],[180.59,167.791],[179.584,168.589],[178.607,169.425],[177.514,170.115],[176.577,171.001],[175.646,171.897],[174.538,172.569],[173.543,173.384],[172.614,174.288],[173.402,175.316],[173.826,176.567],[174.638,177.574],[175.134,178.78],[176.001,179.754],[176.495,180.96],[177.328,181.955],[177.871,183.132],[178.646,184.165],[179.187,185.412],[180.267,184.611],[181.16,183.67],[182.159,182.861],[183.13,182.017],[184.189,181.286],[185.046,180.298],[186.158,179.63],[187.157,178.822],[188.07,177.9]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (3)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[120.125,185.182],[119.687,184.035],[118.821,183.062],[118.279,181.885],[117.668,180.753],[117.037,179.631],[116.345,178.548],[115.747,177.406],[115.007,176.352],[114.249,175.309],[113.68,174.07],[112.703,175.015],[111.761,175.893],[110.758,176.696],[109.771,177.52],[108.76,178.314],[107.662,178.996],[106.77,179.941],[105.693,180.652],[104.824,181.626],[103.716,182.368],[104.531,183.424],[105.048,184.617],[105.86,185.624],[106.289,186.872],[107.011,187.936],[107.841,188.932],[108.326,190.146],[109.111,191.171],[109.795,192.26],[110.375,193.336],[111.277,192.512],[112.304,191.738],[113.383,191.031],[114.25,190.057],[115.385,189.419],[116.281,188.481],[117.273,187.664],[118.313,186.906],[119.198,185.958]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (3)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[136.61,188.01],[135.942,186.925],[135.36,185.773],[134.759,184.632],[133.979,183.604],[133.347,182.485],[132.638,181.412],[132.071,180.25],[131.371,179.172],[130.601,178.137],[130.065,176.853],[129.127,177.877],[127.992,178.513],[127.113,179.473],[126.155,180.333],[125.076,181.042],[124.088,181.862],[123.064,182.64],[122.13,183.533],[121.159,184.377],[120.258,185.205],[120.725,186.35],[121.396,187.447],[122.249,188.429],[122.709,189.657],[123.436,190.717],[124.018,191.87],[124.885,192.843],[125.356,194.065],[125.969,195.199],[126.719,196.357],[127.737,195.421],[128.753,194.634],[129.628,193.669],[130.626,192.86],[131.688,192.13],[132.738,191.387],[133.746,190.588],[134.575,189.565],[135.631,188.826]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.925,0.957,0.996]},"r":1,"o":{"a":0,"k":60}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"gr","hd":false,"nm":"Path 1 Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[172.734,174.309],[172.17,173.122],[171.31,172.145],[170.67,171.029],[170.105,169.866],[169.309,168.849],[168.722,167.699],[168.186,166.518],[167.343,165.529],[166.809,164.346],[166.114,163.311],[165.172,164.127],[164.067,164.801],[163.174,165.744],[162.159,166.531],[161.208,167.4],[160.202,168.2],[159.204,169.011],[158.299,169.938],[157.176,170.592],[156.199,171.473],[156.943,172.554],[157.654,173.625],[158.306,174.734],[158.983,175.826],[159.473,177.034],[160.203,178.094],[160.95,179.143],[161.516,180.305],[162.149,181.427],[162.817,182.533],[163.839,181.742],[164.728,180.795],[165.704,179.958],[166.817,179.294],[167.808,178.476],[168.815,177.676],[169.673,176.69],[170.642,175.842],[171.65,175.043]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.925,0.957,0.996]},"r":1,"o":{"a":0,"k":60}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"gr","hd":false,"nm":"Path 1 Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[156.312,171.492],[155.674,170.384],[155.106,169.222],[154.368,168.169],[153.787,167.017],[152.909,166.051],[152.331,164.896],[151.61,163.831],[151.178,162.584],[150.501,161.49],[149.745,160.436],[148.681,161.178],[147.757,162.08],[146.75,162.88],[145.857,163.821],[144.812,164.57],[143.861,165.44],[142.76,166.122],[141.879,167.079],[140.843,167.843],[139.882,168.674],[140.495,169.786],[141.072,170.941],[141.906,171.935],[142.386,173.151],[143.168,174.178],[143.808,175.293],[144.385,176.449],[145.046,177.551],[145.68,178.672],[146.46,179.587],[147.353,178.8],[148.481,178.154],[149.462,177.324],[150.359,176.385],[151.336,175.552],[152.46,174.9],[153.316,173.91],[154.33,173.12],[155.413,172.416]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.925,0.957,0.996]},"r":1,"o":{"a":0,"k":60}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"gr","hd":false,"nm":"Path 1 Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[139.907,168.678],[139.32,167.557],[138.575,166.508],[137.955,165.38],[137.278,164.288],[136.633,163.176],[135.914,162.11],[135.237,161.016],[134.594,159.902],[133.995,158.76],[133.383,157.513],[132.427,158.529],[131.438,159.35],[130.353,160.048],[129.486,161.024],[128.384,161.702],[127.494,162.648],[126.438,163.386],[125.512,164.286],[124.476,165.05],[123.441,165.854],[124.12,166.973],[124.853,168.031],[125.392,169.21],[126.148,170.251],[126.722,171.409],[127.325,172.547],[128.189,173.524],[128.829,174.639],[129.378,175.813],[130.081,176.765],[131.131,176.189],[131.994,175.211],[133.003,174.416],[134.071,173.693],[135.002,172.799],[135.963,171.942],[137.035,171.224],[137.968,170.334],[139.033,169.605]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.925,0.957,0.996]},"r":1,"o":{"a":0,"k":60}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"gr","hd":false,"nm":"Path 1 (2) Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1 (2)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[192.457,157.795],[191.807,156.64],[191.197,155.506],[190.33,154.533],[189.83,153.33],[189.216,152.198],[188.374,151.209],[187.687,150.122],[187.106,148.969],[186.512,147.823],[185.838,146.631],[184.873,147.615],[183.925,148.488],[182.785,149.119],[181.898,150.068],[180.839,150.802],[179.87,151.648],[178.926,152.526],[177.891,153.29],[177.009,154.247],[176.034,154.978],[176.614,156.051],[177.347,157.109],[177.963,158.239],[178.447,159.452],[179.323,160.42],[179.959,161.538],[180.497,162.719],[181.249,163.764],[181.764,164.96],[182.496,166.116],[183.468,165.14],[184.408,164.257],[185.496,163.562],[186.542,162.813],[187.496,161.946],[188.441,161.07],[189.405,160.218],[190.399,159.403],[191.466,158.68]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (2)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[123.626,165.886],[122.841,164.81],[122.346,163.603],[121.561,162.578],[120.879,161.49],[120.346,160.307],[119.543,159.294],[118.952,158.147],[118.39,156.982],[117.575,155.975],[116.961,154.947],[115.935,155.581],[114.96,156.419],[114.062,157.354],[113.049,158.144],[112.01,158.903],[111.016,159.717],[110.034,160.548],[109.104,161.443],[108.03,162.16],[107.122,163.055],[107.853,164.092],[108.462,165.227],[108.992,166.412],[109.849,167.391],[110.464,168.522],[111.14,169.615],[111.754,170.747],[112.471,171.815],[112.927,173.047],[113.68,174.073],[114.629,173.23],[115.66,172.463],[116.666,171.662],[117.569,170.733],[118.6,169.966],[119.58,169.132],[120.566,168.307],[121.574,167.509],[122.575,166.701]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.925,0.957,0.996]},"r":1,"o":{"a":0,"k":60}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"gr","hd":false,"nm":"Path 1 (3) Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1 (3)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[143.141,149.336],[142.701,148.188],[141.854,147.202],[141.311,146.026],[140.665,144.914],[139.928,143.861],[139.362,142.698],[138.652,141.626],[138.12,140.442],[137.316,139.427],[136.667,138.364],[135.636,139.067],[134.67,139.916],[133.659,140.709],[132.638,141.49],[131.69,142.362],[130.76,143.258],[129.723,144.02],[128.783,144.904],[127.724,145.638],[126.893,146.549],[127.421,147.654],[128.175,148.698],[128.822,149.809],[129.429,150.946],[130.185,151.987],[130.827,153.102],[131.365,154.282],[131.989,155.408],[132.674,156.497],[133.386,157.498],[134.425,156.836],[135.397,155.994],[136.298,155.062],[137.407,154.392],[138.404,153.582],[139.3,152.643],[140.216,151.728],[141.221,150.928],[142.275,150.186]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (3)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[159.524,152.146],[158.859,151.138],[158.297,149.974],[157.55,148.926],[157.024,147.739],[156.412,146.605],[155.721,145.522],[155.146,144.365],[154.402,143.314],[153.669,142.255],[153.061,141.101],[152.142,142.033],[151.083,142.766],[150.038,143.517],[149.11,144.414],[148.156,145.28],[147.128,146.052],[146.226,146.983],[145.201,147.76],[144.165,148.522],[143.154,149.339],[143.733,150.508],[144.454,151.571],[145.184,152.631],[145.894,153.702],[146.384,154.912],[147.224,155.903],[147.762,157.083],[148.409,158.193],[149.057,159.306],[149.745,160.434],[150.827,159.673],[151.765,158.788],[152.798,158.022],[153.785,157.199],[154.662,156.238],[155.668,155.437],[156.671,154.632],[157.703,153.866],[158.674,153.017]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (3)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[176.054,154.982],[175.458,153.811],[174.618,152.821],[174.047,151.662],[173.418,150.54],[172.797,149.412],[171.997,148.397],[171.41,147.247],[170.834,146.091],[170.129,145.016],[169.425,144.006],[168.518,144.838],[167.441,145.55],[166.531,146.469],[165.431,147.149],[164.511,148.057],[163.59,148.963],[162.505,149.665],[161.464,150.421],[160.505,151.282],[159.632,152.165],[160.177,153.277],[160.861,154.365],[161.402,155.542],[162.301,156.494],[162.814,157.69],[163.47,158.796],[164.034,159.961],[164.691,161.065],[165.556,162.041],[166.126,163.24],[167.072,162.312],[168.079,161.512],[169.185,160.84],[170.062,159.877],[171.085,159.099],[172.123,158.34],[173.053,157.444],[174.129,156.732],[175.042,155.814]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.925,0.957,0.996]},"r":1,"o":{"a":0,"k":60}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"gr","hd":false,"nm":"Path 1 (2) Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1 (2)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[195.698,138.454],[195.021,137.376],[194.426,136.233],[193.727,135.155],[193.026,134.077],[192.509,132.884],[191.865,131.772],[191.004,130.794],[190.555,129.558],[189.87,128.47],[189.129,127.452],[188.173,128.266],[187.18,129.083],[186.181,129.89],[185.152,130.661],[184.212,131.542],[183.289,132.449],[182.135,133.062],[181.187,133.935],[180.318,134.91],[179.289,135.64],[179.842,136.779],[180.532,137.863],[181.184,138.971],[181.759,140.128],[182.631,141.098],[183.22,142.245],[183.738,143.438],[184.553,144.444],[185.163,145.578],[185.82,146.743],[186.792,145.821],[187.803,145.027],[188.776,144.187],[189.868,143.496],[190.829,142.639],[191.718,141.692],[192.678,140.833],[193.785,140.161],[194.773,139.337]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (2)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[126.963,146.561],[126.265,145.412],[125.673,144.266],[124.924,143.22],[124.12,142.207],[123.472,141.097],[122.858,139.965],[122.295,138.801],[121.553,137.749],[120.882,136.651],[120.317,135.375],[119.286,136.296],[118.257,137.066],[117.305,137.934],[116.311,138.749],[115.314,139.56],[114.44,140.526],[113.363,141.238],[112.318,141.988],[111.363,142.853],[110.438,143.727],[111.11,144.8],[111.802,145.883],[112.363,147.048],[113.036,148.142],[113.633,149.284],[114.304,150.38],[115.105,151.397],[115.551,152.634],[116.275,153.698],[116.973,154.874],[118.052,154.036],[119.04,153.215],[119.874,152.199],[120.972,151.514],[121.967,150.702],[122.925,149.841],[123.943,149.056],[124.984,148.299],[125.919,147.41]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.925,0.957,0.996]},"r":1,"o":{"a":0,"k":60}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"gr","hd":false,"nm":"Path 1 Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[179.472,135.671],[178.685,134.539],[177.98,133.464],[177.379,132.324],[176.82,131.158],[176.116,130.082],[175.425,128.999],[174.726,127.92],[174.125,126.778],[173.492,125.658],[172.765,124.539],[171.702,125.343],[170.859,126.348],[169.779,127.055],[168.821,127.915],[167.806,128.704],[166.82,129.527],[165.769,130.27],[164.807,131.127],[163.873,132.02],[162.767,132.806],[163.575,133.897],[164.065,135.107],[164.823,136.149],[165.478,137.255],[166.036,138.421],[166.709,139.517],[167.516,140.528],[168.036,141.718],[168.849,142.727],[169.467,143.766],[170.477,143.095],[171.348,142.125],[172.485,141.49],[173.313,140.467],[174.362,139.722],[175.398,138.96],[176.451,138.218],[177.285,137.202],[178.357,136.485]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.925,0.957,0.996]},"r":1,"o":{"a":0,"k":60}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"gr","hd":false,"nm":"Path 1 (3) Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1 (3)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[130.234,127.226],[129.582,126.085],[128.817,125.047],[128.26,123.88],[127.464,122.863],[126.908,121.695],[126.293,120.564],[125.482,119.554],[124.887,118.411],[124.343,117.233],[123.598,116.255],[122.588,116.951],[121.559,117.722],[120.691,118.697],[119.61,119.399],[118.705,120.327],[117.708,121.138],[116.662,121.888],[115.758,122.817],[114.751,123.619],[113.779,124.404],[114.499,125.427],[115.011,126.622],[115.686,127.717],[116.291,128.854],[116.922,129.974],[117.799,130.942],[118.281,132.157],[118.98,133.235],[119.768,134.26],[120.29,135.541],[121.388,134.734],[122.35,133.879],[123.331,133.049],[124.303,132.206],[125.163,131.223],[126.328,130.623],[127.3,129.78],[128.158,128.794],[129.281,128.142]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (3)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[146.49,130.014],[145.961,128.895],[145.165,127.878],[144.52,126.765],[143.924,125.623],[143.311,124.489],[142.708,123.351],[141.967,122.298],[141.348,121.17],[140.557,120.147],[140.016,118.847],[138.929,119.71],[138.068,120.693],[137.011,121.427],[135.976,122.191],[135.068,123.113],[134.123,123.991],[133.067,124.728],[132.07,125.539],[131.164,126.466],[130.238,127.226],[130.778,128.301],[131.481,129.376],[132.021,130.554],[132.857,131.547],[133.412,132.715],[133.995,133.868],[134.723,134.928],[135.331,136.063],[135.969,137.183],[136.672,138.343],[137.684,137.436],[138.723,136.68],[139.629,135.753],[140.646,134.967],[141.711,134.242],[142.586,133.277],[143.653,132.554],[144.548,131.615],[145.505,130.755]]}}},{"ty":"sh","hd":false,"nm":"Path 1 (3)","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[162.984,132.843],[162.344,131.704],[161.727,130.573],[161.016,129.503],[160.38,128.385],[159.616,127.347],[159.125,126.139],[158.422,125.061],[157.726,123.98],[157.032,122.897],[156.373,121.796],[155.459,122.708],[154.376,123.411],[153.401,124.25],[152.475,125.151],[151.356,125.805],[150.395,126.663],[149.526,127.635],[148.38,128.26],[147.427,129.129],[146.525,130.02],[147.033,131.189],[147.697,132.289],[148.452,133.333],[149.06,134.468],[149.833,135.5],[150.55,136.568],[151.02,137.791],[151.627,138.927],[152.436,139.937],[153.094,140.915],[153.965,140.12],[155.051,139.423],[155.984,138.531],[157.082,137.846],[158.051,137.002],[158.948,136.064],[159.908,135.205],[160.916,134.408],[161.929,133.614]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.925,0.957,0.996]},"r":1,"o":{"a":0,"k":60}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[144.8,189.52]},"o":{"a":0,"k":60},"p":{"a":0,"k":[144.8,189.52]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-4722-8N90-mask","layers":[{"ddd":0,"ind":189,"ty":4,"nm":"center","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[-100000,-100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":190,"ty":4,"nm":"Mask Group","parent":189,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Mask Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Mask","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[11.142,0],[372.142,0],[372.142,471],[11.142,471]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.85,0.85,0.85]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-4722-8N90-masked","layers":[{"ddd":0,"ind":193,"ty":4,"nm":"center","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[-100000,-100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":194,"ty":4,"nm":"Mask Group","parent":193,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Path 1 Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[165.623,262.632],[164.303,262.566],[162.979,262.537],[161.743,261.983],[160.456,261.736],[159.155,261.558],[157.83,261.528],[156.575,261.09],[155.255,261.029],[153.971,260.754],[152.656,260.66],[151.401,260.228],[150.104,260.029],[148.798,259.886],[147.488,259.766],[146.172,259.674],[144.884,259.428],[143.648,258.882],[142.322,258.854],[141.069,258.398],[139.747,258.35],[138.442,258.197],[137.191,257.734],[135.842,257.836],[134.546,257.636],[133.309,257.088],[131.999,256.968],[130.669,256.962],[129.437,256.388],[128.126,256.267],[126.786,256.323],[125.552,255.761],[124.21,255.827],[122.942,255.461],[121.654,255.206],[120.324,255.204],[119.068,254.766],[117.798,254.411],[116.5,254.223],[115.153,254.317],[113.858,254.107],[112.581,253.793],[111.308,253.459],[110.017,253.217],[108.73,252.961],[107.44,252.724],[106.139,252.552],[104.814,252.516],[103.544,252.157],[102.236,252.025],[100.99,251.522],[99.64,251.624],[98.404,251.07],[97.106,250.929],[95.988,250.263],[94.735,249.774],[94.061,248.607],[93.189,247.654],[92.711,246.458],[92.091,245.279],[92.087,243.96],[92.377,242.694],[92.657,241.429],[92.664,240.117],[92.838,238.835],[92.906,237.533],[93.502,236.323],[93.347,234.984],[93.893,233.765],[93.898,232.453],[94.004,231.158],[94.356,229.906],[94.658,228.645],[94.684,227.337],[95.274,226.125],[95.266,224.811],[95.337,223.51],[95.85,222.285],[95.788,220.962],[96.312,219.739],[96.372,218.437],[96.61,217.165],[96.788,215.883],[96.851,214.581],[97.361,213.355],[97.297,212.032],[97.841,210.812],[98.117,209.547],[98.205,208.249],[98.567,206.999],[98.533,205.679],[98.72,204.398],[99.181,203.165],[99.418,201.892],[99.484,200.591],[99.838,199.34],[99.787,198.019],[100.106,196.762],[100.3,195.482],[100.48,194.2],[100.762,192.936],[100.969,191.659],[101.286,190.4],[101.432,189.114],[101.437,187.802],[101.964,186.578],[101.867,185.25],[102.139,183.984],[102.364,182.71],[102.881,181.486],[102.951,180.185],[103.158,178.908],[103.255,177.612],[103.494,176.34],[103.626,175.05],[103.958,173.794],[104.458,172.567],[104.477,171.257],[104.551,169.957],[104.882,168.7],[105.009,167.409],[105.351,166.155],[105.44,164.858],[105.616,163.575],[105.886,162.309],[106.248,161.057],[106.435,159.776],[106.814,158.529],[106.833,157.219],[107.236,155.974],[107.171,154.651],[107.676,153.424],[107.938,152.156],[108.103,150.871],[108.223,149.579],[108.267,148.273],[108.581,147.014],[108.783,145.736],[109.136,144.482],[109.352,143.207],[109.417,141.905],[109.899,140.675],[109.886,139.36],[110.005,138.067],[110.414,136.824],[110.599,135.542],[110.818,134.267],[111.149,133.01],[111.467,131.751],[111.296,130.408],[111.496,129.129],[112.039,127.909],[112.047,126.598],[112.489,125.36],[112.502,124.048],[112.961,122.813],[112.953,121.497],[113.153,120.187],[113.785,119.019],[114.68,118.054],[115.684,117.25],[116.54,116.218],[117.789,115.771],[119.043,115.366],[120.362,115.334],[121.668,115.435],[122.973,115.585],[124.264,115.808],[125.57,115.955],[126.854,116.223],[128.144,116.461],[129.387,116.97],[130.743,116.825],[131.989,117.31],[133.317,117.33],[134.599,117.615],[135.905,117.759],[137.166,118.162],[138.437,118.508],[139.774,118.472],[141.065,118.706],[142.315,119.177],[143.629,119.267],[144.897,119.632],[146.234,119.595],[147.549,119.691],[148.811,120.092],[150.116,120.241],[151.388,120.585],[152.677,120.828],[153.986,120.956],[155.292,121.101],[156.599,121.239],[157.889,121.477],[159.171,121.761],[160.426,122.205],[161.709,122.484],[163.024,122.571],[164.315,122.805],[165.602,123.062],[166.902,123.24],[168.221,123.311],[169.474,123.766],[170.809,123.739],[172.085,124.057],[173.341,124.497],[174.652,124.608],[175.958,124.753],[177.257,124.95],[178.553,125.149],[179.804,125.617],[181.095,125.855],[182.401,125.997],[183.749,125.9],[185.04,126.137],[186.308,126.504],[187.597,126.757],[188.882,127.029],[190.124,127.399],[191.245,128.028],[192.467,128.54],[193.383,129.482],[194.095,130.576],[194.826,131.684],[195.12,132.974],[195.154,134.283],[194.884,135.55],[194.923,136.869],[194.391,138.09],[194.17,139.365],[194.338,140.707],[193.907,141.946],[193.626,143.21],[193.55,144.51],[193.288,145.778],[193.045,147.049],[192.925,148.341],[192.824,149.636],[192.553,150.903],[192.303,152.173],[192.053,153.443],[191.633,154.683],[191.455,155.965],[191.143,157.225],[191.154,158.539],[190.98,159.822],[190.637,161.076],[190.533,162.371],[190.168,163.621],[190.087,164.92],[189.708,166.168],[189.496,167.444],[189.442,168.748],[189.232,170.024],[189.085,171.312],[188.733,172.565],[188.66,173.866],[188.228,175.104],[188.188,176.41],[187.632,177.628],[187.44,178.906],[187.396,180.211],[187.222,181.494],[186.784,182.731],[186.817,184.049],[186.642,185.332],[186.391,186.602],[186.136,187.871],[186.031,189.164],[185.684,190.418],[185.543,191.707],[185.389,192.992],[184.924,194.225],[184.858,195.527],[184.462,196.772],[184.43,198.079],[184.215,199.355],[184.003,200.631],[183.834,201.915],[183.573,203.183],[183.313,204.451],[183.13,205.732],[182.836,206.996],[182.689,208.282],[182.438,209.553],[181.946,210.781],[181.905,212.087],[181.569,213.342],[181.449,214.634],[181.075,215.883],[181.082,217.198],[181.012,218.499],[180.439,219.713],[180.345,221.01],[179.95,222.256],[180.142,223.602],[179.643,224.829],[179.56,226.128],[179.355,227.406],[178.876,228.637],[178.986,229.969],[178.802,231.251],[178.545,232.52],[178.023,233.743],[177.831,235.023],[177.734,236.319],[177.347,237.566],[177.411,238.89],[176.963,240.127],[176.913,241.431],[176.544,242.681],[176.467,243.981],[176.098,245.231],[175.881,246.507],[175.848,247.815],[175.536,249.075],[175.355,250.357],[175.333,251.666],[175.019,252.926],[174.659,254.178],[174.424,255.452],[174.264,256.739],[173.977,258.01],[173.216,259.077],[172.627,260.228],[171.636,261.062],[170.639,261.887],[169.518,262.598],[168.234,262.961],[166.9,263.079]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[1,1,1]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-4717-8N90-mask","layers":[{"ddd":0,"ind":200,"ty":4,"nm":"center","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[-100000,-100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":201,"ty":4,"nm":"Mask Group","parent":200,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Mask Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Mask","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[11.142,0],[372.142,0],[372.142,471],[11.142,471]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.85,0.85,0.85]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]},{"id":"el-4717-8N90-masked","layers":[{"ddd":0,"ind":204,"ty":4,"nm":"center","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[-100000,-100000]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":205,"ty":4,"nm":"Mask Group","parent":204,"hd":false,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Path 1 Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[167.859,310.286],[165.721,309.657],[163.491,309.387],[161.408,308.546],[159.131,308.447],[157.014,307.736],[154.964,306.766],[152.648,306.833],[150.602,305.849],[148.31,305.804],[146.11,305.419],[144.101,304.291],[141.955,303.694],[139.687,303.571],[137.631,302.611],[135.331,302.614],[133.284,301.649],[131.171,300.923],[128.856,300.986],[126.763,300.182],[124.726,299.161],[122.423,299.178],[120.386,298.158],[118.282,297.381],[115.955,297.491],[113.932,296.417],[111.657,296.323],[109.512,295.723],[107.432,294.856],[105.156,294.765],[103.023,294.118],[100.867,293.546],[98.637,293.276],[96.632,292.134],[94.455,291.644],[92.351,290.882],[90.218,290.22],[87.836,290.545],[85.858,289.28],[83.658,288.895],[81.368,288.858],[79.418,287.482],[77.299,286.766],[75.106,286.352],[72.936,285.837],[70.756,285.377],[68.416,285.519],[66.499,284.001],[64.316,283.533],[62.001,283.597],[60.263,282.064],[58.329,281.101],[56.29,280.237],[54.256,279.237],[53.229,277.118],[51.851,275.444],[50.498,273.75],[48.977,272.101],[48.523,269.888],[47.403,267.97],[46.973,265.81],[46.303,263.683],[46.666,261.45],[46.656,259.274],[46.715,257.074],[46.921,254.858],[47.164,252.618],[47.776,250.472],[49.112,248.507],[49.202,246.227],[50.314,244.21],[50.453,241.946],[50.843,239.743],[51.977,237.731],[51.755,235.371],[52.256,233.197],[53.594,231.237],[53.906,229.014],[53.941,226.72],[55.345,224.773],[55.653,222.549],[55.542,220.217],[56.912,218.266],[56.78,215.929],[57.681,213.857],[58.315,211.717],[58.344,209.421],[59.417,207.393],[60.29,205.314],[60.763,203.129],[60.563,200.774],[61.089,198.606],[61.865,196.502],[62.688,194.41],[63.289,192.262],[63.927,190.118],[64.521,187.968],[64.954,185.776],[65.063,183.497],[65.95,181.421],[66.286,179.204],[67.513,177.216],[67.292,174.856],[68.644,172.9],[68.745,170.619],[69.636,168.545],[69.459,166.192],[70.868,164.251],[71.051,161.995],[71.805,159.885],[71.699,157.551],[72.533,155.462],[73.524,153.413],[74.223,151.286],[74.273,148.995],[74.727,146.809],[74.918,144.551],[76.374,142.622],[76.043,140.229],[77.373,138.268],[77.42,135.977],[77.707,133.744],[78.577,131.664],[79.329,129.55],[79.429,127.272],[80.865,125.334],[80.883,123.035],[81.084,120.78],[82.34,118.799],[82.226,116.467],[83.507,114.489],[83.687,112.232],[84.288,110.079],[84.747,107.894],[84.93,105.634],[86.175,103.65],[86.061,101.318],[87.388,99.356],[88.079,97.226],[88.323,94.986],[89.221,92.913],[89.171,90.597],[89.651,88.413],[90.781,86.4],[91.381,84.247],[91.574,81.993],[92.451,79.912],[92.366,77.587],[93.157,75.483],[93.651,73.306],[94.117,71.119],[94.823,68.993],[95.349,66.825],[96.14,64.722],[96.52,62.512],[96.572,60.218],[97.857,58.241],[97.664,55.889],[98.345,53.756],[98.914,51.599],[100.178,49.617],[100.379,47.362],[100.91,45.191],[101.172,42.951],[101.777,40.8],[102.129,38.583],[102.954,36.483],[104.178,34.491],[104.347,32.258],[104.86,30.068],[106.134,28.222],[107.153,26.23],[108.758,24.671],[110.058,22.843],[111.673,21.291],[113.554,20.08],[115.635,19.252],[117.589,18.235],[119.776,17.855],[121.766,16.816],[124.001,16.9],[126.146,16.047],[128.329,16.794],[130.467,17.14],[132.613,17.454],[134.83,17.774],[137.093,17.911],[139.192,18.691],[141.359,19.205],[143.438,20.079],[145.596,20.626],[147.847,20.813],[149.843,21.995],[152.142,21.996],[154.364,22.31],[156.405,23.316],[158.581,23.794],[160.739,24.344],[162.827,25.167],[164.926,25.958],[167.318,25.582],[169.487,26.089],[171.445,27.418],[173.731,27.468],[175.751,28.553],[178.035,28.615],[180.045,29.74],[182.344,29.756],[184.585,29.979],[186.692,30.73],[188.74,31.707],[190.811,32.595],[193.187,32.31],[195.267,33.161],[197.464,33.561],[199.569,34.315],[201.735,34.848],[203.936,35.229],[206.097,35.784],[208.299,36.162],[210.431,36.827],[212.578,37.417],[214.562,38.661],[216.945,38.335],[218.938,39.524],[221.224,39.59],[223.342,40.298],[225.55,40.666],[227.598,41.659],[229.679,42.507],[231.998,42.444],[234.155,43.028],[236.162,44.182],[238.335,44.506],[240.208,45.66],[242.441,46.1],[244.479,47.055],[245.947,48.779],[247.669,50.177],[248.892,52.04],[250.129,53.846],[251.445,55.623],[252.563,57.551],[253.521,59.583],[254.055,61.767],[254.267,63.998],[253.875,66.239],[253.688,68.41],[253.765,70.618],[253.29,72.765],[252.65,74.904],[252.199,77.091],[252.006,79.345],[250.892,81.365],[250.932,83.675],[250.149,85.777],[249.072,87.804],[248.782,90.033],[248.413,92.241],[247.912,94.416],[247.415,96.591],[246.27,98.601],[245.676,100.751],[245.31,102.964],[245.521,105.321],[244.931,107.473],[244.029,109.545],[243.399,111.686],[242.723,113.816],[242.065,115.95],[241.26,118.046],[241.345,120.372],[240.723,122.515],[240.035,124.645],[239.91,126.916],[239.205,129.038],[238.443,131.146],[237.502,133.207],[237.562,135.526],[236.26,137.495],[235.701,139.658],[236.066,142.055],[235.007,144.087],[234.304,146.213],[234.086,148.46],[233.428,150.594],[232.816,152.74],[232.497,154.962],[232.221,157.198],[231.541,159.326],[230.915,161.469],[230.284,163.614],[229.25,165.652],[228.792,167.837],[228.018,169.946],[228.008,172.246],[227.56,174.434],[226.708,176.522],[226.429,178.754],[225.523,180.825],[225.293,183.072],[224.36,185.136],[223.823,187.301],[223.657,189.566],[223.124,191.732],[222.737,193.939],[221.867,196.019],[221.66,198.269],[220.596,200.303],[220.466,202.577],[219.11,204.532],[218.617,206.708],[218.48,208.98],[218.029,211.167],[216.954,213.199],[216.997,215.513],[216.545,217.704],[215.916,219.845],[215.271,221.987],[214.988,224.217],[214.129,226.3],[213.757,228.508],[213.355,230.711],[212.218,232.722],[212.024,234.976],[211.048,237.028],[210.939,239.308],[210.388,241.469],[209.85,243.638],[209.414,245.829],[208.758,247.968],[208.107,250.104],[207.634,252.289],[206.904,254.405],[206.516,256.613],[205.886,258.758],[204.684,260.752],[204.547,263.024],[203.716,265.118],[203.393,267.342],[202.47,269.408],[202.451,271.71],[202.246,273.964],[200.854,275.91],[200.595,278.151],[199.621,280.208],[200.039,282.622],[198.818,284.615],[198.584,286.862],[198.061,289.035],[196.886,291.044],[197.113,293.409],[196.537,295.58],[195.606,297.617],[193.928,299.226],[192.806,301.084],[191.688,302.989],[189.887,304.287],[188.693,306.253],[186.587,307.09],[184.912,308.565],[182.774,309.171],[180.837,310.254],[178.626,310.46],[176.485,310.825],[174.334,311.439],[172.135,311.169],[169.942,311.008]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.176,0.227,0.29]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"gr","hd":false,"nm":"Path 1 Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[38.394,253.098],[42.186,240.196],[46.196,227.349],[49.616,214.351],[52.676,201.256],[55.8,188.182],[60.286,175.457],[62.794,162.225],[66.655,149.336],[69.507,136.192],[72.047,122.968],[75.31,109.929],[78.66,96.909],[82.818,84.096],[86.892,71.266],[89.183,57.974],[92.283,44.89],[96.29,32.038],[101.394,20.605],[110.52,11.818],[122.686,8.743],[134.793,9.926],[147.939,12.076],[160.611,16.078],[173.744,18.283],[186.398,22.353],[199.112,26.202],[212.349,28],[224.837,32.732],[237.831,35.494],[248.775,41.145],[257.983,49.668],[260.545,61.938],[260.486,74.184],[257.181,87.211],[253.546,100.155],[250.841,113.336],[247.867,126.453],[245.087,139.616],[240.64,152.35],[237.792,165.495],[234.117,178.432],[230.042,191.262],[227.624,204.518],[223.366,217.301],[219.748,230.252],[217.029,243.43],[214.009,256.535],[210.43,269.496],[206.98,282.491],[203.549,295.49],[198.486,306.777],[189.569,315.626],[177.489,318.768],[164.96,319.394],[152.532,314.444],[139.622,311.371],[126.644,308.564],[113.986,304.508],[100.663,303.028],[88.046,298.817],[75.19,295.518],[62.53,291.453],[51.617,286.204],[42.008,278.067],[37.934,265.927]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.651,0.769,0.91]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]}],"ddd":0,"fr":30,"h":180,"ip":0,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"ํ๋ค","hd":true,"sr":1,"ks":{"a":{"a":1,"k":[{"t":0,"s":[126.775,93.172],"i":{"x":0.88,"y":0.77},"o":{"x":0.5,"y":0}},{"t":21,"s":[126.765,93.172],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"o":{"a":0,"k":100},"p":{"a":1,"k":[{"t":0,"s":[159.63,88.876],"i":{"x":0.88,"y":0.77},"o":{"x":0.5,"y":0},"ti":[0,0],"to":[0,0]},{"t":12,"s":[151.916,97.447],"i":{"x":0.88,"y":0.77},"o":{"x":0.5,"y":0},"ti":[0,0],"to":[0,0]},{"t":21,"s":[155.63,86.876],"i":{"x":0.88,"y":0.77},"o":{"x":0.5,"y":0},"ti":[0,0],"to":[0,0]},{"t":33.3,"s":[149.63,94.876],"i":{"x":0.88,"y":0.77},"o":{"x":0.5,"y":0},"ti":[0,0],"to":[0,0]},{"t":48,"s":[148.63,94.876],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"r":{"a":1,"k":[{"t":0,"s":[-7],"i":{"x":0.88,"y":0.77},"o":{"x":0.5,"y":0}},{"t":12,"s":[-13.571],"i":{"x":0.88,"y":0.77},"o":{"x":0.5,"y":0}},{"t":21,"s":[-7],"i":{"x":0.88,"y":0.77},"o":{"x":0.5,"y":0}},{"t":33.3,"s":[-17],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"s":{"a":0,"k":[92.398,92.398]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"sh","hd":true,"nm":"ํ๋ค","d":1,"ks":{"a":1,"k":[{"t":0,"s":[{"c":false,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[189.598,25.821],[191.285,25.848],[192.797,26.559],[194.108,27.613],[195.842,27.924],[197.126,28.987],[198.435,30.006],[199.521,31.258],[200.742,32.387],[201.219,34.058],[202.38,35.246],[203.241,36.638],[203.773,38.19],[204.309,39.72],[205.164,41.155],[205.431,42.772],[205.834,44.354],[206.136,45.954]]}],"i":{"x":0.88,"y":0.77},"o":{"x":0.5,"y":0}},{"t":21,"s":[{"c":false,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[189.527,25.821],[191.214,25.848],[192.725,26.559],[194.036,27.613],[195.769,27.924],[197.053,28.987],[198.361,30.006],[199.447,31.258],[200.667,32.387],[201.144,34.058],[202.305,35.246],[203.165,36.638],[203.697,38.19],[204.233,39.72],[205.088,41.155],[205.354,42.772],[205.758,44.354],[206.059,45.954]]}],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]}},{"ty":"sh","hd":true,"nm":"ํ๋ค","d":1,"ks":{"a":1,"k":[{"t":0,"s":[{"c":false,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[201.102,18.631],[202.643,18.652],[204.023,19.211],[205.221,20.038],[206.803,20.283],[207.976,21.119],[209.171,21.919],[210.163,22.903],[211.278,23.791],[211.714,25.104],[212.773,26.035],[213.56,27.129],[214.046,28.348],[214.534,29.551],[215.316,30.679],[215.559,31.949],[215.927,33.192],[216.202,34.449]]}],"i":{"x":0.88,"y":0.77},"o":{"x":0.5,"y":0}},{"t":21,"s":[{"c":false,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[201.027,18.631],[202.567,18.652],[203.947,19.211],[205.144,20.038],[206.726,20.283],[207.899,21.119],[209.093,21.919],[210.085,22.903],[211.199,23.791],[211.635,25.104],[212.693,26.035],[213.48,27.129],[213.966,28.348],[214.455,29.551],[215.236,30.679],[215.478,31.949],[215.847,33.192],[216.122,34.449]]}],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]}},{"ty":"sh","hd":true,"nm":"ํ๋ค","d":1,"ks":{"a":1,"k":[{"t":0,"s":[{"c":false,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[63.951,160.524],[62.264,160.497],[60.753,159.786],[59.441,158.732],[57.707,158.421],[56.423,157.358],[55.115,156.339],[54.028,155.087],[52.808,153.958],[52.33,152.287],[51.169,151.1],[50.308,149.708],[49.775,148.155],[49.24,146.625],[48.385,145.19],[48.118,143.573],[47.715,141.992],[47.413,140.391]]}],"i":{"x":0.88,"y":0.77},"o":{"x":0.5,"y":0}},{"t":21,"s":[{"c":false,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[63.927,160.524],[62.241,160.497],[60.73,159.786],[59.419,158.732],[57.685,158.421],[56.402,157.358],[55.094,156.339],[54.008,155.087],[52.788,153.958],[52.311,152.287],[51.15,151.1],[50.289,149.708],[49.757,148.155],[49.222,146.625],[48.367,145.19],[48.1,143.573],[47.697,141.992],[47.396,140.391]]}],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]}},{"ty":"sh","hd":true,"nm":"ํ๋ค","d":1,"ks":{"a":1,"k":[{"t":0,"s":[{"c":false,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[52.447,167.714],[50.907,167.694],[49.526,167.135],[48.328,166.307],[46.745,166.063],[45.573,165.227],[44.379,164.427],[43.386,163.443],[42.272,162.555],[41.836,161.242],[40.777,160.31],[39.99,159.216],[39.504,157.997],[39.016,156.794],[38.233,155.666],[37.991,154.396],[37.622,153.153],[37.347,151.896]]}],"i":{"x":0.88,"y":0.77},"o":{"x":0.5,"y":0}},{"t":21,"s":[{"c":false,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[52.428,167.714],[50.888,167.694],[49.508,167.135],[48.31,166.307],[46.728,166.063],[45.556,165.227],[44.362,164.427],[43.37,163.443],[42.257,162.555],[41.82,161.242],[40.761,160.31],[39.975,159.216],[39.489,157.997],[39.001,156.794],[38.219,155.666],[37.977,154.396],[37.608,153.153],[37.333,151.896]]}],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]}},{"ty":"st","hd":false,"bm":0,"c":{"a":0,"k":[0.996,1,0.396]},"lc":2,"lj":2,"ml":4,"o":{"a":0,"k":60},"w":{"a":0,"k":5.42197}}]},{"ddd":0,"ind":4,"ty":0,"nm":"แแ
ฅแผแแ
ฉแผ","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[21.5,16]},"o":{"a":1,"k":[{"t":0,"s":[0],"i":{"x":0.88,"y":0.77},"o":{"x":0.5,"y":0}},{"t":48,"s":[0],"i":{"x":0.88,"y":0.77},"o":{"x":0.5,"y":0}},{"t":53.1,"s":[100],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"p":{"a":1,"k":[{"t":0,"s":[169.383,50.519],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":48,"s":[157.007,57.519],"i":{"x":0.88,"y":0.77},"o":{"x":0.5,"y":0},"ti":[0,0],"to":[0,0]},{"t":53.1,"s":[156.383,57.519],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":60.6,"s":[156.383,57.519],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":65.1,"s":[156.383,57.519],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"r":{"a":1,"k":[{"t":0,"s":[-1],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":48,"s":[-9.642],"i":{"x":0.88,"y":0.77},"o":{"x":0.5,"y":0}},{"t":53.1,"s":[-11],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"s":{"a":1,"k":[{"t":0,"s":[80,80],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":29.1,"s":[60,60],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":48,"s":[70,70],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":53.1,"s":[70,70],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":65.1,"s":[60,60],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":32,"refId":"el-5276-8N90","w":43},{"ddd":0,"ind":7,"ty":0,"nm":"10","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[12.5,12]},"o":{"a":1,"k":[{"t":0,"s":[0],"i":{"x":0.88,"y":0.77},"o":{"x":0.5,"y":0}},{"t":29.1,"s":[0],"i":{"x":0.88,"y":0.77},"o":{"x":0.5,"y":0}},{"t":33.3,"s":[100],"i":{"x":0.88,"y":0.77},"o":{"x":0.5,"y":0}},{"t":48,"s":[0],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"p":{"a":1,"k":[{"t":0,"s":[170.383,49.519],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":29.1,"s":[165.085,53.519],"i":{"x":0.88,"y":0.77},"o":{"x":0.5,"y":0},"ti":[0,0],"to":[0,0]},{"t":33.3,"s":[156.383,56.519],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":48,"s":[156.383,56.212],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":60.6,"s":[156.383,57.438],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":65.1,"s":[156.383,56.519],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"r":{"a":1,"k":[{"t":0,"s":[-7],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":29.1,"s":[3],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":33.3,"s":[-9],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"s":{"a":1,"k":[{"t":0,"s":[80,80],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":29.1,"s":[60,60],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":33.3,"s":[60,60],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":48,"s":[60,60],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":60.6,"s":[60,60],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":65.1,"s":[60,60],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":24,"refId":"el-5253-8N90","w":25},{"ddd":0,"ind":10,"ty":0,"nm":"4","hd":true,"sr":1,"ks":{"a":{"a":0,"k":[10,11]},"o":{"a":0,"k":100},"p":{"a":0,"k":[185.5,155]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":22,"refId":"el-5242-8N90","w":20},{"ddd":0,"ind":13,"ty":0,"nm":"3","hd":true,"sr":1,"ks":{"a":{"a":0,"k":[8.5,10.5]},"o":{"a":0,"k":100},"p":{"a":0,"k":[214.5,154]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":21,"refId":"el-5200-8N90","w":17},{"ddd":0,"ind":209,"ty":0,"nm":"แแ
ฅแซแแ
ฆ","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[210.07,235.5]},"o":{"a":0,"k":100},"p":{"a":0,"k":[189.906,122.019]},"r":{"a":1,"k":[{"t":0,"s":[0],"i":{"x":0.88,"y":0.77},"o":{"x":0.5,"y":0}},{"t":12,"s":[-12],"i":{"x":0.88,"y":0.77},"o":{"x":0.5,"y":0}},{"t":21,"s":[0],"i":{"x":0.88,"y":0.77},"o":{"x":0.5,"y":0}},{"t":33.3,"s":[-12],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"s":{"a":0,"k":[47.567,47.567]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"h":471,"refId":"el-4715-8N90","w":420.1394958496094},{"ddd":0,"ind":210,"ty":4,"nm":"Screen","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[163.5,90]},"o":{"a":0,"k":100},"p":{"a":0,"k":[163.5,177]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":91,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Screen Group","bm":0,"it":[{"ty":"rc","hd":false,"nm":"Screen","d":1,"p":{"a":0,"k":[163.5,90]},"r":{"a":0,"k":0},"s":{"a":0,"k":[327,180]}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[1,1,1]},"r":1,"o":{"a":0,"k":0}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}],"meta":{"g":"@phase-software/lottie-exporter 0.7.0"},"nm":"","op":90,"v":"5.6.0","w":327}
\ No newline at end of file
diff --git a/core/designsystem/src/main/res/raw/mission_tap.json b/core/designsystem/src/main/res/raw/mission_tap.json
new file mode 100644
index 00000000..f8572947
--- /dev/null
+++ b/core/designsystem/src/main/res/raw/mission_tap.json
@@ -0,0 +1 @@
+{"assets":[{"id":"el-159-_-Uo","layers":[{"ddd":0,"ind":19,"ty":4,"nm":"Layer 1","hd":true,"sr":1,"ks":{"a":{"a":0,"k":[38.633,28.203]},"o":{"a":0,"k":100},"p":{"a":0,"k":[38.633,28.203]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":37,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":true,"nm":"Path 1 (4) Group","bm":0,"it":[{"ty":"sh","hd":true,"nm":"Path 1 (4)","d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[-0.485,-0.112],[-0.137,-0.272],[0.121,-0.528],[0.287,-0.128],[0.097,-0.085],[0.364,0.016],[0.151,0.08],[-0.015,0.688],[-0.561,0.304]],"o":[[0.318,-0.192],[0.5,0.096],[0.181,0.384],[-0.107,0.512],[-0.115,0.059],[-0.06,0.064],[-0.364,-0.016],[-0.546,-0.256],[0.015,-0.688],[0,0]],"v":[[50.177,32.693],[51.382,32.573],[52.337,33.125],[52.427,34.493],[51.837,35.453],[51.518,35.669],[50.882,35.741],[50.109,35.597],[49.313,34.181],[50.177,32.693]]}}},{"ty":"sh","hd":true,"nm":"Path 1 (4)","d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[-0.121,0.08],[-0.333,0],[-0.288,-0.08],[-0.257,0],[-0.273,-0.112],[-0.09,-0.128],[-0.303,-0.352],[-0.197,-0.144],[-0.046,-0.384],[-0.075,-0.176],[0.122,-0.23],[0.008,-0.143],[0.091,-0.208],[0.03,-0.352],[0.137,-0.16],[0.455,-0.336],[0.47,-0.096],[0.682,0.176],[0.292,0.02],[0.212,0.16],[0.182,0.091],[0.122,0.12],[0,0.048],[0.257,0.288],[0.11,0.161],[0.091,0.416],[-0.137,0.544],[0,0.208],[-0.045,0.176],[-0.061,0.192],[-0.293,0.117],[-0.091,0.114],[-0.132,0.039],[-0.319,0.304]],"o":[[0.304,-0.288],[0.121,-0.08],[0.333,0],[0.273,0.08],[0.227,0],[0.273,0.112],[0.425,0.544],[0.061,0.096],[0.379,0.272],[0,0.16],[0.093,0.243],[-0.067,0.126],[0,0.112],[-0.243,0.512],[-0.03,0.192],[-0.136,0.144],[-0.606,0.432],[-0.455,0.08],[-0.283,-0.076],[-0.318,-0.016],[-0.166,-0.117],[-0.136,-0.104],[-0.167,-0.176],[0,-0.032],[-0.132,-0.143],[-0.045,-0.112],[-0.091,-0.416],[0.075,-0.336],[0.015,-0.208],[0.06,-0.16],[0.079,-0.305],[0.133,-0.059],[0.091,-0.103],[0.106,-0.016],[0,0]],"v":[[41.76,27.461],[42.397,26.909],[43.079,26.789],[44.011,26.909],[44.807,27.029],[45.557,27.197],[46.102,27.557],[47.194,28.901],[47.58,29.261],[48.217,30.245],[48.33,30.749],[48.285,31.493],[48.171,31.901],[48.035,32.381],[47.625,33.677],[47.375,34.205],[46.489,34.925],[44.875,35.717],[43.17,35.573],[42.306,35.429],[41.511,35.165],[40.988,34.853],[40.601,34.517],[40.351,34.181],[39.965,33.701],[39.601,33.245],[39.396,32.453],[39.465,31.013],[39.578,30.197],[39.669,29.621],[39.851,29.093],[40.442,28.421],[40.783,28.157],[41.124,27.941],[41.761,27.461]]}}},{"ty":"sh","hd":true,"nm":"Path 1 (4)","d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0.454,-0.112],[0.288,-0.224],[0.052,-0.086],[0.016,-0.129],[0.077,-0.144],[0.031,-0.096],[0,-0.272],[-0.485,-0.496],[-0.485,0.112],[-0.167,0],[-0.091,0.048],[-0.227,0],[-0.106,0.336],[-0.091,0.128],[-0.03,0.416],[0.849,0.272],[0.154,0.104],[0.137,0.075]],"o":[[-0.228,-0.112],[-0.455,0.096],[-0.083,0.056],[-0.029,0.127],[-0.03,0.16],[-0.136,0.208],[-0.03,0.096],[0,0.64],[0.485,0.496],[0.273,-0.064],[0.151,-0.016],[0.091,-0.064],[0.425,0],[0.045,-0.16],[0.107,-0.128],[0.091,-1.248],[-0.178,-0.055],[-0.12,-0.1],[0,0]],"v":[[44.217,28.877],[43.194,28.877],[42.08,29.357],[41.875,29.573],[41.807,29.957],[41.647,30.413],[41.397,30.869],[41.352,31.421],[42.08,33.125],[43.535,33.701],[44.194,33.605],[44.558,33.509],[45.035,33.413],[45.831,32.909],[46.035,32.477],[46.24,31.661],[45.103,29.381],[44.603,29.141],[44.217,28.877]]}}},{"ty":"sh","hd":true,"nm":"Path 1 (4)","d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[-0.348,-0.08],[-0.288,0.048],[-0.227,-0.064],[-0.712,0],[-0.454,0.048],[-0.182,-0.096],[-0.212,0.032],[-0.227,-0.08],[-0.151,0.064],[-0.379,-0.112],[-0.106,-0.224],[0.303,-0.288],[0.652,0.144],[0.194,-0.049],[0.772,0.032],[0.152,-0.112],[0.061,-0.24],[-0.03,-0.192],[0.058,-0.153],[0,-0.224],[0.031,-0.352],[-0.061,-0.768],[0.045,-0.224],[-0.045,-0.286],[0.106,-1.216],[-0.06,-0.48],[0.075,-0.224],[0.287,-0.064],[0.121,0.416],[0.079,0.163],[-0.046,0.16],[0,1.072],[0.076,0.144],[0.005,0.102],[-0.045,0.288],[-0.016,0.432],[-0.015,0.128],[-0.015,0.544],[-0.061,0.56],[0.03,0.208],[0.175,0.102],[0.53,0.032],[0.288,-0.048],[0.197,0.064],[0.545,-0.112],[0.273,0.32],[0.015,0.304],[-0.045,0.144],[-0.289,0.112],[-0.181,-0.08],[-0.318,-0.016],[-0.197,0.048],[-0.182,0.016]],"o":[[0.455,-0.064],[0.288,0.064],[0.288,-0.048],[0.182,0.048],[0.728,0],[0.243,-0.016],[0.197,0.08],[0.212,-0.032],[0.227,0.064],[0.258,-0.112],[0.379,0.112],[0.242,0.512],[-0.303,0.272],[-0.194,-0.049],[-0.197,0.064],[-1.319,-0.08],[-0.09,0.08],[-0.06,0.224],[0.013,0.163],[-0.06,0.192],[0,0.176],[-0.045,0.304],[0.06,0.784],[-0.045,0.286],[0.03,0.384],[-0.06,0.688],[0.06,0.496],[-0.076,0.224],[-0.607,0.176],[-0.056,-0.172],[-0.107,-0.176],[0.06,-0.24],[0.016,-1.088],[-0.054,-0.087],[0,-0.08],[0.06,-0.416],[0.03,-0.624],[0.015,-0.192],[0,-0.4],[0.061,-0.624],[-0.025,-0.201],[-0.107,-0.08],[-0.531,-0.048],[-0.288,0.048],[-0.258,-0.096],[-0.424,0.096],[-0.045,-0.048],[-0.015,-0.32],[0.06,-0.176],[0.288,-0.112],[0.137,0.064],[0.333,0],[0.243,-0.064],[0,0]],"v":[[28.159,20.693],[29.363,20.717],[30.227,20.741],[31,20.765],[32.341,20.837],[34.114,20.765],[34.751,20.885],[35.365,20.957],[36.024,21.029],[36.592,21.029],[37.547,21.029],[38.275,21.533],[38.184,22.733],[36.752,22.925],[36.16,22.925],[34.706,22.973],[32.5,23.021],[32.273,23.501],[32.228,24.125],[32.159,24.605],[32.069,25.229],[32.023,26.021],[32.046,27.629],[32.069,29.141],[32.069,30.005],[31.955,32.405],[31.955,34.157],[31.932,35.237],[31.387,35.669],[30.295,35.309],[30.091,34.805],[30,34.301],[30.09,32.333],[30,30.485],[29.909,30.197],[29.977,29.645],[30.091,28.373],[30.159,27.245],[30.204,26.141],[30.295,24.701],[30.341,23.453],[30.023,22.973],[29.068,22.805],[27.84,22.805],[27.113,22.781],[25.908,22.805],[24.862,22.469],[24.772,21.941],[24.817,21.245],[25.34,20.813],[26.044,20.765],[26.726,20.885],[27.522,20.813],[28.159,20.693]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.239,0.259,0.294]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"gr","hd":true,"nm":"Path 1 Group","bm":0,"it":[{"ty":"sh","hd":true,"nm":"Path 1","d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[-4.091,0.264],[-0.792,1.308],[-0.424,1.179],[-0.952,3.38],[-1.926,-0.633],[-5.175,-3.921],[-1.642,0.295],[-3.385,0.078],[-3.023,-0.025],[0,-2.811],[0.803,-3.235],[1.714,-2.38],[-0.125,-2.702],[-0.096,-1.036],[1.571,-2.667],[2.879,0.554],[5.408,2.839],[1.645,-1.726],[3.323,-2.424],[5.002,0.47],[0,2.701],[-0.282,1.373],[1.978,1.221],[3.056,2.308],[0,1.972],[-1.682,1.258]],"o":[[3.35,-2.506],[1.108,-1.057],[0.638,-1.053],[1.182,-3.281],[0.575,-2.044],[6.016,1.978],[1.291,0.977],[3.325,-0.6],[3.023,-0.07],[2.153,0.018],[0,3.331],[-0.747,3.005],[-1.486,2.063],[0.048,1.03],[0.285,3.075],[-1.763,2.993],[-5.936,-1.143],[-1.794,-0.942],[-2.877,3.022],[-4.26,3.106],[-1.904,-0.18],[0,-1.396],[0.416,-2.02],[-3.242,-2.001],[-1.583,-1.195],[0,-2.147],[0,0]],"v":[[3.407,23.315],[14.353,18.27],[17.51,15.165],[18.782,11.232],[22.424,1.447],[31.175,0.57],[48.305,8.768],[53,12.18],[63.045,10.568],[72.126,10.568],[76.732,12.961],[75.724,22.676],[70.898,30.281],[68.046,37.01],[68.814,40.019],[67.871,50.847],[59.054,53.027],[41.111,47.008],[36.286,45.539],[26.898,53.548],[14.22,56.178],[8.43,53.618],[8.451,49.165],[12.378,37.342],[2.99,30.708],[0.533,27.178],[3.407,23.315]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[1,0.949,0.49]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}]}],"ddd":0,"fr":30,"h":180,"ip":0,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"ํธ์ง","hd":false,"sr":1,"ks":{"a":{"a":1,"k":[{"t":0,"s":[0,-21.883],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[0,-21.883],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":12.6,"s":[0,-21.883],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":16.2,"s":[0,-21.883],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":22.8,"s":[0,-21.883],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"o":{"a":0,"k":100},"p":{"a":1,"k":[{"t":0,"s":[163.518,85.533],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":4.2,"s":[163.518,75.533],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":8.4,"s":[163.585,61.68],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":12.6,"s":[164.317,82.27],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":16.2,"s":[163.849,82.27],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":22.8,"s":[162.71,82.701],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"r":{"a":1,"k":[{"t":0,"s":[0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":4.2,"s":[1],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":12.6,"s":[-3],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":16.2,"s":[0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":19.2,"s":[-3],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":22.8,"s":[-1],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":24.6,"s":[0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":27,"s":[2],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":30,"s":[1],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":30.6,"s":[0],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"s":{"a":1,"k":[{"t":0,"s":[58.252,58.252],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":16.2,"s":[64.707,64.707],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":19.2,"s":[70.488,70.488],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":30.6,"s":[84.855,84.855],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":37,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"์ด๋ฆฌ๋ ๋ถ๋ถ Group","bm":0,"it":[{"ty":"gr","hd":false,"nm":"์ด๋ฆฌ๋๋ถ๋ถ_์ Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"์ด๋ฆฌ๋๋ถ๋ถ_์ ","d":1,"ks":{"a":1,"k":[{"t":0,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[152.96,607.943],[149.567,611.868],[173.751,622.035],[194.591,636.937],[215.222,652.147],[233.018,671.363],[257.422,681.221],[275.009,700.736],[298.095,712.471],[320.153,725.655],[340.305,741.535],[360.177,757.794],[379.95,774.223],[403.156,785.779],[423.208,801.799],[441.693,820.046],[464.21,832.6],[484.522,848.26],[506.68,861.314],[526.203,878.092],[549.868,889.009],[569.521,905.618],[594.334,918.541],[622.44,917.882],[649.499,908.634],[669.561,893.753],[690.352,879.89],[710.963,865.788],[729.797,849.139],[748.921,832.919],[772.057,822.393],[792.358,807.831],[808.977,788.046],[832.562,778.159],[849.989,759.522],[872.856,748.616],[893.457,734.484],[912.052,717.515],[935.916,708.007],[951.966,687.413],[976.33,678.624],[996.022,663.194],[1014.418,645.925],[1035.308,632.193],[1058.155,621.246],[1050.948,605.966],[1026.006,602.2],[1001.063,604.198],[976.12,607.574],[951.187,605.127],[926.245,603.858],[901.302,606.805],[876.359,601.881],[851.427,601.681],[826.474,604.837],[801.541,607.014],[776.598,601.971],[751.656,602.68],[726.713,603.069],[701.77,607.024],[676.827,603.888],[651.885,604.987],[626.942,603.239],[601.999,605.516],[577.056,605.297],[552.114,604.957],[527.161,600.932],[502.218,601.122],[477.276,608.013],[452.333,606.006],[427.38,602.79],[402.437,601.132],[377.495,600.423],[352.552,600.513],[327.609,606.964],[302.656,606.285],[277.714,603.689],[252.761,601.292],[227.818,606.115],[202.866,601.232],[177.743,601.911]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[152.96,406.676],[149.567,409.302],[173.751,416.103],[194.591,426.071],[215.222,436.246],[233.018,449.1],[257.422,455.694],[275.009,468.749],[298.095,476.599],[320.153,485.418],[340.305,496.041],[360.177,506.917],[379.95,517.908],[403.156,525.638],[423.208,536.354],[441.693,548.56],[464.21,556.958],[484.522,567.433],[506.68,576.165],[526.203,587.389],[549.868,594.692],[569.521,605.802],[594.334,614.447],[622.44,614.006],[649.499,607.82],[669.561,597.865],[690.352,588.592],[710.963,579.159],[729.797,568.021],[748.921,557.171],[772.057,550.13],[792.358,540.389],[808.977,527.154],[832.562,520.54],[849.989,508.073],[872.856,500.778],[893.457,491.324],[912.052,479.973],[935.916,473.613],[951.966,459.837],[976.33,453.958],[996.022,443.635],[1014.418,432.084],[1035.308,422.897],[1058.155,415.575],[1050.948,405.353],[1026.006,402.835],[1001.063,404.171],[976.12,406.429],[951.187,404.792],[926.245,403.944],[901.302,405.915],[876.359,402.621],[851.427,402.487],[826.474,404.598],[801.541,406.055],[776.598,402.681],[751.656,403.155],[726.713,403.416],[701.77,406.062],[676.827,403.964],[651.885,404.699],[626.942,403.53],[601.999,405.053],[577.056,404.906],[552.114,404.679],[527.161,401.986],[502.218,402.113],[477.276,406.723],[452.333,405.38],[427.38,403.229],[402.437,402.12],[377.495,401.645],[352.552,401.706],[327.609,406.021],[302.656,405.567],[277.714,403.83],[252.761,402.227],[227.818,405.454],[202.866,402.187],[177.743,402.641]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":12.6,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[134.343,406.676],[131.362,409.302],[152.603,416.103],[170.907,426.071],[189.027,436.246],[204.657,449.1],[226.091,455.694],[241.537,468.749],[261.813,476.599],[281.187,485.418],[298.886,496.041],[316.339,506.917],[333.705,517.908],[354.087,525.638],[371.698,536.354],[387.934,548.56],[407.71,556.958],[425.55,567.433],[445.011,576.165],[462.158,587.389],[482.942,594.692],[500.203,605.802],[521.996,614.447],[546.682,614.006],[570.447,607.82],[588.067,597.865],[606.328,588.592],[624.43,579.159],[640.972,568.021],[657.768,557.171],[678.088,550.13],[695.919,540.389],[710.515,527.154],[731.229,520.54],[746.535,508.073],[766.619,500.778],[784.712,491.324],[801.044,479.973],[822.004,473.613],[836.1,459.837],[857.499,453.958],[874.794,443.635],[890.951,432.084],[909.298,422.897],[929.364,415.575],[923.035,405.353],[901.128,402.835],[879.221,404.171],[857.314,406.429],[835.416,404.792],[813.509,403.944],[791.603,405.915],[769.696,402.621],[747.797,402.487],[725.882,404.598],[703.984,406.055],[682.077,402.681],[660.17,403.155],[638.263,403.416],[616.356,406.062],[594.449,403.964],[572.542,404.699],[550.635,403.53],[528.729,405.053],[506.822,404.906],[484.915,404.679],[462.999,401.986],[441.092,402.113],[419.185,406.723],[397.278,405.38],[375.363,403.229],[353.456,402.12],[331.549,401.645],[309.642,401.706],[287.735,406.021],[265.819,405.567],[243.913,403.83],[221.997,402.227],[200.09,405.454],[178.174,402.187],[156.11,402.641]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":16.2,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[146.512,505.491],[143.261,508.755],[166.426,517.209],[186.388,529.599],[206.149,542.246],[223.195,558.224],[246.57,566.42],[263.415,582.646],[285.528,592.404],[306.657,603.366],[325.959,616.569],[344.994,630.089],[363.933,643.75],[386.16,653.358],[405.367,666.678],[423.073,681.85],[444.641,692.289],[464.096,705.309],[485.32,716.163],[504.02,730.114],[526.688,739.191],[545.512,753.001],[569.279,763.747],[596.201,763.199],[622.119,755.509],[641.335,743.135],[661.249,731.609],[680.991,719.884],[699.031,706.04],[717.349,692.554],[739.51,683.801],[758.956,671.694],[774.873,655.243],[797.464,647.021],[814.157,631.526],[836.059,622.458],[855.792,610.707],[873.603,596.598],[896.462,588.692],[911.835,571.569],[935.171,564.261],[954.034,551.431],[971.654,537.073],[991.663,525.654],[1013.547,516.552],[1006.644,503.847],[982.753,500.716],[958.862,502.377],[934.97,505.184],[911.089,503.149],[887.198,502.095],[863.306,504.544],[839.415,500.451],[815.533,500.285],[791.633,502.908],[767.751,504.719],[743.86,500.525],[719.969,501.115],[696.077,501.439],[672.186,504.727],[648.295,502.12],[624.404,503.033],[600.512,501.58],[576.621,503.473],[552.73,503.291],[528.839,503.008],[504.938,499.662],[481.047,499.82],[457.155,505.549],[433.264,503.88],[409.363,501.206],[385.472,499.827],[361.581,499.238],[337.69,499.313],[313.798,504.677],[289.898,504.113],[266.006,501.954],[242.106,499.961],[218.214,503.972],[194.313,499.911],[170.25,500.475]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":19.2,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[141.098,430.387],[137.967,433.165],[160.276,440.363],[179.5,450.912],[198.531,461.681],[214.947,475.284],[237.458,482.263],[253.681,496.078],[274.977,504.386],[295.325,513.719],[313.913,524.961],[332.245,536.472],[350.484,548.103],[371.89,556.284],[390.387,567.625],[407.438,580.542],[428.209,589.43],[446.946,600.516],[467.385,609.758],[485.394,621.636],[507.224,629.364],[525.353,641.122],[548.241,650.271],[574.168,649.805],[599.129,643.257],[617.635,632.722],[636.813,622.909],[655.825,612.925],[673.199,601.139],[690.84,589.656],[712.182,582.204],[730.909,571.895],[746.239,557.889],[767.995,550.889],[784.07,537.695],[805.163,529.974],[824.167,519.97],[841.319,507.957],[863.333,501.226],[878.138,486.647],[900.613,480.425],[918.778,469.501],[935.747,457.276],[955.017,447.554],[976.092,439.804],[969.444,428.987],[946.436,426.321],[923.428,427.735],[900.419,430.125],[877.42,428.393],[854.412,427.495],[831.403,429.581],[808.395,426.095],[785.396,425.954],[762.378,428.188],[739.379,429.729],[716.371,426.159],[693.362,426.66],[670.354,426.936],[647.346,429.736],[624.337,427.516],[601.329,428.294],[578.321,427.057],[555.312,428.669],[532.304,428.513],[509.296,428.273],[486.278,425.423],[463.27,425.558],[440.261,430.436],[417.253,429.015],[394.236,426.738],[371.227,425.564],[348.219,425.063],[325.211,425.126],[302.202,429.694],[279.185,429.213],[256.176,427.375],[233.159,425.678],[210.15,429.093],[187.133,425.635],[163.959,426.116]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":22.8,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[135.509,390.319],[132.503,392.839],[153.928,399.367],[172.391,408.934],[190.668,418.7],[206.434,431.037],[228.054,437.366],[243.634,449.895],[264.086,457.43],[283.628,465.894],[301.48,476.089],[319.086,486.529],[336.602,497.077],[357.161,504.496],[374.925,514.781],[391.301,526.496],[411.25,534.557],[429.244,544.611],[448.874,552.992],[466.17,563.764],[487.135,570.773],[504.545,581.436],[526.528,589.734],[551.428,589.311],[575.399,583.373],[593.173,573.819],[611.591,564.918],[629.851,555.865],[646.536,545.175],[663.478,534.761],[683.975,528.003],[701.96,518.654],[716.683,505.951],[737.577,499.603],[753.016,487.638],[773.274,480.636],[791.525,471.563],[807.998,460.668],[829.14,454.564],[843.359,441.342],[864.943,435.699],[882.389,425.792],[898.685,414.705],[917.192,405.888],[937.432,398.86],[931.048,389.05],[908.951,386.632],[886.854,387.915],[864.757,390.082],[842.669,388.511],[820.571,387.697],[798.475,389.588],[776.378,386.427],[754.289,386.299],[732.183,388.325],[710.095,389.723],[687.998,386.485],[665.901,386.94],[643.804,387.19],[621.707,389.73],[599.61,387.716],[577.513,388.421],[555.416,387.299],[533.318,388.761],[511.221,388.62],[489.124,388.402],[467.018,385.818],[444.921,385.94],[422.824,390.364],[400.727,389.076],[378.621,387.011],[356.524,385.946],[334.427,385.491],[312.33,385.549],[290.233,389.691],[268.127,389.255],[246.03,387.588],[223.924,386.049],[201.827,389.146],[179.721,386.011],[157.465,386.446]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":24.6,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[135.509,348.79],[132.503,351.042],[153.928,356.876],[172.391,365.425],[190.668,374.151],[206.434,385.176],[228.054,390.831],[243.634,402.028],[264.086,408.761],[283.628,416.324],[301.48,425.435],[319.086,434.763],[336.602,444.189],[357.161,450.819],[374.925,460.01],[391.301,470.478],[411.25,477.681],[429.244,486.665],[448.874,494.154],[466.17,503.781],[487.135,510.044],[504.545,519.573],[526.528,526.987],[551.428,526.609],[575.399,521.303],[593.173,512.766],[611.591,504.812],[629.851,496.722],[646.536,487.17],[663.478,477.864],[683.975,471.825],[701.96,463.47],[716.683,452.119],[737.577,446.447],[753.016,435.755],[773.274,429.497],[791.525,421.389],[807.998,411.654],[829.14,406.199],[843.359,394.384],[864.943,389.342],[882.389,380.489],[898.685,370.582],[917.192,362.703],[937.432,356.423],[931.048,347.656],[908.951,345.496],[886.854,346.642],[864.757,348.578],[842.669,347.174],[820.571,346.447],[798.475,348.137],[776.378,345.312],[754.289,345.198],[732.183,347.008],[710.095,348.258],[687.998,345.364],[665.901,345.771],[643.804,345.994],[621.707,348.263],[599.61,346.464],[577.513,347.094],[555.416,346.092],[533.318,347.398],[511.221,347.272],[489.124,347.077],[467.018,344.768],[444.921,344.877],[422.824,348.831],[400.727,347.679],[378.621,345.834],[356.524,344.882],[334.427,344.476],[312.33,344.527],[290.233,348.229],[268.127,347.839],[246.03,346.349],[223.924,344.974],[201.827,347.742],[179.721,344.94],[157.465,345.329]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":27,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[135.509,403.714],[132.503,406.32],[153.928,413.072],[172.391,422.967],[190.668,433.068],[206.434,445.829],[228.054,452.375],[243.634,465.334],[264.086,473.127],[283.628,481.882],[301.48,492.427],[319.086,503.225],[336.602,514.135],[357.161,521.809],[374.925,532.447],[391.301,544.564],[411.25,552.901],[429.244,563.3],[448.874,571.968],[466.17,583.111],[487.135,590.36],[504.545,601.389],[526.528,609.971],[551.428,609.533],[575.399,603.392],[593.173,593.51],[611.591,584.304],[629.851,574.94],[646.536,563.883],[663.478,553.112],[683.975,546.122],[701.96,536.452],[716.683,523.314],[737.577,516.748],[753.016,504.372],[773.274,497.13],[791.525,487.745],[807.998,476.477],[829.14,470.163],[843.359,456.487],[864.943,450.651],[882.389,440.404],[898.685,428.937],[917.192,419.817],[937.432,412.548],[931.048,402.401],[908.951,399.9],[886.854,401.227],[864.757,403.468],[842.669,401.843],[820.571,401.001],[798.475,402.957],[776.378,399.688],[754.289,399.555],[732.183,401.651],[710.095,403.097],[687.998,399.748],[665.901,400.218],[643.804,400.477],[621.707,403.104],[599.61,401.021],[577.513,401.751],[555.416,400.59],[533.318,402.102],[511.221,401.956],[489.124,401.73],[467.018,399.058],[444.921,399.184],[422.824,403.76],[400.727,402.427],[378.621,400.292],[356.524,399.19],[334.427,398.72],[312.33,398.779],[290.233,403.064],[268.127,402.613],[246.03,400.888],[223.924,399.297],[201.827,402.5],[179.721,399.257],[157.465,399.708]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":28.2,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[135.509,417.452],[132.503,420.147],[153.928,427.129],[172.391,437.361],[190.668,447.805],[206.434,461],[228.054,467.769],[243.634,481.169],[264.086,489.228],[283.628,498.28],[301.48,509.184],[319.086,520.35],[336.602,531.631],[357.161,539.566],[374.925,550.566],[391.301,563.095],[411.25,571.716],[429.244,582.469],[448.874,591.432],[466.17,602.954],[487.135,610.45],[504.545,621.854],[526.528,630.729],[551.428,630.276],[575.399,623.925],[593.173,613.707],[611.591,604.188],[629.851,594.505],[646.536,583.072],[663.478,571.935],[683.975,564.707],[701.96,554.708],[716.683,541.122],[737.577,534.333],[753.016,521.536],[773.274,514.047],[791.525,504.343],[807.998,492.691],[829.14,486.163],[843.359,472.021],[864.943,465.986],[882.389,455.391],[898.685,443.533],[917.192,434.103],[937.432,426.587],[931.048,416.094],[908.951,413.509],[886.854,414.88],[864.757,417.198],[842.669,415.518],[820.571,414.647],[798.475,416.67],[776.378,413.289],[754.289,413.152],[732.183,415.319],[710.095,416.814],[687.998,413.351],[665.901,413.838],[643.804,414.105],[621.707,416.821],[599.61,414.668],[577.513,415.422],[555.416,414.222],[533.318,415.786],[511.221,415.635],[489.124,415.401],[467.018,412.638],[444.921,412.768],[422.824,417.5],[400.727,416.122],[378.621,413.913],[356.524,412.775],[334.427,412.288],[312.33,412.35],[290.233,416.78],[268.127,416.314],[246.03,414.531],[223.924,412.885],[201.827,416.197],[179.721,412.844],[157.465,413.31]]}],"i":{"x":0.88,"y":0.17},"o":{"x":0.69,"y":0.25}},{"t":30,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[135.509,449.76],[132.503,452.663],[153.928,460.185],[172.391,471.209],[190.668,482.462],[206.434,496.678],[228.054,503.97],[243.634,518.408],[264.086,527.09],[283.628,536.843],[301.48,548.591],[319.086,560.62],[336.602,572.774],[357.161,581.323],[374.925,593.175],[391.301,606.674],[411.25,615.962],[429.244,627.547],[448.874,637.204],[466.17,649.617],[487.135,657.693],[504.545,669.98],[526.528,679.542],[551.428,679.054],[575.399,672.212],[593.173,661.203],[611.591,650.947],[629.851,640.515],[646.536,628.197],[663.478,616.197],[683.975,608.41],[701.96,597.637],[716.683,583],[737.577,575.686],[753.016,561.898],[773.274,553.83],[791.525,543.375],[807.998,530.821],[829.14,523.787],[843.359,508.552],[864.943,502.05],[882.389,490.634],[898.685,477.859],[917.192,467.699],[937.432,459.601],[931.048,448.296],[908.951,445.511],[886.854,446.988],[864.757,449.486],[842.669,447.676],[820.571,446.737],[798.475,448.917],[776.378,445.274],[754.289,445.127],[732.183,447.462],[710.095,449.072],[687.998,445.341],[665.901,445.865],[643.804,446.154],[621.707,449.08],[599.61,446.759],[577.513,447.572],[555.416,446.279],[533.318,447.964],[511.221,447.801],[489.124,447.55],[467.018,444.573],[444.921,444.713],[422.824,449.811],[400.727,448.326],[378.621,445.947],[356.524,444.72],[334.427,444.196],[312.33,444.262],[290.233,449.035],[268.127,448.533],[246.03,446.612],[223.924,444.838],[201.827,448.407],[179.721,444.794],[157.465,445.296]]}],"i":{"x":0.88,"y":0.17},"o":{"x":0.69,"y":0.25}},{"t":30.6,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[135.509,593.414],[132.503,597.245],[153.928,607.169],[172.391,621.714],[190.668,636.561],[206.434,655.318],[228.054,664.939],[243.634,683.988],[264.086,695.444],[283.628,708.312],[301.48,723.812],[319.086,739.683],[336.602,755.72],[357.161,766.999],[374.925,782.636],[391.301,800.447],[411.25,812.701],[429.244,827.986],[448.874,840.728],[466.17,857.106],[487.135,867.762],[504.545,883.973],[526.528,896.589],[551.428,895.945],[575.399,886.918],[593.173,872.392],[611.591,858.861],[629.851,845.096],[646.536,828.845],[663.478,813.012],[683.975,802.738],[701.96,788.524],[716.683,769.212],[737.577,759.561],[753.016,741.37],[773.274,730.724],[791.525,716.93],[807.998,700.367],[829.14,691.086],[843.359,670.984],[864.943,662.405],[882.389,647.344],[898.685,630.488],[917.192,617.083],[937.432,606.399],[931.048,591.483],[908.951,587.808],[886.854,589.757],[864.757,593.053],[842.669,590.664],[820.571,589.426],[798.475,592.302],[776.378,587.496],[754.289,587.301],[732.183,590.382],[710.095,592.507],[687.998,587.584],[665.901,588.276],[643.804,588.656],[621.707,592.517],[599.61,589.455],[577.513,590.528],[555.416,588.822],[533.318,591.045],[511.221,590.83],[489.124,590.498],[467.018,586.57],[444.921,586.755],[422.824,593.482],[400.727,591.522],[378.621,588.383],[356.524,586.764],[334.427,586.073],[312.33,586.16],[290.233,592.458],[268.127,591.795],[246.03,589.26],[223.924,586.921],[201.827,591.629],[179.721,586.862],[157.465,587.525]]}],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]}},{"ty":"st","hd":false,"bm":0,"c":{"a":0,"k":[0.537,0.702,0.871]},"lc":2,"lj":2,"ml":4,"o":{"a":0,"k":100},"w":{"a":0,"k":10.96}},{"ty":"tr","nm":"Transform","a":{"a":1,"k":[{"t":0,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":12.6,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":16.2,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":19.2,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":22.8,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":24.6,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":27,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":28.2,"s":[0,0],"i":{"x":0.88,"y":0.17},"o":{"x":0.69,"y":0.25}},{"t":30,"s":[0,0],"i":{"x":0.88,"y":0.17},"o":{"x":0.69,"y":0.25}},{"t":30.6,"s":[0,0],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"o":{"a":0,"k":100},"p":{"a":1,"k":[{"t":0,"s":[-603.861,-759.482],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[-603.861,-508.046],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":12.6,"s":[-530.363,-508.046],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":16.2,"s":[-578.404,-631.493],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":19.2,"s":[-557.029,-537.667],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":22.8,"s":[-534.968,-487.612],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":24.6,"s":[-534.968,-435.732],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":27,"s":[-534.968,-504.346],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":28.2,"s":[-534.968,-521.508],"i":{"x":0.88,"y":0.17},"o":{"x":0.69,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":30,"s":[-534.968,-561.869],"i":{"x":0.88,"y":0.17},"o":{"x":0.69,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":30.6,"s":[-534.968,-741.331],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"gr","hd":false,"nm":"์ด๋ฆฌ๋๋ถ๋ถ_๋ฉด Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"์ด๋ฆฌ๋๋ถ๋ถ_๋ฉด","d":1,"ks":{"a":1,"k":[{"t":0,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[152.96,607.943],[149.567,611.868],[173.751,622.035],[194.591,636.937],[215.222,652.147],[233.018,671.363],[257.422,681.221],[275.009,700.736],[298.095,712.471],[320.153,725.655],[340.305,741.535],[360.177,757.794],[379.95,774.223],[403.156,785.779],[423.208,801.799],[441.693,820.046],[464.21,832.6],[484.522,848.26],[506.68,861.314],[526.203,878.092],[549.868,889.009],[569.521,905.618],[594.334,918.541],[622.44,917.882],[649.499,908.634],[669.561,893.753],[690.352,879.89],[710.963,865.788],[729.797,849.139],[748.921,832.919],[772.057,822.393],[792.358,807.831],[808.977,788.046],[832.562,778.159],[849.989,759.522],[872.856,748.616],[893.457,734.484],[912.052,717.515],[935.916,708.007],[951.966,687.413],[976.33,678.624],[996.022,663.194],[1014.418,645.925],[1035.308,632.193],[1058.155,621.246],[1050.948,605.966],[1026.006,602.2],[1001.063,604.198],[976.12,607.574],[951.187,605.127],[926.245,603.858],[901.302,606.805],[876.359,601.881],[851.427,601.681],[826.474,604.837],[801.541,607.014],[776.598,601.971],[751.656,602.68],[726.713,603.069],[701.77,607.024],[676.827,603.888],[651.885,604.987],[626.942,603.239],[601.999,605.516],[577.056,605.297],[552.114,604.957],[527.161,600.932],[502.218,601.122],[477.276,608.013],[452.333,606.006],[427.38,602.79],[402.437,601.132],[377.495,600.423],[352.552,600.513],[327.609,606.964],[302.656,606.285],[277.714,603.689],[252.761,601.292],[227.818,606.115],[202.866,601.232],[177.743,601.911]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[152.96,406.676],[149.567,409.302],[173.751,416.103],[194.591,426.071],[215.222,436.246],[233.018,449.1],[257.422,455.694],[275.009,468.749],[298.095,476.599],[320.153,485.418],[340.305,496.041],[360.177,506.917],[379.95,517.908],[403.156,525.638],[423.208,536.354],[441.693,548.56],[464.21,556.958],[484.522,567.433],[506.68,576.165],[526.203,587.389],[549.868,594.692],[569.521,605.802],[594.334,614.447],[622.44,614.006],[649.499,607.82],[669.561,597.865],[690.352,588.592],[710.963,579.159],[729.797,568.021],[748.921,557.171],[772.057,550.13],[792.358,540.389],[808.977,527.154],[832.562,520.54],[849.989,508.073],[872.856,500.778],[893.457,491.324],[912.052,479.973],[935.916,473.613],[951.966,459.837],[976.33,453.958],[996.022,443.635],[1014.418,432.084],[1035.308,422.897],[1058.155,415.575],[1050.948,405.353],[1026.006,402.835],[1001.063,404.171],[976.12,406.429],[951.187,404.792],[926.245,403.944],[901.302,405.915],[876.359,402.621],[851.427,402.487],[826.474,404.598],[801.541,406.055],[776.598,402.681],[751.656,403.155],[726.713,403.416],[701.77,406.062],[676.827,403.964],[651.885,404.699],[626.942,403.53],[601.999,405.053],[577.056,404.906],[552.114,404.679],[527.161,401.986],[502.218,402.113],[477.276,406.723],[452.333,405.38],[427.38,403.229],[402.437,402.12],[377.495,401.645],[352.552,401.706],[327.609,406.021],[302.656,405.567],[277.714,403.83],[252.761,402.227],[227.818,405.454],[202.866,402.187],[177.743,402.641]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":12.6,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[134.343,406.676],[131.362,409.302],[152.603,416.103],[170.907,426.071],[189.027,436.246],[204.657,449.1],[226.091,455.694],[241.537,468.749],[261.813,476.599],[281.187,485.418],[298.886,496.041],[316.339,506.917],[333.705,517.908],[354.087,525.638],[371.698,536.354],[387.934,548.56],[407.71,556.958],[425.55,567.433],[445.011,576.165],[462.158,587.389],[482.942,594.692],[500.203,605.802],[521.996,614.447],[546.682,614.006],[570.447,607.82],[588.067,597.865],[606.328,588.592],[624.43,579.159],[640.972,568.021],[657.768,557.171],[678.088,550.13],[695.919,540.389],[710.515,527.154],[731.229,520.54],[746.535,508.073],[766.619,500.778],[784.712,491.324],[801.044,479.973],[822.004,473.613],[836.1,459.837],[857.499,453.958],[874.794,443.635],[890.951,432.084],[909.298,422.897],[929.364,415.575],[923.035,405.353],[901.128,402.835],[879.221,404.171],[857.314,406.429],[835.416,404.792],[813.509,403.944],[791.603,405.915],[769.696,402.621],[747.797,402.487],[725.882,404.598],[703.984,406.055],[682.077,402.681],[660.17,403.155],[638.263,403.416],[616.356,406.062],[594.449,403.964],[572.542,404.699],[550.635,403.53],[528.729,405.053],[506.822,404.906],[484.915,404.679],[462.999,401.986],[441.092,402.113],[419.185,406.723],[397.278,405.38],[375.363,403.229],[353.456,402.12],[331.549,401.645],[309.642,401.706],[287.735,406.021],[265.819,405.567],[243.913,403.83],[221.997,402.227],[200.09,405.454],[178.174,402.187],[156.11,402.641]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":16.2,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[146.512,505.491],[143.261,508.755],[166.426,517.209],[186.388,529.599],[206.149,542.246],[223.195,558.224],[246.57,566.42],[263.415,582.646],[285.528,592.404],[306.657,603.366],[325.959,616.569],[344.994,630.089],[363.933,643.75],[386.16,653.358],[405.367,666.678],[423.073,681.85],[444.641,692.289],[464.096,705.309],[485.32,716.163],[504.02,730.114],[526.688,739.191],[545.512,753.001],[569.279,763.747],[596.201,763.199],[622.119,755.509],[641.335,743.135],[661.249,731.609],[680.991,719.884],[699.031,706.04],[717.349,692.554],[739.51,683.801],[758.956,671.694],[774.873,655.243],[797.464,647.021],[814.157,631.526],[836.059,622.458],[855.792,610.707],[873.603,596.598],[896.462,588.692],[911.835,571.569],[935.171,564.261],[954.034,551.431],[971.654,537.073],[991.663,525.654],[1013.547,516.552],[1006.644,503.847],[982.753,500.716],[958.862,502.377],[934.97,505.184],[911.089,503.149],[887.198,502.095],[863.306,504.544],[839.415,500.451],[815.533,500.285],[791.633,502.908],[767.751,504.719],[743.86,500.525],[719.969,501.115],[696.077,501.439],[672.186,504.727],[648.295,502.12],[624.404,503.033],[600.512,501.58],[576.621,503.473],[552.73,503.291],[528.839,503.008],[504.938,499.662],[481.047,499.82],[457.155,505.549],[433.264,503.88],[409.363,501.206],[385.472,499.827],[361.581,499.238],[337.69,499.313],[313.798,504.677],[289.898,504.113],[266.006,501.954],[242.106,499.961],[218.214,503.972],[194.313,499.911],[170.25,500.475]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":19.2,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[141.098,430.387],[137.967,433.165],[160.276,440.363],[179.5,450.912],[198.531,461.681],[214.947,475.284],[237.458,482.263],[253.681,496.078],[274.977,504.386],[295.325,513.719],[313.913,524.961],[332.245,536.472],[350.484,548.103],[371.89,556.284],[390.387,567.625],[407.438,580.542],[428.209,589.43],[446.946,600.516],[467.385,609.758],[485.394,621.636],[507.224,629.364],[525.353,641.122],[548.241,650.271],[574.168,649.805],[599.129,643.257],[617.635,632.722],[636.813,622.909],[655.825,612.925],[673.199,601.139],[690.84,589.656],[712.182,582.204],[730.909,571.895],[746.239,557.889],[767.995,550.889],[784.07,537.695],[805.163,529.974],[824.167,519.97],[841.319,507.957],[863.333,501.226],[878.138,486.647],[900.613,480.425],[918.778,469.501],[935.747,457.276],[955.017,447.554],[976.092,439.804],[969.444,428.987],[946.436,426.321],[923.428,427.735],[900.419,430.125],[877.42,428.393],[854.412,427.495],[831.403,429.581],[808.395,426.095],[785.396,425.954],[762.378,428.188],[739.379,429.729],[716.371,426.159],[693.362,426.66],[670.354,426.936],[647.346,429.736],[624.337,427.516],[601.329,428.294],[578.321,427.057],[555.312,428.669],[532.304,428.513],[509.296,428.273],[486.278,425.423],[463.27,425.558],[440.261,430.436],[417.253,429.015],[394.236,426.738],[371.227,425.564],[348.219,425.063],[325.211,425.126],[302.202,429.694],[279.185,429.213],[256.176,427.375],[233.159,425.678],[210.15,429.093],[187.133,425.635],[163.959,426.116]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":22.8,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[135.509,390.319],[132.503,392.839],[153.928,399.367],[172.391,408.934],[190.668,418.7],[206.434,431.037],[228.054,437.366],[243.634,449.895],[264.086,457.43],[283.628,465.894],[301.48,476.089],[319.086,486.529],[336.602,497.077],[357.161,504.496],[374.925,514.781],[391.301,526.496],[411.25,534.557],[429.244,544.611],[448.874,552.992],[466.17,563.764],[487.135,570.773],[504.545,581.436],[526.528,589.734],[551.428,589.311],[575.399,583.373],[593.173,573.819],[611.591,564.918],[629.851,555.865],[646.536,545.175],[663.478,534.761],[683.975,528.003],[701.96,518.654],[716.683,505.951],[737.577,499.603],[753.016,487.638],[773.274,480.636],[791.525,471.563],[807.998,460.668],[829.14,454.564],[843.359,441.342],[864.943,435.699],[882.389,425.792],[898.685,414.705],[917.192,405.888],[937.432,398.86],[931.048,389.05],[908.951,386.632],[886.854,387.915],[864.757,390.082],[842.669,388.511],[820.571,387.697],[798.475,389.588],[776.378,386.427],[754.289,386.299],[732.183,388.325],[710.095,389.723],[687.998,386.485],[665.901,386.94],[643.804,387.19],[621.707,389.73],[599.61,387.716],[577.513,388.421],[555.416,387.299],[533.318,388.761],[511.221,388.62],[489.124,388.402],[467.018,385.818],[444.921,385.94],[422.824,390.364],[400.727,389.076],[378.621,387.011],[356.524,385.946],[334.427,385.491],[312.33,385.549],[290.233,389.691],[268.127,389.255],[246.03,387.588],[223.924,386.049],[201.827,389.146],[179.721,386.011],[157.465,386.446]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":24.6,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[135.509,348.79],[132.503,351.042],[153.928,356.876],[172.391,365.425],[190.668,374.151],[206.434,385.176],[228.054,390.831],[243.634,402.028],[264.086,408.761],[283.628,416.324],[301.48,425.435],[319.086,434.763],[336.602,444.189],[357.161,450.819],[374.925,460.01],[391.301,470.478],[411.25,477.681],[429.244,486.665],[448.874,494.154],[466.17,503.781],[487.135,510.044],[504.545,519.573],[526.528,526.987],[551.428,526.609],[575.399,521.303],[593.173,512.766],[611.591,504.812],[629.851,496.722],[646.536,487.17],[663.478,477.864],[683.975,471.825],[701.96,463.47],[716.683,452.119],[737.577,446.447],[753.016,435.755],[773.274,429.497],[791.525,421.389],[807.998,411.654],[829.14,406.199],[843.359,394.384],[864.943,389.342],[882.389,380.489],[898.685,370.582],[917.192,362.703],[937.432,356.423],[931.048,347.656],[908.951,345.496],[886.854,346.642],[864.757,348.578],[842.669,347.174],[820.571,346.447],[798.475,348.137],[776.378,345.312],[754.289,345.198],[732.183,347.008],[710.095,348.258],[687.998,345.364],[665.901,345.771],[643.804,345.994],[621.707,348.263],[599.61,346.464],[577.513,347.094],[555.416,346.092],[533.318,347.398],[511.221,347.272],[489.124,347.077],[467.018,344.768],[444.921,344.877],[422.824,348.831],[400.727,347.679],[378.621,345.834],[356.524,344.882],[334.427,344.476],[312.33,344.527],[290.233,348.229],[268.127,347.839],[246.03,346.349],[223.924,344.974],[201.827,347.742],[179.721,344.94],[157.465,345.329]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":27,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[135.509,403.714],[132.503,406.32],[153.928,413.072],[172.391,422.967],[190.668,433.068],[206.434,445.829],[228.054,452.375],[243.634,465.334],[264.086,473.127],[283.628,481.882],[301.48,492.427],[319.086,503.225],[336.602,514.135],[357.161,521.809],[374.925,532.447],[391.301,544.564],[411.25,552.901],[429.244,563.3],[448.874,571.968],[466.17,583.111],[487.135,590.36],[504.545,601.389],[526.528,609.971],[551.428,609.533],[575.399,603.392],[593.173,593.51],[611.591,584.304],[629.851,574.94],[646.536,563.883],[663.478,553.112],[683.975,546.122],[701.96,536.452],[716.683,523.314],[737.577,516.748],[753.016,504.372],[773.274,497.13],[791.525,487.745],[807.998,476.477],[829.14,470.163],[843.359,456.487],[864.943,450.651],[882.389,440.404],[898.685,428.937],[917.192,419.817],[937.432,412.548],[931.048,402.401],[908.951,399.9],[886.854,401.227],[864.757,403.468],[842.669,401.843],[820.571,401.001],[798.475,402.957],[776.378,399.688],[754.289,399.555],[732.183,401.651],[710.095,403.097],[687.998,399.748],[665.901,400.218],[643.804,400.477],[621.707,403.104],[599.61,401.021],[577.513,401.751],[555.416,400.59],[533.318,402.102],[511.221,401.956],[489.124,401.73],[467.018,399.058],[444.921,399.184],[422.824,403.76],[400.727,402.427],[378.621,400.292],[356.524,399.19],[334.427,398.72],[312.33,398.779],[290.233,403.064],[268.127,402.613],[246.03,400.888],[223.924,399.297],[201.827,402.5],[179.721,399.257],[157.465,399.708]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":28.2,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[135.509,417.452],[132.503,420.147],[153.928,427.129],[172.391,437.361],[190.668,447.805],[206.434,461],[228.054,467.769],[243.634,481.169],[264.086,489.228],[283.628,498.28],[301.48,509.184],[319.086,520.35],[336.602,531.631],[357.161,539.566],[374.925,550.566],[391.301,563.095],[411.25,571.716],[429.244,582.469],[448.874,591.432],[466.17,602.954],[487.135,610.45],[504.545,621.854],[526.528,630.729],[551.428,630.276],[575.399,623.925],[593.173,613.707],[611.591,604.188],[629.851,594.505],[646.536,583.072],[663.478,571.935],[683.975,564.707],[701.96,554.708],[716.683,541.122],[737.577,534.333],[753.016,521.536],[773.274,514.047],[791.525,504.343],[807.998,492.691],[829.14,486.163],[843.359,472.021],[864.943,465.986],[882.389,455.391],[898.685,443.533],[917.192,434.103],[937.432,426.587],[931.048,416.094],[908.951,413.509],[886.854,414.88],[864.757,417.198],[842.669,415.518],[820.571,414.647],[798.475,416.67],[776.378,413.289],[754.289,413.152],[732.183,415.319],[710.095,416.814],[687.998,413.351],[665.901,413.838],[643.804,414.105],[621.707,416.821],[599.61,414.668],[577.513,415.422],[555.416,414.222],[533.318,415.786],[511.221,415.635],[489.124,415.401],[467.018,412.638],[444.921,412.768],[422.824,417.5],[400.727,416.122],[378.621,413.913],[356.524,412.775],[334.427,412.288],[312.33,412.35],[290.233,416.78],[268.127,416.314],[246.03,414.531],[223.924,412.885],[201.827,416.197],[179.721,412.844],[157.465,413.31]]}],"i":{"x":0.88,"y":0.17},"o":{"x":0.69,"y":0.25}},{"t":30,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[135.509,449.76],[132.503,452.663],[153.928,460.185],[172.391,471.209],[190.668,482.462],[206.434,496.678],[228.054,503.97],[243.634,518.408],[264.086,527.09],[283.628,536.843],[301.48,548.591],[319.086,560.62],[336.602,572.774],[357.161,581.323],[374.925,593.175],[391.301,606.674],[411.25,615.962],[429.244,627.547],[448.874,637.204],[466.17,649.617],[487.135,657.693],[504.545,669.98],[526.528,679.542],[551.428,679.054],[575.399,672.212],[593.173,661.203],[611.591,650.947],[629.851,640.515],[646.536,628.197],[663.478,616.197],[683.975,608.41],[701.96,597.637],[716.683,583],[737.577,575.686],[753.016,561.898],[773.274,553.83],[791.525,543.375],[807.998,530.821],[829.14,523.787],[843.359,508.552],[864.943,502.05],[882.389,490.634],[898.685,477.859],[917.192,467.699],[937.432,459.601],[931.048,448.296],[908.951,445.511],[886.854,446.988],[864.757,449.486],[842.669,447.676],[820.571,446.737],[798.475,448.917],[776.378,445.274],[754.289,445.127],[732.183,447.462],[710.095,449.072],[687.998,445.341],[665.901,445.865],[643.804,446.154],[621.707,449.08],[599.61,446.759],[577.513,447.572],[555.416,446.279],[533.318,447.964],[511.221,447.801],[489.124,447.55],[467.018,444.573],[444.921,444.713],[422.824,449.811],[400.727,448.326],[378.621,445.947],[356.524,444.72],[334.427,444.196],[312.33,444.262],[290.233,449.035],[268.127,448.533],[246.03,446.612],[223.924,444.838],[201.827,448.407],[179.721,444.794],[157.465,445.296]]}],"i":{"x":0.88,"y":0.17},"o":{"x":0.69,"y":0.25}},{"t":30.6,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[135.509,593.414],[132.503,597.245],[153.928,607.169],[172.391,621.714],[190.668,636.561],[206.434,655.318],[228.054,664.939],[243.634,683.988],[264.086,695.444],[283.628,708.312],[301.48,723.812],[319.086,739.683],[336.602,755.72],[357.161,766.999],[374.925,782.636],[391.301,800.447],[411.25,812.701],[429.244,827.986],[448.874,840.728],[466.17,857.106],[487.135,867.762],[504.545,883.973],[526.528,896.589],[551.428,895.945],[575.399,886.918],[593.173,872.392],[611.591,858.861],[629.851,845.096],[646.536,828.845],[663.478,813.012],[683.975,802.738],[701.96,788.524],[716.683,769.212],[737.577,759.561],[753.016,741.37],[773.274,730.724],[791.525,716.93],[807.998,700.367],[829.14,691.086],[843.359,670.984],[864.943,662.405],[882.389,647.344],[898.685,630.488],[917.192,617.083],[937.432,606.399],[931.048,591.483],[908.951,587.808],[886.854,589.757],[864.757,593.053],[842.669,590.664],[820.571,589.426],[798.475,592.302],[776.378,587.496],[754.289,587.301],[732.183,590.382],[710.095,592.507],[687.998,587.584],[665.901,588.276],[643.804,588.656],[621.707,592.517],[599.61,589.455],[577.513,590.528],[555.416,588.822],[533.318,591.045],[511.221,590.83],[489.124,590.498],[467.018,586.57],[444.921,586.755],[422.824,593.482],[400.727,591.522],[378.621,588.383],[356.524,586.764],[334.427,586.073],[312.33,586.16],[290.233,592.458],[268.127,591.795],[246.03,589.26],[223.924,586.921],[201.827,591.629],[179.721,586.862],[157.465,587.525]]}],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.737,0.863,0.992]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":1,"k":[{"t":0,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":12.6,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":16.2,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":19.2,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":22.8,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":24.6,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":27,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":28.2,"s":[0,0],"i":{"x":0.88,"y":0.17},"o":{"x":0.69,"y":0.25}},{"t":30,"s":[0,0],"i":{"x":0.88,"y":0.17},"o":{"x":0.69,"y":0.25}},{"t":30.6,"s":[0,0],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"o":{"a":0,"k":100},"p":{"a":1,"k":[{"t":0,"s":[-603.861,-759.482],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[-603.861,-508.046],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":12.6,"s":[-530.363,-508.046],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":16.2,"s":[-578.404,-631.493],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":19.2,"s":[-557.029,-537.667],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":22.8,"s":[-534.968,-487.612],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":24.6,"s":[-534.968,-435.732],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":27,"s":[-534.968,-504.346],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":28.2,"s":[-534.968,-521.508],"i":{"x":0.88,"y":0.17},"o":{"x":0.69,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":30,"s":[-534.968,-561.869],"i":{"x":0.88,"y":0.17},"o":{"x":0.69,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":30.6,"s":[-534.968,-741.331],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"tr","nm":"Transform","a":{"a":1,"k":[{"t":0,"s":[0,5.274],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":12.6,"s":[0,5.274],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":16.2,"s":[0,5.274],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":19.2,"s":[0,-107.33],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":22.8,"s":[0,-96.847],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":24.6,"s":[0,-85.982],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":27,"s":[0,-52.34],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":28.2,"s":[0,5.274],"i":{"x":0.88,"y":0.17},"o":{"x":0.69,"y":0.25}},{"t":30,"s":[0,-51.22],"i":{"x":0.88,"y":0.17},"o":{"x":0.69,"y":0.25}},{"t":30.6,"s":[0,-151.755],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"o":{"a":1,"k":[{"t":0,"s":[100],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":22.8,"s":[100],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":27,"s":[100],"i":{"x":0.88,"y":0.17},"o":{"x":0.69,"y":0.25}},{"t":30,"s":[100],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"p":{"a":1,"k":[{"t":0,"s":[0.832,-25.463],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":8.4,"s":[0.832,-33.732],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":12.6,"s":[0.731,-33.732],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":16.2,"s":[0.797,-28.198],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":19.2,"s":[-0.179,-56.508],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":22.8,"s":[0.737,-56.508],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":24.6,"s":[0.737,-56.346],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":27,"s":[0.737,-46.1],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":28.2,"s":[0.539,-33.568],"i":{"x":0.88,"y":0.17},"o":{"x":0.69,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":30,"s":[0.055,-43.893],"i":{"x":0.88,"y":0.17},"o":{"x":0.69,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":30.6,"s":[-0.115,-57.943],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"r":{"a":0,"k":0},"s":{"a":0,"k":[21.405,21.405]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"gr","hd":false,"nm":"์ผํฐ Group","bm":0,"it":[{"ty":"gr","hd":false,"nm":"์ผํฐ_์ Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"์ผํฐ_์ ","d":1,"ks":{"a":1,"k":[{"t":0,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[1045.339,1159.138],[1049.132,1153.825],[1024.249,1138.913],[1003.928,1119.238],[983.905,1099.264],[967.726,1075.254],[942.544,1060.652],[926.654,1036.353],[903.288,1019.854],[881.3,1001.916],[861.927,981.262],[842.903,960.229],[824.048,939.016],[800.493,922.716],[781.249,901.922],[764.132,878.891],[741.545,861.583],[721.942,841.149],[699.834,823.331],[681.299,801.769],[657.135,786.099],[638.43,764.715],[615.054,748.196],[586.349,748.915],[559.889,761.659],[539.378,782.183],[517.879,801.679],[496.629,821.424],[477.805,843.716],[458.571,865.578],[433.888,881.728],[413.057,901.922],[397.237,927.37],[371.945,942.881],[355.027,967.17],[330.703,983.699],[309.474,1003.464],[290.959,1026.086],[265.287,1041.207],[250.246,1067.474],[223.886,1081.875],[203.884,1102.939],[185.658,1125.87],[164.029,1145.245],[168.521,1167.238],[196.807,1161.385],[225.093,1165.67],[253.37,1163.393],[281.656,1159.557],[309.933,1162.344],[338.209,1163.782],[366.495,1160.436],[394.772,1166.039],[423.058,1166.259],[451.335,1162.673],[479.621,1160.197],[507.908,1165.929],[536.184,1165.12],[564.47,1164.671],[592.757,1160.187],[621.043,1163.752],[649.32,1162.504],[677.606,1164.481],[705.892,1161.894],[734.179,1162.154],[762.465,1162.534],[790.751,1167.108],[819.028,1166.898],[847.314,1159.058],[875.601,1161.345],[903.887,1164.99],[932.174,1166.878],[960.47,1167.687],[988.756,1167.587],[1016.823,1160.266]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[1045.339,1101.741],[1049.132,1096.691],[1024.249,1082.518],[1003.928,1063.817],[983.905,1044.831],[967.726,1022.01],[942.544,1008.132],[926.654,985.036],[903.288,969.354],[881.3,952.304],[861.927,932.673],[842.903,912.681],[824.048,892.518],[800.493,877.026],[781.249,857.262],[764.132,835.371],[741.545,818.92],[721.942,799.498],[699.834,782.563],[681.299,762.068],[657.135,747.173],[638.43,726.849],[615.054,711.148],[586.349,711.831],[559.889,723.944],[539.378,743.452],[517.879,761.982],[496.629,780.75],[477.805,801.938],[458.571,822.717],[433.888,838.068],[413.057,857.262],[397.237,881.45],[371.945,896.192],[355.027,919.279],[330.703,934.989],[309.474,953.776],[290.959,975.277],[265.287,989.649],[250.246,1014.616],[223.886,1028.304],[203.884,1048.325],[185.658,1070.12],[164.029,1088.536],[168.521,1109.44],[196.807,1103.877],[225.093,1107.949],[253.37,1105.785],[281.656,1102.14],[309.933,1104.788],[338.209,1106.155],[366.495,1102.975],[394.772,1108.301],[423.058,1108.509],[451.335,1105.101],[479.621,1102.747],[507.908,1108.196],[536.184,1107.427],[564.47,1107],[592.757,1102.738],[621.043,1106.127],[649.32,1104.94],[677.606,1106.82],[705.892,1104.361],[734.179,1104.608],[762.465,1104.969],[790.751,1109.316],[819.028,1109.117],[847.314,1101.665],[875.601,1103.839],[903.887,1107.304],[932.174,1109.098],[960.47,1109.867],[988.756,1109.772],[1016.823,1102.814]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":12.6,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[918.108,1101.741],[921.44,1096.691],[899.585,1082.518],[881.737,1063.817],[864.152,1044.831],[849.942,1022.01],[827.825,1008.132],[813.869,985.036],[793.347,969.354],[774.035,952.304],[757.02,932.673],[740.311,912.681],[723.752,892.518],[703.063,877.026],[686.162,857.262],[671.128,835.371],[651.29,818.92],[634.073,799.498],[614.655,782.563],[598.376,762.068],[577.153,747.173],[560.725,726.849],[540.195,711.148],[514.983,711.831],[491.744,723.944],[473.729,743.452],[454.846,761.982],[436.183,780.75],[419.65,801.938],[402.757,822.717],[381.078,838.068],[362.783,857.262],[348.889,881.45],[326.675,896.192],[311.816,919.279],[290.453,934.989],[271.807,953.776],[255.545,975.277],[232.999,989.649],[219.788,1014.616],[196.636,1028.304],[179.069,1048.325],[163.061,1070.12],[144.065,1088.536],[148.01,1109.44],[172.853,1103.877],[197.697,1107.949],[222.532,1105.785],[247.375,1102.14],[272.21,1104.788],[297.045,1106.155],[321.888,1102.975],[346.723,1108.301],[371.567,1108.509],[396.402,1105.101],[421.245,1102.747],[446.089,1108.196],[470.924,1107.427],[495.767,1107],[520.611,1102.738],[545.455,1106.127],[570.289,1104.94],[595.133,1106.82],[619.977,1104.361],[644.82,1104.608],[669.664,1104.969],[694.507,1109.316],[719.342,1109.117],[744.186,1101.665],[769.029,1103.839],[793.873,1107.304],[818.717,1109.098],[843.569,1109.867],[868.412,1109.772],[893.063,1102.814]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":16.2,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[1001.271,1101.741],[1004.904,1096.691],[981.07,1082.518],[961.606,1063.817],[942.428,1044.831],[926.93,1022.01],[902.81,1008.132],[887.59,985.036],[865.209,969.354],[844.147,952.304],[825.591,932.673],[807.369,912.681],[789.31,892.518],[766.747,877.026],[748.315,857.262],[731.919,835.371],[710.284,818.92],[691.507,799.498],[670.331,782.563],[652.578,762.068],[629.432,747.173],[611.516,726.849],[589.126,711.148],[561.631,711.831],[536.286,723.944],[516.64,743.452],[496.047,761.982],[475.693,780.75],[457.662,801.938],[439.239,822.717],[415.597,838.068],[395.644,857.262],[380.491,881.45],[356.265,896.192],[340.061,919.279],[316.762,934.989],[296.427,953.776],[278.693,975.277],[254.104,989.649],[239.696,1014.616],[214.448,1028.304],[195.289,1048.325],[177.831,1070.12],[157.114,1088.536],[161.416,1109.44],[188.51,1103.877],[215.604,1107.949],[242.689,1105.785],[269.783,1102.14],[296.867,1104.788],[323.951,1106.155],[351.045,1102.975],[378.13,1108.301],[405.224,1108.509],[432.308,1105.101],[459.402,1102.747],[486.496,1108.196],[513.58,1107.427],[540.674,1107],[567.768,1102.738],[594.862,1106.127],[621.947,1104.94],[649.04,1106.82],[676.134,1104.361],[703.229,1104.608],[730.322,1104.969],[757.416,1109.316],[784.501,1109.117],[811.595,1101.665],[838.689,1103.839],[865.783,1107.304],[892.876,1109.098],[919.98,1109.867],[947.074,1109.772],[973.958,1102.814]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":22.8,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[926.079,1101.741],[929.439,1096.691],[907.395,1082.518],[889.392,1063.817],[871.654,1044.831],[857.321,1022.01],[835.011,1008.132],[820.934,985.036],[800.234,969.354],[780.754,952.304],[763.592,932.673],[746.738,912.681],[730.035,892.518],[709.167,877.026],[692.119,857.262],[676.954,835.371],[656.944,818.92],[639.577,799.498],[619.991,782.563],[603.571,762.068],[582.164,747.173],[565.593,726.849],[544.884,711.148],[519.454,711.831],[496.013,723.944],[477.842,743.452],[458.795,761.982],[439.97,780.75],[423.293,801.938],[406.254,822.717],[384.387,838.068],[365.932,857.262],[351.917,881.45],[329.511,896.192],[314.523,919.279],[292.974,934.989],[274.166,953.776],[257.764,975.277],[235.021,989.649],[221.696,1014.616],[198.343,1028.304],[180.623,1048.325],[164.477,1070.12],[145.315,1088.536],[149.295,1109.44],[174.354,1103.877],[199.413,1107.949],[224.464,1105.785],[249.523,1102.14],[274.573,1104.788],[299.624,1106.155],[324.683,1102.975],[349.733,1108.301],[374.793,1108.509],[399.843,1105.101],[424.902,1102.747],[449.962,1108.196],[475.012,1107.427],[500.071,1107],[525.13,1102.738],[550.19,1106.127],[575.24,1104.94],[600.299,1106.82],[625.359,1104.361],[650.418,1104.608],[675.477,1104.969],[700.537,1109.316],[725.587,1109.117],[750.646,1101.665],[775.706,1103.839],[800.765,1107.304],[825.824,1109.098],[850.892,1109.867],[875.951,1109.772],[900.816,1102.814]]}],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]}},{"ty":"st","hd":false,"bm":0,"c":{"a":0,"k":[0.537,0.702,0.871]},"lc":2,"lj":2,"ml":4,"o":{"a":0,"k":100},"w":{"a":0,"k":12.46}},{"ty":"tr","nm":"Transform","a":{"a":1,"k":[{"t":0,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":12.6,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":16.2,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":22.8,"s":[0,0],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"o":{"a":0,"k":100},"p":{"a":1,"k":[{"t":0,"s":[-606.581,-957.942],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[-606.581,-910.507],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":12.6,"s":[-532.752,-910.507],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":16.2,"s":[-581.009,-910.507],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":22.8,"s":[-537.377,-910.507],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"gr","hd":false,"nm":"์ผํฐ_๋ฉด Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"์ผํฐ_๋ฉด","d":1,"ks":{"a":1,"k":[{"t":0,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[1045.339,1159.138],[1049.132,1153.825],[1024.249,1138.913],[1003.928,1119.238],[983.905,1099.264],[967.726,1075.254],[942.544,1060.652],[926.654,1036.353],[903.288,1019.854],[881.3,1001.916],[861.927,981.262],[842.903,960.229],[824.048,939.016],[800.493,922.716],[781.249,901.922],[764.132,878.891],[741.545,861.583],[721.942,841.149],[699.834,823.331],[681.299,801.769],[657.135,786.099],[638.43,764.715],[615.054,748.196],[586.349,748.915],[559.889,761.659],[539.378,782.183],[517.879,801.679],[496.629,821.424],[477.805,843.716],[458.571,865.578],[433.888,881.728],[413.057,901.922],[397.237,927.37],[371.945,942.881],[355.027,967.17],[330.703,983.699],[309.474,1003.464],[290.959,1026.086],[265.287,1041.207],[250.246,1067.474],[223.886,1081.875],[203.884,1102.939],[185.658,1125.87],[164.029,1145.245],[168.521,1167.238],[196.807,1161.385],[225.093,1165.67],[253.37,1163.393],[281.656,1159.557],[309.933,1162.344],[338.209,1163.782],[366.495,1160.436],[394.772,1166.039],[423.058,1166.259],[451.335,1162.673],[479.621,1160.197],[507.908,1165.929],[536.184,1165.12],[564.47,1164.671],[592.757,1160.187],[621.043,1163.752],[649.32,1162.504],[677.606,1164.481],[705.892,1161.894],[734.179,1162.154],[762.465,1162.534],[790.751,1167.108],[819.028,1166.898],[847.314,1159.058],[875.601,1161.345],[903.887,1164.99],[932.174,1166.878],[960.47,1167.687],[988.756,1167.587],[1016.823,1160.266]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[1045.339,1101.741],[1049.132,1096.691],[1024.249,1082.518],[1003.928,1063.817],[983.905,1044.831],[967.726,1022.01],[942.544,1008.132],[926.654,985.036],[903.288,969.354],[881.3,952.304],[861.927,932.673],[842.903,912.681],[824.048,892.518],[800.493,877.026],[781.249,857.262],[764.132,835.371],[741.545,818.92],[721.942,799.498],[699.834,782.563],[681.299,762.068],[657.135,747.173],[638.43,726.849],[615.054,711.148],[586.349,711.831],[559.889,723.944],[539.378,743.452],[517.879,761.982],[496.629,780.75],[477.805,801.938],[458.571,822.717],[433.888,838.068],[413.057,857.262],[397.237,881.45],[371.945,896.192],[355.027,919.279],[330.703,934.989],[309.474,953.776],[290.959,975.277],[265.287,989.649],[250.246,1014.616],[223.886,1028.304],[203.884,1048.325],[185.658,1070.12],[164.029,1088.536],[168.521,1109.44],[196.807,1103.877],[225.093,1107.949],[253.37,1105.785],[281.656,1102.14],[309.933,1104.788],[338.209,1106.155],[366.495,1102.975],[394.772,1108.301],[423.058,1108.509],[451.335,1105.101],[479.621,1102.747],[507.908,1108.196],[536.184,1107.427],[564.47,1107],[592.757,1102.738],[621.043,1106.127],[649.32,1104.94],[677.606,1106.82],[705.892,1104.361],[734.179,1104.608],[762.465,1104.969],[790.751,1109.316],[819.028,1109.117],[847.314,1101.665],[875.601,1103.839],[903.887,1107.304],[932.174,1109.098],[960.47,1109.867],[988.756,1109.772],[1016.823,1102.814]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":12.6,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[918.108,1101.741],[921.44,1096.691],[899.585,1082.518],[881.737,1063.817],[864.152,1044.831],[849.942,1022.01],[827.825,1008.132],[813.869,985.036],[793.347,969.354],[774.035,952.304],[757.02,932.673],[740.311,912.681],[723.752,892.518],[703.063,877.026],[686.162,857.262],[671.128,835.371],[651.29,818.92],[634.073,799.498],[614.655,782.563],[598.376,762.068],[577.153,747.173],[560.725,726.849],[540.195,711.148],[514.983,711.831],[491.744,723.944],[473.729,743.452],[454.846,761.982],[436.183,780.75],[419.65,801.938],[402.757,822.717],[381.078,838.068],[362.783,857.262],[348.889,881.45],[326.675,896.192],[311.816,919.279],[290.453,934.989],[271.807,953.776],[255.545,975.277],[232.999,989.649],[219.788,1014.616],[196.636,1028.304],[179.069,1048.325],[163.061,1070.12],[144.065,1088.536],[148.01,1109.44],[172.853,1103.877],[197.697,1107.949],[222.532,1105.785],[247.375,1102.14],[272.21,1104.788],[297.045,1106.155],[321.888,1102.975],[346.723,1108.301],[371.567,1108.509],[396.402,1105.101],[421.245,1102.747],[446.089,1108.196],[470.924,1107.427],[495.767,1107],[520.611,1102.738],[545.455,1106.127],[570.289,1104.94],[595.133,1106.82],[619.977,1104.361],[644.82,1104.608],[669.664,1104.969],[694.507,1109.316],[719.342,1109.117],[744.186,1101.665],[769.029,1103.839],[793.873,1107.304],[818.717,1109.098],[843.569,1109.867],[868.412,1109.772],[893.063,1102.814]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":16.2,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[1001.271,1101.741],[1004.904,1096.691],[981.07,1082.518],[961.606,1063.817],[942.428,1044.831],[926.93,1022.01],[902.81,1008.132],[887.59,985.036],[865.209,969.354],[844.147,952.304],[825.591,932.673],[807.369,912.681],[789.31,892.518],[766.747,877.026],[748.315,857.262],[731.919,835.371],[710.284,818.92],[691.507,799.498],[670.331,782.563],[652.578,762.068],[629.432,747.173],[611.516,726.849],[589.126,711.148],[561.631,711.831],[536.286,723.944],[516.64,743.452],[496.047,761.982],[475.693,780.75],[457.662,801.938],[439.239,822.717],[415.597,838.068],[395.644,857.262],[380.491,881.45],[356.265,896.192],[340.061,919.279],[316.762,934.989],[296.427,953.776],[278.693,975.277],[254.104,989.649],[239.696,1014.616],[214.448,1028.304],[195.289,1048.325],[177.831,1070.12],[157.114,1088.536],[161.416,1109.44],[188.51,1103.877],[215.604,1107.949],[242.689,1105.785],[269.783,1102.14],[296.867,1104.788],[323.951,1106.155],[351.045,1102.975],[378.13,1108.301],[405.224,1108.509],[432.308,1105.101],[459.402,1102.747],[486.496,1108.196],[513.58,1107.427],[540.674,1107],[567.768,1102.738],[594.862,1106.127],[621.947,1104.94],[649.04,1106.82],[676.134,1104.361],[703.229,1104.608],[730.322,1104.969],[757.416,1109.316],[784.501,1109.117],[811.595,1101.665],[838.689,1103.839],[865.783,1107.304],[892.876,1109.098],[919.98,1109.867],[947.074,1109.772],[973.958,1102.814]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":22.8,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[926.079,1101.741],[929.439,1096.691],[907.395,1082.518],[889.392,1063.817],[871.654,1044.831],[857.321,1022.01],[835.011,1008.132],[820.934,985.036],[800.234,969.354],[780.754,952.304],[763.592,932.673],[746.738,912.681],[730.035,892.518],[709.167,877.026],[692.119,857.262],[676.954,835.371],[656.944,818.92],[639.577,799.498],[619.991,782.563],[603.571,762.068],[582.164,747.173],[565.593,726.849],[544.884,711.148],[519.454,711.831],[496.013,723.944],[477.842,743.452],[458.795,761.982],[439.97,780.75],[423.293,801.938],[406.254,822.717],[384.387,838.068],[365.932,857.262],[351.917,881.45],[329.511,896.192],[314.523,919.279],[292.974,934.989],[274.166,953.776],[257.764,975.277],[235.021,989.649],[221.696,1014.616],[198.343,1028.304],[180.623,1048.325],[164.477,1070.12],[145.315,1088.536],[149.295,1109.44],[174.354,1103.877],[199.413,1107.949],[224.464,1105.785],[249.523,1102.14],[274.573,1104.788],[299.624,1106.155],[324.683,1102.975],[349.733,1108.301],[374.793,1108.509],[399.843,1105.101],[424.902,1102.747],[449.962,1108.196],[475.012,1107.427],[500.071,1107],[525.13,1102.738],[550.19,1106.127],[575.24,1104.94],[600.299,1106.82],[625.359,1104.361],[650.418,1104.608],[675.477,1104.969],[700.537,1109.316],[725.587,1109.117],[750.646,1101.665],[775.706,1103.839],[800.765,1107.304],[825.824,1109.098],[850.892,1109.867],[875.951,1109.772],[900.816,1102.814]]}],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.737,0.863,0.992]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":1,"k":[{"t":0,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":12.6,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":16.2,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":22.8,"s":[0,0],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"o":{"a":0,"k":100},"p":{"a":1,"k":[{"t":0,"s":[-606.581,-957.942],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[-606.581,-910.507],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":12.6,"s":[-532.752,-910.507],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":16.2,"s":[-581.009,-910.507],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":22.8,"s":[-537.377,-910.507],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"tr","nm":"Transform","a":{"a":1,"k":[{"t":0,"s":[0,13.331],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[0,13.331],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":12.6,"s":[0,13.331],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":16.2,"s":[0,13.331],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":22.8,"s":[0,13.331],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"o":{"a":0,"k":100},"p":{"a":1,"k":[{"t":0,"s":[1.414,18.743],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":8.4,"s":[1.414,17.956],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":12.6,"s":[1.242,17.956],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":16.2,"s":[1.355,17.956],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":22.8,"s":[1.253,17.956],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"r":{"a":0,"k":0},"s":{"a":0,"k":[21.405,21.405]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"gr","hd":false,"nm":"์ผ์ชฝ Group","bm":0,"it":[{"ty":"gr","hd":false,"nm":"์ผ์ชฝ_์ Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"์ผ์ชฝ_์ ","d":1,"ks":{"a":1,"k":[{"t":0,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[564.62,859.995],[538.739,841.908],[510.083,833.539],[487.506,815.272],[461.156,803.137],[437.501,786.618],[412.858,771.707],[388.953,755.587],[366.855,736.511],[342.172,721.68],[315.672,709.795],[292.466,692.536],[264.359,683.278],[242.201,664.302],[219.155,646.764],[193.174,634.04],[168.341,619.419],[145.215,606.865],[145.215,632.472],[141.851,660.667],[139.665,688.861],[144.297,717.056],[145.684,745.26],[148.219,773.454],[144.197,801.649],[145.434,829.853],[143.029,858.048],[142.57,886.252],[145.454,914.447],[143.348,942.641],[143.219,970.835],[146.812,999.04],[141.801,1027.244],[144.217,1055.449],[140.843,1083.653],[140.334,1111.858],[140.134,1140.062],[159.957,1151.637],[186.826,1138.993],[209.313,1120.297],[232.749,1102.919],[256.204,1085.551],[280.748,1069.671],[304.293,1052.432],[324.335,1030.41],[347.462,1012.583],[371.945,996.633],[398.674,983.739],[422.669,967.11],[441.703,943.7],[466.177,927.73],[491.878,913.408],[515.523,896.309],[535.944,874.607]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[564.62,817.411],[538.739,800.219],[510.083,792.264],[487.506,774.902],[461.156,763.368],[437.501,747.667],[412.858,733.494],[388.953,718.173],[366.855,700.041],[342.172,685.944],[315.672,674.648],[292.466,658.244],[264.359,649.444],[242.201,631.408],[219.155,614.738],[193.174,602.645],[168.341,588.747],[145.215,576.814],[145.215,601.154],[141.851,627.953],[139.665,654.751],[144.297,681.549],[145.684,708.357],[148.219,735.155],[144.197,761.954],[145.434,788.762],[143.029,815.56],[142.57,842.368],[145.454,869.166],[143.348,895.964],[143.219,922.763],[146.812,949.57],[141.801,976.378],[144.217,1003.186],[140.843,1029.994],[140.334,1056.802],[140.134,1083.61],[159.957,1094.612],[186.826,1082.594],[209.313,1064.823],[232.749,1048.306],[256.204,1031.798],[280.748,1016.704],[304.293,1000.319],[324.335,979.388],[347.462,962.443],[371.945,947.283],[398.674,935.027],[422.669,919.222],[441.703,896.971],[466.177,881.792],[491.878,868.179],[515.523,851.927],[535.944,831.299]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":12.6,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[495.899,817.411],[473.168,800.219],[448,792.264],[428.171,774.902],[405.028,763.368],[384.252,747.667],[362.608,733.494],[341.613,718.173],[322.204,700.041],[300.525,685.944],[277.251,674.648],[256.869,658.244],[232.183,649.444],[212.722,631.408],[192.481,614.738],[169.662,602.645],[147.852,588.747],[127.54,576.814],[127.54,601.154],[124.586,627.953],[122.666,654.751],[126.734,681.549],[127.952,708.357],[130.179,735.155],[126.646,761.954],[127.733,788.762],[125.621,815.56],[125.217,842.368],[127.751,869.166],[125.901,895.964],[125.787,922.763],[128.943,949.57],[124.542,976.378],[126.664,1003.186],[123.701,1029.994],[123.254,1056.802],[123.078,1083.61],[140.488,1094.612],[164.087,1082.594],[183.837,1064.823],[204.421,1048.306],[225.021,1031.798],[246.578,1016.704],[267.257,1000.319],[284.86,979.388],[305.171,962.443],[326.675,947.283],[350.151,935.027],[371.225,919.222],[387.942,896.971],[409.437,881.792],[432.01,868.179],[452.778,851.927],[470.713,831.299]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":16.2,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[540.818,817.411],[516.028,800.219],[488.58,792.264],[466.955,774.902],[441.715,763.368],[419.057,747.667],[395.453,733.494],[372.556,718.173],[351.39,700.041],[327.747,685.944],[302.364,674.648],[280.136,658.244],[253.215,649.444],[231.991,631.408],[209.916,614.738],[185.03,602.645],[161.244,588.747],[139.093,576.814],[139.093,601.154],[135.871,627.953],[133.778,654.751],[138.214,681.549],[139.542,708.357],[141.971,735.155],[138.118,761.954],[139.303,788.762],[136.999,815.56],[136.56,842.368],[139.323,869.166],[137.305,895.964],[137.181,922.763],[140.623,949.57],[135.823,976.378],[138.137,1003.186],[134.906,1029.994],[134.418,1056.802],[134.227,1083.61],[153.214,1094.612],[178.95,1082.594],[200.489,1064.823],[222.937,1048.306],[245.404,1031.798],[268.913,1016.704],[291.465,1000.319],[310.663,979.388],[332.814,962.443],[356.265,947.283],[381.868,935.027],[404.851,919.222],[423.082,896.971],[446.524,881.792],[471.142,868.179],[493.791,851.927],[513.351,831.299]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":22.8,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[500.204,817.411],[477.276,800.219],[451.889,792.264],[431.888,774.902],[408.544,763.368],[387.587,747.667],[365.756,733.494],[344.578,718.173],[325.001,700.041],[303.134,685.944],[279.658,674.648],[259.099,658.244],[234.199,649.444],[214.569,631.408],[194.152,614.738],[171.135,602.645],[149.135,588.747],[128.648,576.814],[128.648,601.154],[125.668,627.953],[123.731,654.751],[127.834,681.549],[129.063,708.357],[131.309,735.155],[127.746,761.954],[128.842,788.762],[126.711,815.56],[126.304,842.368],[128.86,869.166],[126.994,895.964],[126.879,922.763],[130.062,949.57],[125.624,976.378],[127.763,1003.186],[124.775,1029.994],[124.324,1056.802],[124.147,1083.61],[141.708,1094.612],[165.511,1082.594],[185.433,1064.823],[206.195,1048.306],[226.975,1031.798],[248.718,1016.704],[269.577,1000.319],[287.333,979.388],[307.82,962.443],[329.511,947.283],[353.191,935.027],[374.448,919.222],[391.31,896.971],[412.992,881.792],[435.761,868.179],[456.708,851.927],[474.8,831.299]]}],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]}},{"ty":"st","hd":false,"bm":0,"c":{"a":0,"k":[0.537,0.702,0.871]},"lc":2,"lj":2,"ml":4,"o":{"a":0,"k":100},"w":{"a":0,"k":11.13}},{"ty":"tr","nm":"Transform","a":{"a":1,"k":[{"t":0,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":12.6,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":16.2,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":22.8,"s":[0,0],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"o":{"a":0,"k":100},"p":{"a":1,"k":[{"t":0,"s":[-352.143,-879.251],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[-352.143,-835.713],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":12.6,"s":[-309.283,-835.713],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":16.2,"s":[-337.298,-835.713],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":22.8,"s":[-311.968,-835.713],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"gr","hd":false,"nm":"์ผ์ชฝ_๋ฉด Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"์ผ์ชฝ_๋ฉด","d":1,"ks":{"a":1,"k":[{"t":0,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[564.62,859.995],[538.739,841.908],[510.083,833.539],[487.506,815.272],[461.156,803.137],[437.501,786.618],[412.858,771.707],[388.953,755.587],[366.855,736.511],[342.172,721.68],[315.672,709.795],[292.466,692.536],[264.359,683.278],[242.201,664.302],[219.155,646.764],[193.174,634.04],[168.341,619.419],[145.215,606.865],[145.215,632.472],[141.851,660.667],[139.665,688.861],[144.297,717.056],[145.684,745.26],[148.219,773.454],[144.197,801.649],[145.434,829.853],[143.029,858.048],[142.57,886.252],[145.454,914.447],[143.348,942.641],[143.219,970.835],[146.812,999.04],[141.801,1027.244],[144.217,1055.449],[140.843,1083.653],[140.334,1111.858],[140.134,1140.062],[159.957,1151.637],[186.826,1138.993],[209.313,1120.297],[232.749,1102.919],[256.204,1085.551],[280.748,1069.671],[304.293,1052.432],[324.335,1030.41],[347.462,1012.583],[371.945,996.633],[398.674,983.739],[422.669,967.11],[441.703,943.7],[466.177,927.73],[491.878,913.408],[515.523,896.309],[535.944,874.607]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[564.62,817.411],[538.739,800.219],[510.083,792.264],[487.506,774.902],[461.156,763.368],[437.501,747.667],[412.858,733.494],[388.953,718.173],[366.855,700.041],[342.172,685.944],[315.672,674.648],[292.466,658.244],[264.359,649.444],[242.201,631.408],[219.155,614.738],[193.174,602.645],[168.341,588.747],[145.215,576.814],[145.215,601.154],[141.851,627.953],[139.665,654.751],[144.297,681.549],[145.684,708.357],[148.219,735.155],[144.197,761.954],[145.434,788.762],[143.029,815.56],[142.57,842.368],[145.454,869.166],[143.348,895.964],[143.219,922.763],[146.812,949.57],[141.801,976.378],[144.217,1003.186],[140.843,1029.994],[140.334,1056.802],[140.134,1083.61],[159.957,1094.612],[186.826,1082.594],[209.313,1064.823],[232.749,1048.306],[256.204,1031.798],[280.748,1016.704],[304.293,1000.319],[324.335,979.388],[347.462,962.443],[371.945,947.283],[398.674,935.027],[422.669,919.222],[441.703,896.971],[466.177,881.792],[491.878,868.179],[515.523,851.927],[535.944,831.299]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":12.6,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[495.899,817.411],[473.168,800.219],[448,792.264],[428.171,774.902],[405.028,763.368],[384.252,747.667],[362.608,733.494],[341.613,718.173],[322.204,700.041],[300.525,685.944],[277.251,674.648],[256.869,658.244],[232.183,649.444],[212.722,631.408],[192.481,614.738],[169.662,602.645],[147.852,588.747],[127.54,576.814],[127.54,601.154],[124.586,627.953],[122.666,654.751],[126.734,681.549],[127.952,708.357],[130.179,735.155],[126.646,761.954],[127.733,788.762],[125.621,815.56],[125.217,842.368],[127.751,869.166],[125.901,895.964],[125.787,922.763],[128.943,949.57],[124.542,976.378],[126.664,1003.186],[123.701,1029.994],[123.254,1056.802],[123.078,1083.61],[140.488,1094.612],[164.087,1082.594],[183.837,1064.823],[204.421,1048.306],[225.021,1031.798],[246.578,1016.704],[267.257,1000.319],[284.86,979.388],[305.171,962.443],[326.675,947.283],[350.151,935.027],[371.225,919.222],[387.942,896.971],[409.437,881.792],[432.01,868.179],[452.778,851.927],[470.713,831.299]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":16.2,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[540.818,817.411],[516.028,800.219],[488.58,792.264],[466.955,774.902],[441.715,763.368],[419.057,747.667],[395.453,733.494],[372.556,718.173],[351.39,700.041],[327.747,685.944],[302.364,674.648],[280.136,658.244],[253.215,649.444],[231.991,631.408],[209.916,614.738],[185.03,602.645],[161.244,588.747],[139.093,576.814],[139.093,601.154],[135.871,627.953],[133.778,654.751],[138.214,681.549],[139.542,708.357],[141.971,735.155],[138.118,761.954],[139.303,788.762],[136.999,815.56],[136.56,842.368],[139.323,869.166],[137.305,895.964],[137.181,922.763],[140.623,949.57],[135.823,976.378],[138.137,1003.186],[134.906,1029.994],[134.418,1056.802],[134.227,1083.61],[153.214,1094.612],[178.95,1082.594],[200.489,1064.823],[222.937,1048.306],[245.404,1031.798],[268.913,1016.704],[291.465,1000.319],[310.663,979.388],[332.814,962.443],[356.265,947.283],[381.868,935.027],[404.851,919.222],[423.082,896.971],[446.524,881.792],[471.142,868.179],[493.791,851.927],[513.351,831.299]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":22.8,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[500.204,817.411],[477.276,800.219],[451.889,792.264],[431.888,774.902],[408.544,763.368],[387.587,747.667],[365.756,733.494],[344.578,718.173],[325.001,700.041],[303.134,685.944],[279.658,674.648],[259.099,658.244],[234.199,649.444],[214.569,631.408],[194.152,614.738],[171.135,602.645],[149.135,588.747],[128.648,576.814],[128.648,601.154],[125.668,627.953],[123.731,654.751],[127.834,681.549],[129.063,708.357],[131.309,735.155],[127.746,761.954],[128.842,788.762],[126.711,815.56],[126.304,842.368],[128.86,869.166],[126.994,895.964],[126.879,922.763],[130.062,949.57],[125.624,976.378],[127.763,1003.186],[124.775,1029.994],[124.324,1056.802],[124.147,1083.61],[141.708,1094.612],[165.511,1082.594],[185.433,1064.823],[206.195,1048.306],[226.975,1031.798],[248.718,1016.704],[269.577,1000.319],[287.333,979.388],[307.82,962.443],[329.511,947.283],[353.191,935.027],[374.448,919.222],[391.31,896.971],[412.992,881.792],[435.761,868.179],[456.708,851.927],[474.8,831.299]]}],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.737,0.863,0.992]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":1,"k":[{"t":0,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":12.6,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":16.2,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":22.8,"s":[0,0],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"o":{"a":0,"k":100},"p":{"a":1,"k":[{"t":0,"s":[-352.143,-879.251],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[-352.143,-835.713],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":12.6,"s":[-309.283,-835.713],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":16.2,"s":[-337.298,-835.713],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":22.8,"s":[-311.968,-835.713],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"tr","nm":"Transform","a":{"a":1,"k":[{"t":0,"s":[0,8.281],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[0,8.281],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":12.6,"s":[0,8.281],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":16.2,"s":[0,8.281],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":22.8,"s":[0,8.281],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"o":{"a":0,"k":100},"p":{"a":1,"k":[{"t":0,"s":[-53.049,0.818],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":8.4,"s":[-53.049,0.865],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":12.6,"s":[-46.593,0.865],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":16.2,"s":[-50.813,0.865],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":22.8,"s":[-46.997,0.865],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"r":{"a":0,"k":0},"s":{"a":0,"k":[21.405,21.405]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"gr","hd":false,"nm":"์ค๋ฅธ์ชฝ Group","bm":0,"it":[{"ty":"gr","hd":false,"nm":"์ค๋ฅธ์ชฝ_์ Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"์ค๋ฅธ์ชฝ_์ ","d":1,"ks":{"a":1,"k":[{"t":0,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[635.705,859.945],[662.904,845.613],[688.895,832.899],[710.573,813.164],[738.041,802.867],[762.206,787.167],[785.172,769.519],[812.131,758.383],[836.734,743.412],[858.333,723.527],[881.999,707.018],[907.79,693.995],[931.774,677.985],[957.046,664.102],[981.051,648.133],[1006.543,634.6],[1031.006,619.389],[1057.586,600.533],[1052.635,632.802],[1059.792,661.336],[1054.412,689.87],[1051.348,718.414],[1059.173,746.948],[1054.112,775.482],[1057.187,804.016],[1052.346,832.56],[1056.588,861.094],[1055.151,889.628],[1057.606,918.162],[1059.991,946.696],[1052.495,975.24],[1057.127,1003.774],[1056.717,1032.318],[1058.873,1060.852],[1058.654,1089.396],[1058.145,1117.94],[1052.176,1146.484],[1042.664,1156.381],[1020.756,1139.143],[997.57,1123.653],[974.324,1108.242],[950.06,1094.21],[927.961,1077.231],[908.169,1057.097],[885.203,1041.307],[863.923,1023.209],[841.655,1006.461],[816.912,993.097],[796.63,973.622],[773.903,957.512],[750.069,942.901],[727.581,926.441],[706.202,908.464],[683.036,892.944],[662.844,873.179]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[635.705,817.364],[662.904,803.741],[688.895,791.657],[710.573,772.899],[738.041,763.112],[762.206,748.189],[785.172,731.415],[812.131,720.831],[836.734,706.601],[858.333,687.701],[881.999,672.009],[907.79,659.63],[931.774,644.413],[957.046,631.218],[981.051,616.039],[1006.543,603.176],[1031.006,588.719],[1057.586,570.796],[1052.635,601.467],[1059.792,628.588],[1054.412,655.71],[1051.348,682.84],[1059.173,709.961],[1054.112,737.082],[1057.187,764.203],[1052.346,791.334],[1056.588,818.455],[1055.151,845.576],[1057.606,872.697],[1059.991,899.818],[1052.495,926.949],[1057.127,954.07],[1056.717,981.201],[1058.873,1008.322],[1058.654,1035.452],[1058.145,1062.583],[1052.176,1089.714],[1042.664,1099.121],[1020.756,1082.736],[997.57,1068.013],[974.324,1053.365],[950.06,1040.028],[927.961,1023.89],[908.169,1004.753],[885.203,989.744],[863.923,972.543],[841.655,956.624],[816.912,943.922],[796.63,925.411],[773.903,910.099],[750.069,896.211],[727.581,880.567],[706.202,863.48],[683.036,848.728],[662.844,829.942]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":12.6,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[558.332,817.364],[582.22,803.741],[605.048,791.657],[624.088,772.899],[648.213,763.112],[669.436,748.189],[689.607,731.415],[713.285,720.831],[734.893,706.601],[753.864,687.701],[774.648,672.009],[797.301,659.63],[818.366,644.413],[840.562,631.218],[861.645,616.039],[884.034,603.176],[905.52,588.719],[928.865,570.796],[924.517,601.467],[930.802,628.588],[926.077,655.71],[923.386,682.84],[930.258,709.961],[925.814,737.082],[928.514,764.203],[924.262,791.334],[927.988,818.455],[926.726,845.576],[928.882,872.697],[930.977,899.818],[924.394,926.949],[928.461,954.07],[928.102,981.201],[929.995,1008.322],[929.803,1035.452],[929.356,1062.583],[924.113,1089.714],[915.759,1099.121],[896.517,1082.736],[876.153,1068.013],[855.736,1053.365],[834.426,1040.028],[815.017,1023.89],[797.634,1004.753],[777.463,989.744],[758.773,972.543],[739.215,956.624],[717.484,943.922],[699.671,925.411],[679.71,910.099],[658.776,896.211],[639.026,880.567],[620.248,863.48],[599.902,848.728],[582.168,829.942]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":16.2,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[608.906,817.364],[634.958,803.741],[659.853,791.657],[680.618,772.899],[706.928,763.112],[730.074,748.189],[752.072,731.415],[777.894,720.831],[801.461,706.601],[822.149,687.701],[844.817,672.009],[869.521,659.63],[892.494,644.413],[916.701,631.218],[939.693,616.039],[964.11,603.176],[987.543,588.719],[1013.002,570.796],[1008.26,601.467],[1015.115,628.588],[1009.962,655.71],[1007.027,682.84],[1014.522,709.961],[1009.675,737.082],[1012.619,764.203],[1007.983,791.334],[1012.046,818.455],[1010.669,845.576],[1013.021,872.697],[1015.306,899.818],[1008.126,926.949],[1012.562,954.07],[1012.17,981.201],[1014.235,1008.322],[1014.025,1035.452],[1013.537,1062.583],[1007.82,1089.714],[998.709,1099.121],[977.724,1082.736],[955.516,1068.013],[933.25,1053.365],[910.009,1040.028],[888.842,1023.89],[869.884,1004.753],[847.886,989.744],[827.503,972.543],[806.174,956.624],[782.474,943.922],[763.047,925.411],[741.278,910.099],[718.448,896.211],[696.909,880.567],[676.431,863.48],[654.241,848.728],[634.901,829.942]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":22.8,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[563.179,817.364],[587.275,803.741],[610.3,791.657],[629.506,772.899],[653.84,763.112],[675.247,748.189],[695.594,731.415],[719.477,720.831],[741.273,706.601],[760.408,687.701],[781.374,672.009],[804.222,659.63],[825.47,644.413],[847.859,631.218],[869.125,616.039],[891.708,603.176],[913.381,588.719],[936.928,570.796],[932.543,601.467],[938.883,628.588],[934.117,655.71],[931.402,682.84],[938.334,709.961],[933.851,737.082],[936.575,764.203],[932.286,791.334],[936.044,818.455],[934.771,845.576],[936.946,872.697],[939.059,899.818],[932.419,926.949],[936.522,954.07],[936.159,981.201],[938.069,1008.322],[937.875,1035.452],[937.424,1062.583],[932.136,1089.714],[923.709,1099.121],[904.3,1082.736],[883.759,1068.013],[863.165,1053.365],[841.67,1040.028],[822.093,1023.89],[804.558,1004.753],[784.212,989.744],[765.36,972.543],[745.633,956.624],[723.712,943.922],[705.745,925.411],[685.611,910.099],[664.495,896.211],[644.573,880.567],[625.633,863.48],[605.11,848.728],[587.222,829.942]]}],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]}},{"ty":"st","hd":false,"bm":0,"c":{"a":0,"k":[0.537,0.702,0.871]},"lc":2,"lj":2,"ml":4,"o":{"a":0,"k":100},"w":{"a":0,"k":11.13}},{"ty":"tr","nm":"Transform","a":{"a":1,"k":[{"t":0,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":12.6,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":16.2,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":22.8,"s":[0,0],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"o":{"a":0,"k":100},"p":{"a":1,"k":[{"t":0,"s":[-847.848,-878.457],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[-847.848,-834.959],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":12.6,"s":[-744.655,-834.959],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":16.2,"s":[-812.106,-834.959],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":22.8,"s":[-751.119,-834.959],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"gr","hd":false,"nm":"์ค๋ฅธ์ชฝ_๋ฉด Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"์ค๋ฅธ์ชฝ_๋ฉด","d":1,"ks":{"a":1,"k":[{"t":0,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[635.705,859.945],[662.904,845.613],[688.895,832.899],[710.573,813.164],[738.041,802.867],[762.206,787.167],[785.172,769.519],[812.131,758.383],[836.734,743.412],[858.333,723.527],[881.999,707.018],[907.79,693.995],[931.774,677.985],[957.046,664.102],[981.051,648.133],[1006.543,634.6],[1031.006,619.389],[1057.586,600.533],[1052.635,632.802],[1059.792,661.336],[1054.412,689.87],[1051.348,718.414],[1059.173,746.948],[1054.112,775.482],[1057.187,804.016],[1052.346,832.56],[1056.588,861.094],[1055.151,889.628],[1057.606,918.162],[1059.991,946.696],[1052.495,975.24],[1057.127,1003.774],[1056.717,1032.318],[1058.873,1060.852],[1058.654,1089.396],[1058.145,1117.94],[1052.176,1146.484],[1042.664,1156.381],[1020.756,1139.143],[997.57,1123.653],[974.324,1108.242],[950.06,1094.21],[927.961,1077.231],[908.169,1057.097],[885.203,1041.307],[863.923,1023.209],[841.655,1006.461],[816.912,993.097],[796.63,973.622],[773.903,957.512],[750.069,942.901],[727.581,926.441],[706.202,908.464],[683.036,892.944],[662.844,873.179]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[635.705,817.364],[662.904,803.741],[688.895,791.657],[710.573,772.899],[738.041,763.112],[762.206,748.189],[785.172,731.415],[812.131,720.831],[836.734,706.601],[858.333,687.701],[881.999,672.009],[907.79,659.63],[931.774,644.413],[957.046,631.218],[981.051,616.039],[1006.543,603.176],[1031.006,588.719],[1057.586,570.796],[1052.635,601.467],[1059.792,628.588],[1054.412,655.71],[1051.348,682.84],[1059.173,709.961],[1054.112,737.082],[1057.187,764.203],[1052.346,791.334],[1056.588,818.455],[1055.151,845.576],[1057.606,872.697],[1059.991,899.818],[1052.495,926.949],[1057.127,954.07],[1056.717,981.201],[1058.873,1008.322],[1058.654,1035.452],[1058.145,1062.583],[1052.176,1089.714],[1042.664,1099.121],[1020.756,1082.736],[997.57,1068.013],[974.324,1053.365],[950.06,1040.028],[927.961,1023.89],[908.169,1004.753],[885.203,989.744],[863.923,972.543],[841.655,956.624],[816.912,943.922],[796.63,925.411],[773.903,910.099],[750.069,896.211],[727.581,880.567],[706.202,863.48],[683.036,848.728],[662.844,829.942]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":12.6,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[558.332,817.364],[582.22,803.741],[605.048,791.657],[624.088,772.899],[648.213,763.112],[669.436,748.189],[689.607,731.415],[713.285,720.831],[734.893,706.601],[753.864,687.701],[774.648,672.009],[797.301,659.63],[818.366,644.413],[840.562,631.218],[861.645,616.039],[884.034,603.176],[905.52,588.719],[928.865,570.796],[924.517,601.467],[930.802,628.588],[926.077,655.71],[923.386,682.84],[930.258,709.961],[925.814,737.082],[928.514,764.203],[924.262,791.334],[927.988,818.455],[926.726,845.576],[928.882,872.697],[930.977,899.818],[924.394,926.949],[928.461,954.07],[928.102,981.201],[929.995,1008.322],[929.803,1035.452],[929.356,1062.583],[924.113,1089.714],[915.759,1099.121],[896.517,1082.736],[876.153,1068.013],[855.736,1053.365],[834.426,1040.028],[815.017,1023.89],[797.634,1004.753],[777.463,989.744],[758.773,972.543],[739.215,956.624],[717.484,943.922],[699.671,925.411],[679.71,910.099],[658.776,896.211],[639.026,880.567],[620.248,863.48],[599.902,848.728],[582.168,829.942]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":16.2,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[608.906,817.364],[634.958,803.741],[659.853,791.657],[680.618,772.899],[706.928,763.112],[730.074,748.189],[752.072,731.415],[777.894,720.831],[801.461,706.601],[822.149,687.701],[844.817,672.009],[869.521,659.63],[892.494,644.413],[916.701,631.218],[939.693,616.039],[964.11,603.176],[987.543,588.719],[1013.002,570.796],[1008.26,601.467],[1015.115,628.588],[1009.962,655.71],[1007.027,682.84],[1014.522,709.961],[1009.675,737.082],[1012.619,764.203],[1007.983,791.334],[1012.046,818.455],[1010.669,845.576],[1013.021,872.697],[1015.306,899.818],[1008.126,926.949],[1012.562,954.07],[1012.17,981.201],[1014.235,1008.322],[1014.025,1035.452],[1013.537,1062.583],[1007.82,1089.714],[998.709,1099.121],[977.724,1082.736],[955.516,1068.013],[933.25,1053.365],[910.009,1040.028],[888.842,1023.89],[869.884,1004.753],[847.886,989.744],[827.503,972.543],[806.174,956.624],[782.474,943.922],[763.047,925.411],[741.278,910.099],[718.448,896.211],[696.909,880.567],[676.431,863.48],[654.241,848.728],[634.901,829.942]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":22.8,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[563.179,817.364],[587.275,803.741],[610.3,791.657],[629.506,772.899],[653.84,763.112],[675.247,748.189],[695.594,731.415],[719.477,720.831],[741.273,706.601],[760.408,687.701],[781.374,672.009],[804.222,659.63],[825.47,644.413],[847.859,631.218],[869.125,616.039],[891.708,603.176],[913.381,588.719],[936.928,570.796],[932.543,601.467],[938.883,628.588],[934.117,655.71],[931.402,682.84],[938.334,709.961],[933.851,737.082],[936.575,764.203],[932.286,791.334],[936.044,818.455],[934.771,845.576],[936.946,872.697],[939.059,899.818],[932.419,926.949],[936.522,954.07],[936.159,981.201],[938.069,1008.322],[937.875,1035.452],[937.424,1062.583],[932.136,1089.714],[923.709,1099.121],[904.3,1082.736],[883.759,1068.013],[863.165,1053.365],[841.67,1040.028],[822.093,1023.89],[804.558,1004.753],[784.212,989.744],[765.36,972.543],[745.633,956.624],[723.712,943.922],[705.745,925.411],[685.611,910.099],[664.495,896.211],[644.573,880.567],[625.633,863.48],[605.11,848.728],[587.222,829.942]]}],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.737,0.863,0.992]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":1,"k":[{"t":0,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":12.6,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":16.2,"s":[0,0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":22.8,"s":[0,0],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"o":{"a":0,"k":100},"p":{"a":1,"k":[{"t":0,"s":[-847.848,-878.457],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[-847.848,-834.958],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":12.6,"s":[-744.655,-834.958],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":16.2,"s":[-812.106,-834.958],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":22.8,"s":[-751.119,-834.958],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"gr","hd":false,"nm":"์ข
์ด_half Group","bm":0,"it":[{"ty":"gr","hd":false,"nm":"์ข
์ด_half Group","bm":0,"it":[{"ty":"gr","hd":false,"nm":"Path 1 Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"Path 1","d":1,"ks":{"a":1,"k":[{"t":0,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[178.726,189.879],[178.692,178.952],[178.851,165.753],[176.987,152.554],[177.7,139.355],[178.929,126.156],[179.148,112.957],[179.03,99.759],[177.146,86.3],[192.092,86.928],[206.778,86.688],[221.465,87.427],[236.152,84.589],[250.82,85.678],[264.631,85.678],[280.196,86.23],[294.883,86.23],[309.569,84.7],[324.256,87.981],[338.807,86.709],[337.618,99.761],[339.184,114.46],[339.184,128.074],[338.809,141.349],[339.184,152.559],[338.57,165.758],[338.809,177.732],[338.65,191.157]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[178.726,180.477],[178.692,170.09],[178.851,157.545],[176.987,145],[177.7,132.455],[178.929,119.909],[179.148,107.364],[179.03,94.819],[177.146,82.027],[192.092,82.623],[206.778,82.396],[221.465,83.098],[236.152,80.4],[250.82,81.435],[264.631,81.435],[280.196,81.96],[294.883,81.96],[309.569,80.506],[324.256,83.625],[338.807,82.415],[337.618,94.821],[339.184,108.792],[339.184,121.732],[338.809,134.35],[339.184,145.005],[338.57,157.55],[338.809,168.931],[338.65,181.691]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":12.6,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[156.973,180.477],[156.943,170.09],[157.083,157.545],[155.446,145],[156.072,132.455],[157.151,119.909],[157.344,107.364],[157.24,94.819],[155.585,82.027],[168.712,82.623],[181.611,82.396],[194.51,83.098],[207.409,80.4],[220.293,81.435],[232.422,81.435],[246.093,81.96],[258.992,81.96],[271.891,80.506],[284.79,83.625],[297.57,82.415],[296.526,94.821],[297.901,108.792],[297.901,121.732],[297.572,134.35],[297.901,145.005],[297.362,157.55],[297.572,168.931],[297.432,181.691]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":16.2,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[171.192,180.477],[171.159,170.09],[171.311,157.545],[169.526,145],[170.209,132.455],[171.386,119.909],[171.596,107.364],[171.483,94.819],[169.678,82.027],[183.994,82.623],[198.061,82.396],[212.129,83.098],[226.196,80.4],[240.247,81.435],[253.475,81.435],[268.384,81.96],[282.452,81.96],[296.519,80.506],[310.587,83.625],[324.524,82.415],[323.385,94.821],[324.886,108.792],[324.886,121.732],[324.526,134.35],[324.886,145.005],[324.297,157.55],[324.526,168.931],[324.374,181.691]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":22.8,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[139.412,158.907],[139.386,149.762],[139.51,138.716],[138.056,127.67],[138.612,116.624],[139.571,105.579],[139.741,94.533],[139.649,83.487],[138.18,72.223],[149.838,72.749],[161.294,72.548],[172.75,73.166],[184.206,70.791],[195.648,71.702],[206.421,71.702],[218.562,72.164],[230.018,72.164],[241.474,70.884],[252.93,73.63],[264.28,72.565],[263.353,83.489],[264.575,95.79],[264.575,107.184],[264.282,118.293],[264.575,127.675],[264.096,138.72],[264.282,148.741],[264.158,159.976]]}],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[1,1,1]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":1,"k":[{"t":0,"s":[0.05,39.754],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[0.05,37.786],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":12.6,"s":[0.044,37.786],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":16.2,"s":[0.048,37.786],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":22.8,"s":[0.039,33.27],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"o":{"a":0,"k":100},"p":{"a":1,"k":[{"t":0,"s":[0.05,39.754],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[0.05,37.786],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":12.6,"s":[0.044,37.786],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":16.2,"s":[0.048,37.786],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":22.8,"s":[0.039,33.27],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"tr","nm":"Transform","a":{"a":1,"k":[{"t":0,"s":[240.247,129.182],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[240.247,122.354],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":12.6,"s":[208.835,122.354],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":16.2,"s":[229.367,122.354],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":22.8,"s":[183.476,106.693],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"o":{"a":0,"k":100},"p":{"a":1,"k":[{"t":0,"s":[-17.839,1.227],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[-17.839,0.736],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":12.6,"s":[-17.839,0.736],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":16.2,"s":[-17.839,0.736],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":22.8,"s":[-17.839,-0.391],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"tr","nm":"Transform","a":{"a":1,"k":[{"t":0,"s":[-17.839,6.411],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[-17.839,6.276],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":12.6,"s":[-17.839,6.276],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":16.2,"s":[-17.839,6.276],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":17.4,"s":[-17.839,5.966],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":22.8,"s":[-17.839,5.966],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"o":{"a":1,"k":[{"t":0,"s":[0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":17.4,"s":[0],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":22.8,"s":[100],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"p":{"a":1,"k":[{"t":0,"s":[-333.819,-65.91],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":8.4,"s":[-333.819,-61.748],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":12.6,"s":[-303.869,-61.748],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":16.2,"s":[-323.445,-61.748],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":17.4,"s":[-323.445,29.045],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":22.8,"s":[-305.745,29.045],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"r":{"a":0,"k":0},"s":{"a":0,"k":[491.888,491.888]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"gr","hd":false,"nm":"๋ท๋ฉดbg Group","bm":0,"it":[{"ty":"sh","hd":false,"nm":"๋ท๋ฉดbg","d":1,"ks":{"a":1,"k":[{"t":0,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[91.974,-56.459],[91.974,56.459],[-91.974,56.459],[-91.974,-56.459]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[91.974,-53.664],[91.974,53.664],[-91.974,53.664],[-91.974,-53.664]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":12.6,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[80.779,-53.664],[80.779,53.664],[-80.779,53.664],[-80.779,-53.664]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":16.2,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[88.096,-53.664],[88.096,53.664],[-88.096,53.664],[-88.096,-53.664]]}],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":22.8,"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[81.481,-53.664],[81.481,53.664],[-81.481,53.664],[-81.481,-53.664]]}],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.537,0.702,0.871]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":1,"k":[{"t":0,"s":[-250.023,8.152],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":8.4,"s":[-250.023,7.748],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":12.6,"s":[-219.592,7.748],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":16.2,"s":[-239.483,7.748],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":22.8,"s":[-221.499,7.748],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"r":{"a":0,"k":0},"s":{"a":0,"k":[491.888,491.888]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"tr","nm":"Transform","a":{"a":1,"k":[{"t":0,"s":[37.148,35.924],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":8.4,"s":[37.148,35.924],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":12.6,"s":[37.148,35.924],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":16.2,"s":[37.148,35.924],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25}},{"t":22.8,"s":[37.148,35.924],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"o":{"a":0,"k":100},"p":{"a":1,"k":[{"t":0,"s":[61.01,6.565],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":8.4,"s":[61.01,6.62],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":12.6,"s":[54.552,6.62],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":16.2,"s":[58.774,6.62],"i":{"x":0.75,"y":0.75},"o":{"x":0.25,"y":0.25},"ti":[0,0],"to":[0,0]},{"t":22.8,"s":[54.957,6.62],"i":{"x":0,"y":0},"o":{"x":1,"y":1}}]},"r":{"a":0,"k":0},"s":{"a":0,"k":[21.405,21.405]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]},{"ddd":0,"ind":18,"ty":4,"nm":"์ข
์ด_์ ์ฒด","hd":true,"sr":1,"ks":{"a":{"a":0,"k":[0,2.728]},"o":{"a":0,"k":100},"p":{"a":0,"k":[187.5,337.03]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":37,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":22,"ty":0,"nm":"to","parent":18,"hd":true,"sr":1,"ks":{"a":{"a":0,"k":[38.5,28.5]},"o":{"a":0,"k":100},"p":{"a":0,"k":[-34,-89.302]},"r":{"a":0,"k":0},"s":{"a":0,"k":[55.541,55.541]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":37,"st":0,"bm":0,"h":57,"refId":"el-159-_-Uo","w":77},{"ddd":0,"ind":23,"ty":4,"nm":"์ข
์ด_์ ์ฒด","hd":true,"sr":1,"ks":{"a":{"a":0,"k":[0,2.728]},"o":{"a":0,"k":100},"p":{"a":0,"k":[187.5,337.03]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":37,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":true,"nm":"์ข
์ด_์ ์ฒด Group","bm":0,"it":[{"ty":"gr","hd":true,"nm":"Path 1 Group","bm":0,"it":[{"ty":"sh","hd":true,"nm":"Path 1","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[339.825,284.126],[324.871,284.404],[310.156,284.126],[295.442,283.591],[280.727,283.203],[266.03,285.436],[251.314,286.009],[237.23,286.009],[221.882,285.325],[207.168,284.255],[192.453,285.105],[178.433,284.237],[177.3,271.686],[178.433,258.47],[179.367,245.255],[177.759,232.039],[177.759,220.928],[177.938,205.608],[178.435,192.393],[179.031,179.177],[179.19,165.962],[177.323,152.746],[178.037,139.531],[179.268,126.315],[179.488,113.1],[179.369,99.884],[177.482,86.409],[192.456,87.037],[207.17,86.798],[221.885,87.537],[236.599,84.696],[251.296,85.786],[265.132,85.786],[280.727,86.338],[295.442,86.338],[310.156,84.807],[324.871,88.092],[339.449,86.818],[338.258,99.887],[339.827,114.604],[339.827,128.236],[339.451,141.527],[339.827,152.751],[339.211,165.967],[339.451,177.956],[339.292,192.398],[339.133,205.613],[339.769,218.829],[338.916,232.044],[338.24,245.26],[339.83,260.004],[339.83,284.131]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[1,1,1]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[258.565,185.352]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,9.931]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]},{"ddd":0,"ind":26,"ty":4,"nm":"์ข
์ด_์ ์ฒด","hd":true,"sr":1,"ks":{"a":{"a":0,"k":[0,2.728]},"o":{"a":0,"k":100},"p":{"a":0,"k":[187.5,337.03]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":37,"st":0,"bm":0,"shapes":[]},{"ddd":0,"ind":27,"ty":4,"nm":"๋ท๋ฉด","hd":true,"sr":1,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[35.002,80.037]},"r":{"a":0,"k":0},"s":{"a":0,"k":[25.311,25.311]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":37,"st":0,"bm":0,"shapes":[{"ty":"sh","hd":true,"nm":"๋ท๋ฉด","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[1058.32,594.37],[1057.48,622.15],[1057.34,649.5],[1058.3,676.85],[1057.04,704.21],[1059.07,731.56],[1056.29,758.92],[1057.99,786.27],[1057.43,813.63],[1055.89,840.99],[1059.2,868.34],[1057.45,895.69],[1057.22,923.05],[1058.97,950.4],[1056.85,977.76],[1058.41,1005.12],[1056.57,1032.48],[1059.58,1059.84],[1057.82,1087.2],[1058.23,1114.57],[1046.38,1143.91],[1019.81,1158.06],[991.87,1157.56],[963.95,1159.39],[936.02,1158.26],[908.09,1160.52],[880.16,1158.63],[852.23,1160.65],[824.31,1161.22],[796.38,1161.03],[768.44,1158.15],[740.51,1157.24],[712.58,1160.4],[684.65,1158.68],[656.72,1160.43],[628.79,1161.27],[600.86,1157.92],[572.92,1161.33],[544.99,1161.39],[517.06,1160.13],[489.13,1161.39],[461.19,1158.47],[433.26,1161.08],[405.33,1158.52],[377.4,1159.73],[349.46,1160.71],[321.53,1161.44],[293.59,1157.4],[265.66,1161.14],[237.72,1159.04],[209.78,1159.17],[181.84,1160.67],[153.67,1145.14],[142.48,1114.57],[144.38,1087.21],[146,1059.86],[145.06,1032.51],[142.89,1005.15],[142.66,977.8],[146.25,950.44],[142.31,923.09],[142.52,895.73],[145.53,868.37],[142.74,841.02],[143.73,813.67],[145.36,786.31],[144.83,758.96],[142.47,731.6],[145.87,704.24],[142.63,676.88],[146,649.52],[145.79,622.16],[144.5,595],[166.34,578.64],[189.51,563.89],[213.91,551],[235.73,534.22],[258.06,518.2],[281.66,504.11],[304.11,488.29],[326.82,472.84],[350.12,458.29],[373.46,443.82],[395.37,427.17],[417.69,411.13],[441.72,397.68],[464.47,382.29],[487.66,367.58],[511.03,353.12],[534.44,338.72],[556.65,322.51],[585.3,310.61],[616.16,311.53],[644.51,323.23],[668.37,336.94],[691.71,351.43],[713.81,367.79],[737.31,382.03],[760.76,396.36],[783,412.51],[805.26,428.62],[829.47,441.79],[852.31,457.04],[874.08,473.9],[897.34,488.5],[919.09,505.39],[942.45,519.86],[965.45,534.87],[987.69,551.03],[1011.76,564.42],[1034.79,579.38]]}}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.537,0.702,0.871]},"r":1,"o":{"a":0,"k":100}}]},{"ddd":0,"ind":28,"ty":4,"nm":"Screen","hd":false,"sr":1,"ks":{"a":{"a":0,"k":[163.5,90]},"o":{"a":0,"k":100},"p":{"a":0,"k":[163.5,90]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}},"ao":0,"ip":0,"op":37,"st":0,"bm":0,"shapes":[{"ty":"gr","hd":false,"nm":"Screen Group","bm":0,"it":[{"ty":"rc","hd":false,"nm":"Screen","d":1,"p":{"a":0,"k":[163.5,90]},"r":{"a":0,"k":0},"s":{"a":0,"k":[327,180]}},{"ty":"fl","hd":false,"bm":0,"c":{"a":0,"k":[0.142,0.142,0.142]},"r":1,"o":{"a":0,"k":0}},{"ty":"tr","nm":"Transform","a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"np":0}]}],"meta":{"g":"@phase-software/lottie-exporter 0.7.0"},"nm":"","op":36,"v":"5.6.0","w":327}
\ No newline at end of file
diff --git a/data/src/main/java/com/yapp/data/local/di/MediaModule.kt b/core/media/src/main/java/com/yapp/media/di/MediaModule.kt
similarity index 64%
rename from data/src/main/java/com/yapp/data/local/di/MediaModule.kt
rename to core/media/src/main/java/com/yapp/media/di/MediaModule.kt
index 1b9e9168..ff75332b 100644
--- a/data/src/main/java/com/yapp/data/local/di/MediaModule.kt
+++ b/core/media/src/main/java/com/yapp/media/di/MediaModule.kt
@@ -1,9 +1,8 @@
-package com.yapp.data.local.di
+package com.yapp.media.di
import android.content.ContentResolver
import android.content.Context
-import com.yapp.data.local.datasource.ImageLocalDataSource
-import com.yapp.data.local.datasource.ImageLocalDataSourceImpl
+import com.yapp.media.storage.ImageSaver
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@@ -23,7 +22,9 @@ object MediaModule {
@Provides
@Singleton
- fun provideImageLocalDataSource(contentResolver: ContentResolver): ImageLocalDataSource {
- return ImageLocalDataSourceImpl(contentResolver)
+ fun provideImageSaver(
+ contentResolver: ContentResolver,
+ ): ImageSaver {
+ return ImageSaver(contentResolver)
}
}
diff --git a/data/src/main/java/com/yapp/data/local/datasource/ImageLocalDataSourceImpl.kt b/core/media/src/main/java/com/yapp/media/storage/ImageSaver.kt
similarity index 76%
rename from data/src/main/java/com/yapp/data/local/datasource/ImageLocalDataSourceImpl.kt
rename to core/media/src/main/java/com/yapp/media/storage/ImageSaver.kt
index 6b1a324f..0c1d98d8 100644
--- a/data/src/main/java/com/yapp/data/local/datasource/ImageLocalDataSourceImpl.kt
+++ b/core/media/src/main/java/com/yapp/media/storage/ImageSaver.kt
@@ -1,4 +1,4 @@
-package com.yapp.data.local.datasource
+package com.yapp.media.storage
import android.content.ContentResolver
import android.content.ContentValues
@@ -9,11 +9,11 @@ import android.util.Log
import java.io.IOException
import javax.inject.Inject
-class ImageLocalDataSourceImpl @Inject constructor(
+class ImageSaver @Inject constructor(
private val contentResolver: ContentResolver,
-) : ImageLocalDataSource {
+) {
- override suspend fun saveImage(byteArray: ByteArray, fileName: String): Boolean {
+ fun saveImage(byteArray: ByteArray, fileName: String): Boolean {
return try {
val bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)
@@ -32,10 +32,10 @@ class ImageLocalDataSourceImpl @Inject constructor(
true
} catch (e: SecurityException) {
- Log.e("ImageLocalDataSource", "๊ถํ ์์: ${e.message}")
+ Log.e("ImageSaver", "๊ถํ ์์: ${e.message}")
false
} catch (e: IOException) {
- Log.e("ImageLocalDataSource", "ํ์ผ ์ ์ฅ ์คํจ: ${e.message}")
+ Log.e("ImageSaver", "ํ์ผ ์ ์ฅ ์คํจ: ${e.message}")
false
}
}
diff --git a/core/network/build.gradle.kts b/core/network/build.gradle.kts
index b15a325a..beabc390 100644
--- a/core/network/build.gradle.kts
+++ b/core/network/build.gradle.kts
@@ -11,7 +11,6 @@ android {
}
dependencies {
- implementation(projects.core.datastore)
implementation(projects.core.common)
implementation(platform(libs.okhttp.bom))
implementation(libs.okhttp.logging)
diff --git a/core/network/src/main/java/com/yapp/network/TokenRefreshService.kt b/core/network/src/main/java/com/yapp/network/TokenRefreshService.kt
deleted file mode 100644
index db8def42..00000000
--- a/core/network/src/main/java/com/yapp/network/TokenRefreshService.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.yapp.network
-
-import com.yapp.network.model.BaseResponse
-import com.yapp.network.model.ResponseAuthRefreshDto
-import retrofit2.http.Header
-import retrofit2.http.POST
-
-interface TokenRefreshService {
- @POST("/$API/$VERSION/$AUTH/$REISSUE")
- suspend fun postAuthRefresh(
- @Header("refreshToken") refreshToken: String,
- ): BaseResponse
-
- companion object {
- const val API = "api"
- const val VERSION = "v1"
- const val AUTH = "auth"
- const val REISSUE = "reissue"
- }
-}
diff --git a/core/network/src/main/java/com/yapp/network/authenticator/AuthenticationIntercept.kt b/core/network/src/main/java/com/yapp/network/authenticator/AuthenticationIntercept.kt
deleted file mode 100644
index 6051ebd9..00000000
--- a/core/network/src/main/java/com/yapp/network/authenticator/AuthenticationIntercept.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.yapp.network.authenticator
-
-import com.yapp.datastore.token.TokenDataStore
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.runBlocking
-import okhttp3.Interceptor
-import okhttp3.Request
-import okhttp3.Response
-import javax.inject.Inject
-
-class AuthenticationIntercept @Inject constructor(
- private val datastore: TokenDataStore,
-) : Interceptor {
-
- override fun intercept(chain: Interceptor.Chain): Response {
- val originalRequest = chain.request()
- val authRequest = originalRequest.addAuthorizationHeader()
- return chain.proceed(authRequest)
- }
-
- private fun Request.addAuthorizationHeader(): Request {
- val accessToken = runBlocking { datastore.token.first().accessToken }
- return this.newBuilder()
- .addHeader("Authorization", "Bearer $accessToken")
- .build()
- }
-}
diff --git a/core/network/src/main/java/com/yapp/network/authenticator/OrbitAuthenticator.kt b/core/network/src/main/java/com/yapp/network/authenticator/OrbitAuthenticator.kt
deleted file mode 100644
index 9183a058..00000000
--- a/core/network/src/main/java/com/yapp/network/authenticator/OrbitAuthenticator.kt
+++ /dev/null
@@ -1,64 +0,0 @@
-package com.yapp.network.authenticator
-
-import android.content.Context
-import com.jakewharton.processphoenix.ProcessPhoenix
-import com.yapp.datastore.token.TokenDataStore
-import com.yapp.network.TokenRefreshService
-import com.yapp.network.model.ResponseAuthRefreshDto
-import dagger.hilt.android.qualifiers.ApplicationContext
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.runBlocking
-import okhttp3.Authenticator
-import okhttp3.Request
-import okhttp3.Response
-import okhttp3.Route
-import javax.inject.Inject
-
-class OrbitAuthenticator @Inject constructor(
- private val dataStore: TokenDataStore,
- private val tokenRefreshService: TokenRefreshService,
- @ApplicationContext private val context: Context,
-) : Authenticator {
-
- override fun authenticate(route: Route?, response: Response): Request? {
- if (response.code == CODE_TOKEN_EXPIRED) {
- return handleTokenExpiration(response)
- }
- return null
- }
-
- private fun handleTokenExpiration(response: Response): Request? {
- val newTokens = refreshTokens()
- return newTokens?.let {
- response.request.newBuilder()
- .header("Authorization", "Bearer ${it.accessToken}")
- .build()
- }
- }
-
- private fun refreshTokens(): ResponseAuthRefreshDto? {
- return runCatching {
- runBlocking {
- val refreshToken = dataStore.token.first().refreshToken
- tokenRefreshService.postAuthRefresh(refreshToken).data
- }
- }.onSuccess { newToken ->
- runBlocking {
- newToken?.let {
- dataStore.setAccessToken(it.accessToken)
- }
- }
- }.onFailure {
- handleTokenRefreshFailure()
- }.getOrNull()
- }
-
- private fun handleTokenRefreshFailure() {
- runBlocking { dataStore.setAutoLogin(false) }
- ProcessPhoenix.triggerRebirth(context)
- }
-
- companion object {
- const val CODE_TOKEN_EXPIRED = 401
- }
-}
diff --git a/core/network/src/main/java/com/yapp/network/di/NetworkModule.kt b/core/network/src/main/java/com/yapp/network/di/NetworkModule.kt
index 3435cb09..25dfb118 100644
--- a/core/network/src/main/java/com/yapp/network/di/NetworkModule.kt
+++ b/core/network/src/main/java/com/yapp/network/di/NetworkModule.kt
@@ -1,15 +1,11 @@
package com.yapp.network.di
import com.yapp.common.buildconfig.BuildConfigFieldProvider
-import com.yapp.network.TokenRefreshService
-import com.yapp.network.authenticator.AuthenticationIntercept
-import com.yapp.network.authenticator.OrbitAuthenticator
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
@@ -22,11 +18,6 @@ import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
object NetworkModule {
- @Provides
- @Singleton
- fun provideTokenRefreshService(@NoneAuth retrofit: Retrofit) =
- retrofit.create(TokenRefreshService::class.java)
-
@Provides
@Singleton
fun provideLoggingInterceptor(
@@ -47,23 +38,7 @@ object NetworkModule {
@Provides
@Singleton
- @Auth
- fun provideAuthOkHttpClient(
- loggingInterceptor: HttpLoggingInterceptor,
- authInterceptor: Interceptor,
- authenticator: OrbitAuthenticator,
- ): OkHttpClient =
- OkHttpClient.Builder()
- .retryOnConnectionFailure(true)
- .addInterceptor(loggingInterceptor)
- .addInterceptor(authInterceptor)
- .authenticator(authenticator)
- .build()
-
- @Provides
- @Singleton
- @NoneAuth
- fun provideNoneAuthOkHttpClient(
+ fun provideHttpClient(
loggingInterceptor: HttpLoggingInterceptor,
): OkHttpClient =
OkHttpClient.Builder()
@@ -76,18 +51,8 @@ object NetworkModule {
@Provides
@Singleton
- @Auth
- fun provideAuthRetrofit(@Auth okHttpClient: OkHttpClient, buildConfigFieldProvider: BuildConfigFieldProvider): Retrofit = Retrofit.Builder()
- .addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
- .baseUrl(buildConfigFieldProvider.get().baseUrl)
- .client(okHttpClient)
- .build()
-
- @Provides
- @Singleton
- @NoneAuth
- fun provideNoneAuthRetrofit(
- @NoneAuth okHttpClient: OkHttpClient,
+ fun provideRetrofit(
+ okHttpClient: OkHttpClient,
buildConfigFieldProvider: BuildConfigFieldProvider,
json: Json,
): Retrofit =
@@ -96,18 +61,4 @@ object NetworkModule {
.baseUrl(buildConfigFieldProvider.get().baseUrl)
.client(okHttpClient)
.build()
-
- @Provides
- @Singleton
- @S3
- fun provideS3Retrofit(@NoneAuth okHttpClient: OkHttpClient, buildConfigFieldProvider: BuildConfigFieldProvider): Retrofit =
- Retrofit.Builder()
- .addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
- .baseUrl(buildConfigFieldProvider.get().baseUrl)
- .client(okHttpClient)
- .build()
-
- @Provides
- @Singleton
- fun provideAuthInterceptor(interceptor: AuthenticationIntercept): Interceptor = interceptor
}
diff --git a/core/network/src/main/java/com/yapp/network/di/Qualifier.kt b/core/network/src/main/java/com/yapp/network/di/Qualifier.kt
deleted file mode 100644
index 68817100..00000000
--- a/core/network/src/main/java/com/yapp/network/di/Qualifier.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.yapp.network.di
-
-import javax.inject.Qualifier
-
-@Qualifier
-@Retention(AnnotationRetention.BINARY)
-annotation class NoneAuth
-
-@Qualifier
-@Retention(AnnotationRetention.BINARY)
-annotation class Auth
-
-@Qualifier
-@Retention(AnnotationRetention.BINARY)
-annotation class S3
diff --git a/data/src/main/java/com/yapp/data/remote/utils/ApiError.kt b/core/network/src/main/java/com/yapp/network/model/ApiError.kt
similarity index 67%
rename from data/src/main/java/com/yapp/data/remote/utils/ApiError.kt
rename to core/network/src/main/java/com/yapp/network/model/ApiError.kt
index 947ef498..6bbcb596 100644
--- a/data/src/main/java/com/yapp/data/remote/utils/ApiError.kt
+++ b/core/network/src/main/java/com/yapp/network/model/ApiError.kt
@@ -1,4 +1,4 @@
-package com.yapp.data.remote.utils
+package com.yapp.network.model
data class ApiError(
override val message: String,
diff --git a/core/network/src/main/java/com/yapp/network/model/ResponseAuthRefreshDto.kt b/core/network/src/main/java/com/yapp/network/model/ResponseAuthRefreshDto.kt
deleted file mode 100644
index d45c731a..00000000
--- a/core/network/src/main/java/com/yapp/network/model/ResponseAuthRefreshDto.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.yapp.network.model
-
-import kotlinx.serialization.SerialName
-import kotlinx.serialization.Serializable
-
-@Serializable
-data class ResponseAuthRefreshDto(
- @SerialName("accessToken")
- val accessToken: String,
-)
diff --git a/core/network/src/main/java/com/yapp/network/utils/ApiCallUtils.kt b/core/network/src/main/java/com/yapp/network/utils/ApiCallUtils.kt
new file mode 100644
index 00000000..e80dc731
--- /dev/null
+++ b/core/network/src/main/java/com/yapp/network/utils/ApiCallUtils.kt
@@ -0,0 +1,33 @@
+package com.yapp.network.utils
+
+import com.yapp.network.model.ApiError
+import kotlinx.coroutines.CancellationException
+import retrofit2.HttpException
+import java.io.IOException
+
+inline fun safeApiCall(action: () -> T): Result {
+ return try {
+ Result.success(action())
+ } catch (exception: Throwable) {
+ if (exception is CancellationException) throw exception
+
+ val mappedException = when (exception) {
+ is HttpException -> mapHttpException(exception)
+ is IOException -> ApiError("๋คํธ์ํฌ ์ค๋ฅ ๋ฐ์")
+ else -> ApiError("์ ์ ์๋ ์ค๋ฅ ๋ฐ์")
+ }
+
+ Result.failure(mappedException)
+ }
+}
+
+fun mapHttpException(exception: HttpException): ApiError {
+ return when (exception.code()) {
+ 400 -> ApiError("์๋ชป๋ ์์ฒญ")
+ 401 -> ApiError("์ธ์ฆ์ด ํ์ํฉ๋๋ค")
+ 403 -> ApiError("๊ถํ์ด ์์ต๋๋ค")
+ 404 -> ApiError("์์ฒญํ ๋ฆฌ์์ค๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค")
+ in 500..599 -> ApiError("์๋ฒ ์ค๋ฅ")
+ else -> ApiError("์ ์ ์๋ ์๋ฒ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.")
+ }
+}
diff --git a/core/security/build.gradle.kts b/core/security/build.gradle.kts
deleted file mode 100644
index f727009c..00000000
--- a/core/security/build.gradle.kts
+++ /dev/null
@@ -1,14 +0,0 @@
-import com.yapp.convention.setNamespace
-
-plugins {
- id("orbit.android.library")
- id("orbit.android.hilt")
-}
-
-android {
- setNamespace("core.security")
-}
-
-dependencies {
- implementation(projects.core.common)
-}
diff --git a/core/security/src/main/AndroidManifest.xml b/core/security/src/main/AndroidManifest.xml
deleted file mode 100644
index 8bdb7e14..00000000
--- a/core/security/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/core/security/src/main/java/com/yapp/security/CryptoManagerImpl.kt b/core/security/src/main/java/com/yapp/security/CryptoManagerImpl.kt
deleted file mode 100644
index c21d2810..00000000
--- a/core/security/src/main/java/com/yapp/security/CryptoManagerImpl.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-package com.yapp.security
-
-import android.security.keystore.KeyGenParameterSpec
-import android.security.keystore.KeyProperties.BLOCK_MODE_GCM
-import android.security.keystore.KeyProperties.ENCRYPTION_PADDING_NONE
-import android.security.keystore.KeyProperties.KEY_ALGORITHM_AES
-import android.security.keystore.KeyProperties.PURPOSE_DECRYPT
-import android.security.keystore.KeyProperties.PURPOSE_ENCRYPT
-import com.yapp.common.security.CryptoManager
-import java.security.KeyStore
-import javax.crypto.Cipher
-import javax.crypto.KeyGenerator
-import javax.crypto.SecretKey
-import javax.crypto.spec.GCMParameterSpec
-import javax.inject.Inject
-
-class CryptoManagerImpl @Inject constructor() : CryptoManager {
-
- private val provider = "AndroidKeyStore"
- private val charset = Charsets.UTF_8
-
- private val cipher: Cipher by lazy { Cipher.getInstance("AES/GCM/NoPadding") }
- private val keyStore: KeyStore by lazy { KeyStore.getInstance(provider).apply { load(null) } }
- private val keyGenerator: KeyGenerator by lazy { KeyGenerator.getInstance(KEY_ALGORITHM_AES, provider) }
-
- /**
- * ๋ฐ์ดํฐ ์ํธํ.
- * @param keyAlias ํค ๋ณ์นญ
- * @param text ์ํธํํ ํ
์คํธ
- * @return ์ํธํ๋ ๋ฐ์ดํฐ + ์ด๊ธฐํ ๋ฒกํฐ(IV)
- */
- override fun encryptData(keyAlias: String, text: String): Pair {
- val secretKey = getOrCreateSecretKey(keyAlias)
- cipher.init(Cipher.ENCRYPT_MODE, secretKey)
- return cipher.doFinal(text.toByteArray(charset)) to cipher.iv
- }
-
- /**
- * ๋ฐ์ดํฐ๋ฅผ ๋ณตํธํ.
- * @param keyAlias ํค ๋ณ์นญ
- * @param encryptedData ์ํธํ๋ ๋ฐ์ดํฐ
- * @param iv ์ด๊ธฐํ ๋ฒกํฐ(IV)
- * @return ๋ณตํธํ๋ ๋ฐ์ดํฐ
- */
- override fun decryptData(keyAlias: String, encryptedData: ByteArray, iv: ByteArray): ByteArray {
- val secretKey = getSecretKey(keyAlias)
- cipher.init(Cipher.DECRYPT_MODE, secretKey, GCMParameterSpec(128, iv))
- return cipher.doFinal(encryptedData)
- }
-
- /**
- * ํค๊ฐ ์์ผ๋ฉด ์์ฑํ๊ณ , ์ด๋ฏธ ์กด์ฌํ๋ฉด ๊ฐ์ ธ์ด.
- * @param keyAlias ํค ๋ณ์นญ
- * @return SecretKey
- */
- private fun getOrCreateSecretKey(keyAlias: String): SecretKey =
- keyStore.getSecretKeyOrNull(keyAlias) ?: generateSecretKey(keyAlias)
-
- /**
- * ์๋ก์ด SecretKey๋ฅผ ์์ฑ.
- * @param keyAlias ํค ๋ณ์นญ
- * @return SecretKey
- */
- private fun generateSecretKey(keyAlias: String): SecretKey {
- val parameterSpec = KeyGenParameterSpec.Builder(keyAlias, PURPOSE_ENCRYPT or PURPOSE_DECRYPT)
- .apply {
- setBlockModes(BLOCK_MODE_GCM)
- setEncryptionPaddings(ENCRYPTION_PADDING_NONE)
- }.build()
- keyGenerator.init(parameterSpec)
- return keyGenerator.generateKey()
- }
-
- /**
- * KeyStore์์ SecretKey ๊ฐ์ ธ์ค๊ธฐ.
- * @param keyAlias ํค ๋ณ์นญ
- * @return SecretKey
- */
- private fun getSecretKey(keyAlias: String): SecretKey =
- keyStore.getSecretKeyOrNull(keyAlias)
- ?: throw IllegalStateException("SecretKey for alias $keyAlias does not exist")
-
- /**
- * ํค ์กด์ฌํ์ง ์์ผ๋ฉด null ๋ฐํ.
- */
- private fun KeyStore.getSecretKeyOrNull(keyAlias: String): SecretKey? =
- (getEntry(keyAlias, null) as? KeyStore.SecretKeyEntry)?.secretKey
-}
diff --git a/core/security/src/main/java/com/yapp/security/di/SecurityModule.kt b/core/security/src/main/java/com/yapp/security/di/SecurityModule.kt
deleted file mode 100644
index 3e8a6c99..00000000
--- a/core/security/src/main/java/com/yapp/security/di/SecurityModule.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.yapp.security.di
-
-import com.yapp.common.security.CryptoManager
-import com.yapp.security.CryptoManagerImpl
-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 SecurityModule {
- @Singleton
- @Binds
- abstract fun bindsCryptoManager(cryptoManagerImpl: CryptoManagerImpl): CryptoManager
-}
diff --git a/core/ui/src/main/java/com/yapp/ui/base/BaseViewModel.kt b/core/ui/src/main/java/com/yapp/ui/base/BaseViewModel.kt
deleted file mode 100644
index 52bf9a30..00000000
--- a/core/ui/src/main/java/com/yapp/ui/base/BaseViewModel.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-package com.yapp.ui.base
-
-import androidx.lifecycle.ViewModel
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.catch
-import org.orbitmvi.orbit.ContainerHost
-import org.orbitmvi.orbit.syntax.simple.intent
-import org.orbitmvi.orbit.syntax.simple.postSideEffect
-import org.orbitmvi.orbit.syntax.simple.reduce
-import org.orbitmvi.orbit.viewmodel.container
-
-abstract class BaseViewModel(
- initialState: UI_STATE,
-) : ViewModel(), ContainerHost {
-
- override val container = container(initialState)
- val currentState: UI_STATE
- get() = container.stateFlow.value
-
- /**
- * UI ์ํ ์
๋ฐ์ดํธ
- * @param reducer ํ์ฌ ์ํ๋ฅผ ์์ ํ๋ ๋๋ค์
- */
- protected fun updateState(reducer: UI_STATE.() -> UI_STATE) = intent {
- reduce { reducer(state) }
- }
-
- /**
- * ๋จ์ผ ๋ถ์ ํจ๊ณผ ์ ๋ฌ
- * @param effect ์ ๋ฌํ ๋ถ์ ํจ๊ณผ
- */
- protected fun emitSideEffect(effect: SIDE_EFFECT) = intent {
- postSideEffect(effect)
- }
-
- /**
- * ์ฌ๋ฌ ๋ถ์ ํจ๊ณผ ์ ๋ฌ
- * @param effects ์ ๋ฌํ ๋ถ์ ํจ๊ณผ ๋ฆฌ์คํธ
- */
- protected fun emitSideEffects(vararg effects: SIDE_EFFECT) = intent {
- effects.forEach { postSideEffect(it) }
- }
-
- /**
- * Flow ๊ตฌ๋
ํ๊ณ ์ํ ์
๋ฐ์ดํธ or ๋ถ์ ํจ๊ณผ ์ฒ๋ฆฌ
- * @param flow ๊ตฌ๋
ํ Flow
- * @param onEach ๊ฐ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ๋ก์ง
- * @param onError ์๋ฌ ์ฒ๋ฆฌ ๋ก์ง
- */
- protected fun collectFlow(
- flow: Flow,
- onEach: (T) -> Unit,
- onError: ((Throwable) -> Unit)? = null,
- ) = intent {
- flow.catch { onError?.invoke(it) }
- .collect { onEach(it) }
- }
-
- /**
- * ๋น๋๊ธฐ ์์
์ํํ๊ณ ์ํ ์
๋ฐ์ดํธ or ๋ถ์ ํจ๊ณผ ์ฒ๋ฆฌ
- * @param block ์คํํ suspend ๋ธ๋ก
- * @param onError ์๋ฌ ์ฒ๋ฆฌ ๋ก์ง (์ต์
)
- */
- protected fun launchWithErrorHandler(
- block: suspend () -> Unit,
- onError: ((Throwable) -> Unit)? = null,
- ) = intent {
- kotlin.runCatching {
- block()
- }.onFailure { onError?.invoke(it) }
- }
-}
diff --git a/core/ui/src/main/java/com/yapp/ui/component/bottomsheet/BottomSheetContent.kt b/core/ui/src/main/java/com/yapp/ui/component/bottomsheet/BottomSheetContent.kt
new file mode 100644
index 00000000..23dba597
--- /dev/null
+++ b/core/ui/src/main/java/com/yapp/ui/component/bottomsheet/BottomSheetContent.kt
@@ -0,0 +1,6 @@
+package com.yapp.ui.component.bottomsheet
+
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.runtime.Composable
+
+typealias BottomSheetContent = @Composable BoxScope.() -> Unit
diff --git a/core/ui/src/main/java/com/yapp/ui/component/OrbitBottomSheet.kt b/core/ui/src/main/java/com/yapp/ui/component/bottomsheet/OrbitBottomSheetLayout.kt
similarity index 69%
rename from core/ui/src/main/java/com/yapp/ui/component/OrbitBottomSheet.kt
rename to core/ui/src/main/java/com/yapp/ui/component/bottomsheet/OrbitBottomSheetLayout.kt
index baf7e50b..ff048441 100644
--- a/core/ui/src/main/java/com/yapp/ui/component/OrbitBottomSheet.kt
+++ b/core/ui/src/main/java/com/yapp/ui/component/bottomsheet/OrbitBottomSheetLayout.kt
@@ -1,22 +1,19 @@
-package com.yapp.ui.component
+package com.yapp.ui.component.bottomsheet
import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
+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.shape.RoundedCornerShape
+import androidx.compose.material.ModalBottomSheetLayout
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.ModalBottomSheet
-import androidx.compose.material3.SheetState
import androidx.compose.material3.Text
-import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Rect
@@ -33,42 +30,31 @@ import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun OrbitBottomSheet(
+fun OrbitBottomSheetLayout(
modifier: Modifier = Modifier,
- sheetState: SheetState = rememberModalBottomSheetState(
- skipPartiallyExpanded = true,
- ),
- isSheetOpen: Boolean,
- onDismissRequest: () -> Unit = {},
+ sheetState: OrbitBottomSheetState,
shape: Shape = RoundedCornerShape(topStart = 30.dp, topEnd = 30.dp),
containerColor: Color = OrbitTheme.colors.gray_800,
strokeColor: Color = OrbitTheme.colors.gray_700,
strokeThickness: Dp = 1.dp,
content: @Composable () -> Unit,
) {
- val scope = rememberCoroutineScope()
- if (isSheetOpen) {
- ModalBottomSheet(
- modifier = modifier,
- sheetState = sheetState,
- shape = shape,
- onDismissRequest = {
- scope.launch {
- sheetState.hide()
- onDismissRequest()
- }
- },
- containerColor = containerColor,
- dragHandle = null,
- ) {
+ ModalBottomSheetLayout(
+ modifier = modifier.navigationBarsPadding(),
+ sheetState = sheetState.state,
+ sheetShape = shape,
+ sheetBackgroundColor = containerColor,
+ sheetContent = {
Box {
- content()
+ sheetState.content?.invoke(this)
BottomSheetTopRoundedStroke(
strokeColor = strokeColor,
strokeThickness = strokeThickness,
)
}
- }
+ },
+ ) {
+ content()
}
}
@@ -139,43 +125,31 @@ fun BottomSheetTopRoundedStroke(
@Preview
@Composable
fun OrbitBottomSheetPreview() {
- var isSheetOpen by rememberSaveable { mutableStateOf(true) }
- val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
+ val sheetState = rememberOrbitBottomSheetState()
val scope = rememberCoroutineScope()
OrbitTheme {
- Button(
- onClick = {
- scope.launch {
- sheetState.show()
- }.invokeOnCompletion {
- if (!isSheetOpen) {
- isSheetOpen = true
- }
- }
- },
- ) {
- Text("Toggle Bottom Sheet")
- }
-
- OrbitBottomSheet(
- isSheetOpen = isSheetOpen,
+ OrbitBottomSheetLayout(
sheetState = sheetState,
- onDismissRequest = { isSheetOpen = !isSheetOpen },
content = {
Box(
modifier = Modifier
- .fillMaxWidth()
- .height(600.dp),
+ .fillMaxSize()
+ .background(color = OrbitTheme.colors.white),
contentAlignment = Alignment.Center,
) {
Button(
onClick = {
scope.launch {
- sheetState.hide()
- }.invokeOnCompletion {
- if (isSheetOpen) {
- isSheetOpen = false
+ sheetState.show {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(500.dp),
+ contentAlignment = Alignment.Center,
+ ) {
+ Text("This is a bottom sheet content")
+ }
}
}
},
diff --git a/core/ui/src/main/java/com/yapp/ui/component/bottomsheet/OrbitBottomSheetState.kt b/core/ui/src/main/java/com/yapp/ui/component/bottomsheet/OrbitBottomSheetState.kt
new file mode 100644
index 00000000..b558e1d8
--- /dev/null
+++ b/core/ui/src/main/java/com/yapp/ui/component/bottomsheet/OrbitBottomSheetState.kt
@@ -0,0 +1,49 @@
+package com.yapp.ui.component.bottomsheet
+
+import androidx.compose.material.ModalBottomSheetState
+import androidx.compose.material.ModalBottomSheetValue
+import androidx.compose.material.rememberModalBottomSheetState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+
+@Composable
+fun rememberOrbitBottomSheetState(): OrbitBottomSheetState {
+ val contentState = remember { mutableStateOf(null) }
+
+ val bottomSheetState = rememberModalBottomSheetState(
+ initialValue = ModalBottomSheetValue.Hidden,
+ confirmValueChange = { value ->
+ if (value == ModalBottomSheetValue.Hidden) {
+ contentState.value = null
+ }
+ true
+ },
+ skipHalfExpanded = true,
+ )
+
+ return remember(contentState, bottomSheetState) {
+ OrbitBottomSheetState(
+ state = bottomSheetState,
+ contentState = contentState,
+ setContent = { contentState.value = it },
+ )
+ }
+}
+
+class OrbitBottomSheetState(
+ val state: ModalBottomSheetState,
+ val contentState: State,
+ private val setContent: (BottomSheetContent?) -> Unit,
+) {
+ val content: BottomSheetContent?
+ get() = contentState.value
+
+ suspend fun show(sheetContent: BottomSheetContent) {
+ setContent(sheetContent)
+ state.show()
+ }
+
+ suspend fun hide() = state.hide()
+}
diff --git a/core/ui/src/main/java/com/yapp/ui/component/button/OrbitButton.kt b/core/ui/src/main/java/com/yapp/ui/component/button/OrbitButton.kt
index b1c97a1d..ddfa218a 100644
--- a/core/ui/src/main/java/com/yapp/ui/component/button/OrbitButton.kt
+++ b/core/ui/src/main/java/com/yapp/ui/component/button/OrbitButton.kt
@@ -33,6 +33,7 @@ fun OrbitButton(
modifier: Modifier = Modifier,
onClick: () -> Unit,
enabled: Boolean = false,
+ useFillMaxWidth: Boolean = true,
debounceTime: Long = 500L,
height: Dp = 54.dp,
containerColor: Color = OrbitTheme.colors.main,
@@ -77,7 +78,9 @@ fun OrbitButton(
),
interactionSource = interactionSource,
modifier = modifier
- .fillMaxWidth()
+ .then(
+ if (useFillMaxWidth) Modifier.fillMaxWidth() else Modifier,
+ )
.padding(padding)
.height(height - padding * 2),
) {
diff --git a/core/ui/src/main/java/com/yapp/ui/component/navigation/NavigationBarScrim.kt b/core/ui/src/main/java/com/yapp/ui/component/navigation/NavigationBarScrim.kt
new file mode 100644
index 00000000..c0bcb5a7
--- /dev/null
+++ b/core/ui/src/main/java/com/yapp/ui/component/navigation/NavigationBarScrim.kt
@@ -0,0 +1,26 @@
+package com.yapp.ui.component.navigation
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.navigationBars
+import androidx.compose.foundation.layout.windowInsetsBottomHeight
+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.zIndex
+
+@Composable
+fun BoxScope.NavigationBarScrim() {
+ Box(
+ modifier = Modifier
+ .align(Alignment.BottomCenter)
+ .fillMaxWidth()
+ .windowInsetsBottomHeight(WindowInsets.navigationBars)
+ .background(Color.Black)
+ .zIndex(1f),
+ )
+}
diff --git a/core/ui/src/main/java/com/yapp/ui/component/timepicker/OrbitPicker.kt b/core/ui/src/main/java/com/yapp/ui/component/timepicker/OrbitPicker.kt
index ee48f71d..6b379cb3 100644
--- a/core/ui/src/main/java/com/yapp/ui/component/timepicker/OrbitPicker.kt
+++ b/core/ui/src/main/java/com/yapp/ui/component/timepicker/OrbitPicker.kt
@@ -14,8 +14,11 @@ import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
@@ -23,16 +26,22 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.yapp.designsystem.theme.OrbitTheme
import kotlinx.coroutines.launch
-import java.util.Locale
+import java.time.LocalTime
+
+enum class TimePeriod(val displayName: String) {
+ AM("์ค์ "),
+ PM("์คํ"),
+ ;
+
+ override fun toString(): String = displayName
+}
@Composable
fun OrbitPicker(
modifier: Modifier = Modifier,
itemSpacing: Dp = 2.dp,
- initialAmPm: String = "์ค์ ",
- initialHour: String = "1",
- initialMinute: String = "00",
- onValueChange: (String, Int, Int) -> Unit,
+ initialTime: LocalTime = LocalTime.now(),
+ onValueChange: (LocalTime) -> Unit,
) {
Surface(
modifier = modifier
@@ -46,23 +55,24 @@ fun OrbitPicker(
.wrapContentSize()
.background(OrbitTheme.colors.gray_900),
) {
- val amPmItems = remember { listOf("์คํ", "์ค์ ") }
- val hourItems = remember { (1..12).map { it.toString() } }
- val minuteItems = remember { (0..59).map { String.format(Locale.ROOT, "%02d", it) } }
+ val amPmItems = remember { TimePeriod.entries.toList().map { it.displayName } }
+ val hourItems = remember { (1..12).toList() }
+ val minuteItems = remember { (0..59).toList() }
val amPmPickerState = rememberPickerState(
- selectedItem = amPmItems.indexOf(initialAmPm).toString(),
- startIndex = amPmItems.indexOf(initialAmPm),
+ initialIndex = if (initialTime.hour < 12) 0 else 1,
+ items = amPmItems,
)
val hourPickerState = rememberPickerState(
- selectedItem = hourItems.indexOf(initialHour).toString(),
- startIndex = hourItems.indexOf(initialHour),
+ initialIndex = hourItems.indexOf(if (initialTime.hour % 12 == 0) 12 else initialTime.hour % 12),
+ items = hourItems,
)
val minutePickerState = rememberPickerState(
- selectedItem = minuteItems.indexOf(initialMinute).toString(),
- startIndex = minuteItems.indexOf(initialMinute),
+ initialIndex = minuteItems.indexOf(initialTime.minute),
+ items = minuteItems,
)
+ var previousHour by remember { mutableIntStateOf(initialTime.hour) }
val scope = rememberCoroutineScope()
Box(modifier = Modifier.fillMaxWidth()) {
@@ -71,7 +81,7 @@ fun OrbitPicker(
.fillMaxWidth()
.align(Alignment.Center)
.padding(horizontal = 20.dp)
- .height(50.dp)
+ .height(45.dp)
.background(OrbitTheme.colors.gray_700, shape = RoundedCornerShape(12.dp)),
)
@@ -86,7 +96,7 @@ fun OrbitPicker(
items = amPmItems,
visibleItemsCount = 3,
itemSpacing = itemSpacing,
- textStyle = OrbitTheme.typography.title2Medium,
+ textStyle = OrbitTheme.typography.heading1SemiBold,
modifier = Modifier.weight(1f),
textModifier = Modifier.padding(8.dp),
infiniteScroll = false,
@@ -105,7 +115,7 @@ fun OrbitPicker(
items = hourItems,
visibleItemsCount = 5,
itemSpacing = itemSpacing,
- textStyle = OrbitTheme.typography.title2Medium,
+ textStyle = OrbitTheme.typography.heading1SemiBold,
modifier = Modifier.weight(1f),
textModifier = Modifier.padding(8.dp),
infiniteScroll = true,
@@ -116,12 +126,17 @@ fun OrbitPicker(
minutePickerState,
onValueChange,
)
- },
- onScrollCompleted = {
scope.launch {
+ val currentHour = hourPickerState.selectedItem
val currentIndex = amPmPickerState.lazyListState.firstVisibleItemIndex % amPmItems.size
val nextIndex = (currentIndex + 1) % amPmItems.size
- amPmPickerState.lazyListState.animateScrollToItem(nextIndex)
+
+ if ((currentHour == 12 && previousHour == 11) ||
+ (currentHour == 11 && previousHour == 12)
+ ) {
+ amPmPickerState.lazyListState.animateScrollToItem(nextIndex)
+ }
+ previousHour = currentHour
}
},
)
@@ -131,10 +146,11 @@ fun OrbitPicker(
items = minuteItems,
visibleItemsCount = 5,
itemSpacing = itemSpacing,
- textStyle = OrbitTheme.typography.title2Medium,
+ textStyle = OrbitTheme.typography.heading1SemiBold,
modifier = Modifier.weight(1f),
textModifier = Modifier.padding(8.dp),
infiniteScroll = true,
+ itemFormatter = { it.toString().padStart(2, '0') },
onValueChange = {
onPickerValueChange(
amPmPickerState,
@@ -151,21 +167,32 @@ fun OrbitPicker(
}
private fun onPickerValueChange(
- amPmState: PickerState,
- hourState: PickerState,
- minuteState: PickerState,
- onValueChange: (String, Int, Int) -> Unit,
+ amPmState: PickerState,
+ hourState: PickerState,
+ minuteState: PickerState,
+ onValueChange: (LocalTime) -> Unit,
) {
val amPm = amPmState.selectedItem
- val hour = hourState.selectedItem.toIntOrNull() ?: 0
- val minute = minuteState.selectedItem.toIntOrNull() ?: 0
- onValueChange(amPm, hour, minute)
+ val hour = hourState.selectedItem
+ val minute = minuteState.selectedItem
+
+ val adjustedHour = if (amPm == TimePeriod.AM.displayName && hour == 12) {
+ 0
+ } else if (amPm == TimePeriod.PM.displayName && hour != 12) {
+ hour + 12
+ } else {
+ hour
+ }
+
+ val newTime = LocalTime.of(adjustedHour, minute)
+
+ onValueChange(newTime)
}
@Preview(showBackground = true)
@Composable
fun OrbitPickerPreview() {
- OrbitPicker { amPm, hour, minute ->
- Log.d("OrbitPicker", "selectedAmPm: $amPm, selectedHour: $hour, selectedMinute: $minute")
+ OrbitPicker() { newTime ->
+ Log.d("OrbitPicker", "selectedTime: $newTime")
}
}
diff --git a/core/ui/src/main/java/com/yapp/ui/component/timepicker/OrbitPickerItem.kt b/core/ui/src/main/java/com/yapp/ui/component/timepicker/OrbitPickerItem.kt
index 76ba98d8..729421a2 100644
--- a/core/ui/src/main/java/com/yapp/ui/component/timepicker/OrbitPickerItem.kt
+++ b/core/ui/src/main/java/com/yapp/ui/component/timepicker/OrbitPickerItem.kt
@@ -10,8 +10,11 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -30,17 +33,17 @@ import kotlinx.coroutines.flow.map
import kotlin.math.abs
@Composable
-fun OrbitPickerItem(
+fun OrbitPickerItem(
modifier: Modifier = Modifier,
- items: List,
- state: PickerState = rememberPickerState(),
+ items: List,
+ state: PickerState = rememberPickerState(items = items),
visibleItemsCount: Int,
textModifier: Modifier = Modifier,
+ itemFormatter: (T) -> String = { it.toString() },
infiniteScroll: Boolean = true,
textStyle: TextStyle,
itemSpacing: Dp,
- onValueChange: (String) -> Unit,
- onScrollCompleted: () -> Unit = {},
+ onValueChange: (T) -> Unit,
) {
val visibleItemsMiddle = visibleItemsCount / 2
val listScrollCount = if (infiniteScroll) Int.MAX_VALUE else items.size + visibleItemsMiddle * 2
@@ -48,31 +51,28 @@ fun OrbitPickerItem(
val listState = state.lazyListState
val flingBehavior = rememberSnapFlingBehavior(lazyListState = listState)
- val itemHeightPixels = remember { mutableIntStateOf(0) }
- val itemHeightDp = with(LocalDensity.current) { itemHeightPixels.intValue.toDp() }
+ var itemHeightPixels by remember { mutableIntStateOf(0) }
+ val itemHeightDp = with(LocalDensity.current) { itemHeightPixels.toDp() }
- LaunchedEffect(key1 = state.startIndex) {
- val safeStartIndex = state.startIndex.takeIf { it >= 0 } ?: 0
+ LaunchedEffect(state.initialIndex) {
+ val safeStartIndex = state.initialIndex
val listStartIndex = if (infiniteScroll) {
- calculateStartIndex(infiniteScroll, items.size, listScrollMiddle, visibleItemsMiddle, safeStartIndex)
+ getStartIndexForInfiniteScroll(itemHeightPixels, listScrollMiddle, visibleItemsMiddle, safeStartIndex)
} else {
safeStartIndex
}
-
listState.scrollToItem(listStartIndex, 0)
if (!infiniteScroll) {
- val selectedItem = items.getOrNull(safeStartIndex) ?: ""
- if (selectedItem != state.selectedItem) {
- state.selectedItem = selectedItem
- onValueChange(selectedItem)
+ val selectedItem = items.getOrNull(listStartIndex) ?: items.first()
+ if (listStartIndex != state.selectedIndex.value) {
+ state.updateSelectedIndex(listStartIndex)
}
+ onValueChange(selectedItem)
}
}
LaunchedEffect(listState) {
- var previousAdjustedIndex = -1
-
snapshotFlow { listState.layoutInfo }
.map { layoutInfo ->
val centerOffset = layoutInfo.viewportStartOffset +
@@ -82,30 +82,20 @@ fun OrbitPickerItem(
abs(itemCenter - centerOffset)
}?.index
}
- .distinctUntilChanged()
- .collect { centerIndex ->
- if (centerIndex != null) {
- val adjustedIndex = if (infiniteScroll) {
- centerIndex % items.size
- } else {
- centerIndex - visibleItemsMiddle
- }.coerceIn(0, items.size - 1)
-
- val newValue = items[adjustedIndex]
-
+ .map { centerIndex ->
+ centerIndex?.let { index ->
if (infiniteScroll) {
- val lastIndex = items.size - 1
- if ((previousAdjustedIndex == 0 && adjustedIndex == lastIndex) ||
- (previousAdjustedIndex == lastIndex && adjustedIndex == 0)
- ) {
- onScrollCompleted()
- }
- }
- if (newValue != state.selectedItem) {
- state.selectedItem = newValue
- onValueChange(newValue)
+ index % items.size
+ } else {
+ (index - visibleItemsMiddle).coerceIn(0, items.size - 1)
}
- previousAdjustedIndex = adjustedIndex
+ }
+ }
+ .distinctUntilChanged()
+ .collect { adjustedIndex ->
+ if (adjustedIndex != null && adjustedIndex != state.selectedIndex.value) {
+ state.updateSelectedIndex(adjustedIndex)
+ onValueChange(items[adjustedIndex])
}
}
}
@@ -122,8 +112,9 @@ fun OrbitPickerItem(
.height(totalItemHeight * visibleItemsCount)
.pointerInput(Unit) { detectVerticalDragGestures { change, _ -> change.consume() } },
) {
- items(listScrollCount) { index ->
- val layoutInfo = listState.layoutInfo
+ items(listScrollCount, key = { index -> index }) { index ->
+ val layoutInfo by remember { derivedStateOf { listState.layoutInfo } }
+
val viewportCenterOffset = layoutInfo.viewportStartOffset +
(layoutInfo.viewportEndOffset - layoutInfo.viewportStartOffset) / 2
@@ -141,15 +132,22 @@ fun OrbitPickerItem(
val scaleY = 1f - (0.2f * (distanceFromCenter / maxDistance)).coerceIn(0f, 0.4f)
+ val item = getItemForIndex(
+ index = index,
+ items = items,
+ infiniteScroll = infiniteScroll,
+ visibleItemsMiddle = visibleItemsMiddle,
+ )
+
Text(
- text = getItemForIndex(index, items, infiniteScroll, visibleItemsMiddle),
+ text = item?.let { itemFormatter(it) } ?: "",
maxLines = 1,
style = textStyle,
color = OrbitTheme.colors.white.copy(alpha = alpha),
modifier = Modifier
.padding(vertical = itemSpacing / 2)
.graphicsLayer(scaleY = scaleY)
- .onSizeChanged { size -> itemHeightPixels.intValue = size.height }
+ .onSizeChanged { size -> itemHeightPixels = size.height }
.then(textModifier),
)
}
@@ -157,37 +155,31 @@ fun OrbitPickerItem(
}
}
-/**
- * ๋ฌดํ ์คํฌ๋กค๊ณผ ์ด๊ธฐ ์์ ์ธ๋ฑ์ค๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋ฆฌ์คํธ์ ์์ ์ธ๋ฑ์ค๋ฅผ ๊ณ์ฐํฉ๋๋ค.
- */
-private fun calculateStartIndex(
- infiniteScroll: Boolean,
+private fun getStartIndexForInfiniteScroll(
itemSize: Int,
listScrollMiddle: Int,
visibleItemsMiddle: Int,
startIndex: Int,
): Int {
- return if (infiniteScroll) {
- listScrollMiddle - listScrollMiddle % itemSize - visibleItemsMiddle + startIndex
- } else {
- startIndex + visibleItemsMiddle
+ if (itemSize == 0) {
+ return listScrollMiddle - visibleItemsMiddle + startIndex
}
+
+ return listScrollMiddle - listScrollMiddle % itemSize - visibleItemsMiddle + startIndex
}
-/**
- * ์ฃผ์ด์ง ์ธ๋ฑ์ค์ ํด๋นํ๋ ํญ๋ชฉ์ ๋ฐํํฉ๋๋ค.
- * ๋ฌดํ ์คํฌ๋กค๊ณผ ๋ณด์ด๋ ํญ๋ชฉ์ ๊ฐ์๋ฅผ ๊ณ ๋ คํฉ๋๋ค.
- */
-private fun getItemForIndex(
+private fun getItemForIndex(
index: Int,
- items: List,
+ items: List,
infiniteScroll: Boolean,
visibleItemsMiddle: Int,
-): String {
+): T? {
+ require(items.isNotEmpty()) { "Items list cannot be empty." }
+
return if (!infiniteScroll) {
- items.getOrNull(index - visibleItemsMiddle) ?: ""
+ items.getOrNull(index - visibleItemsMiddle)
} else {
- items.getOrNull(index % items.size) ?: ""
+ items.getOrNull(index % items.size)
}
}
@@ -197,7 +189,10 @@ fun OrbitPickerItemPreview() {
OrbitTheme {
OrbitPickerItem(
items = (0..100).map { it.toString() },
- state = rememberPickerState(),
+ state = rememberPickerState(
+ initialIndex = 50,
+ items = (0..100).map { it.toString() },
+ ),
visibleItemsCount = 5,
textStyle = TextStyle.Default,
itemSpacing = 8.dp,
diff --git a/core/ui/src/main/java/com/yapp/ui/component/timepicker/OrbitYearMonthPicker.kt b/core/ui/src/main/java/com/yapp/ui/component/timepicker/OrbitYearMonthPicker.kt
index 7d90d39b..012623e5 100644
--- a/core/ui/src/main/java/com/yapp/ui/component/timepicker/OrbitYearMonthPicker.kt
+++ b/core/ui/src/main/java/com/yapp/ui/component/timepicker/OrbitYearMonthPicker.kt
@@ -37,23 +37,29 @@ fun OrbitYearMonthPicker(
) {
val screenWidth = LocalConfiguration.current.screenWidthDp.dp
+ val lunarItems = remember { listOf("์๋ ฅ", "์๋ ฅ") }
+ val yearItems = remember { (1900..2024).map { it.toString() } }
+ val monthItems = remember { (1..12).map { it.toString() } }
+
+ val startIndexYear = yearItems.indexOf(initialYear).coerceAtLeast(0)
+ val startIndexMonth = monthItems.indexOf(initialMonth).coerceAtLeast(0)
+
val lunarState = remember { mutableStateOf(initialLunar) }
val yearState = remember { mutableIntStateOf(initialYear.toInt()) }
val monthState = remember { mutableIntStateOf(initialMonth.toInt()) }
-
- val maxDay = getMaxDaysInMonth(yearState.intValue, monthState.intValue)
- val dayItems = (1..maxDay).map { it.toString() }
-
- val startIndexYear = (1900..2024).map { it.toString() }.indexOf(initialYear).takeIf { it >= 0 } ?: 0
- val startIndexMonth = (1..12).map { it.toString() }.indexOf(initialMonth).takeIf { it >= 0 } ?: 0
- val startIndexDay = dayItems.indexOf(initialDay).takeIf { it >= 0 } ?: 0
-
val dayState = remember { mutableIntStateOf(initialDay.toInt()) }
- val yearPickerState = rememberPickerState(startIndex = startIndexYear)
- val monthPickerState = rememberPickerState(startIndex = startIndexMonth)
- val dayPickerState = rememberPickerState(startIndex = startIndexDay)
+ val yearPickerState = rememberPickerState(initialIndex = startIndexYear, items = yearItems)
+ val monthPickerState = rememberPickerState(initialIndex = startIndexMonth, items = monthItems)
+ // dayItems๋ year/month ๋ณ๊ฒฝ ์๋ง๋ค ๋๊ธฐํ
+ val dayItems = remember(yearState.intValue, monthState.intValue) {
+ (1..getMaxDaysInMonth(yearState.intValue, monthState.intValue)).map { it.toString() }
+ }
+ val startIndexDay = dayItems.indexOf(initialDay).coerceAtLeast(0)
+ val dayPickerState = rememberPickerState(initialIndex = startIndexDay, items = dayItems)
+
+ // ์ผ ์ ๋์ด๊ฐ๋ ๊ฒฝ์ฐ ์กฐ์
LaunchedEffect(yearState.intValue, monthState.intValue) {
val newMaxDay = getMaxDaysInMonth(yearState.intValue, monthState.intValue)
if (dayState.intValue > newMaxDay) {
@@ -61,25 +67,18 @@ fun OrbitYearMonthPicker(
}
}
+ // ๋ณ๊ฒฝ ์ฝ๋ฐฑ
LaunchedEffect(lunarState.value, yearState.intValue, monthState.intValue, dayState.intValue) {
onValueChange(lunarState.value, yearState.intValue, monthState.intValue, dayState.intValue)
}
- Surface(
- modifier = modifier.fillMaxWidth(),
- ) {
+ Surface(modifier = modifier.fillMaxWidth()) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Bottom,
modifier = Modifier.background(OrbitTheme.colors.gray_900),
) {
- val lunarItems = listOf("์๋ ฅ", "์๋ ฅ")
- val yearItems = (1900..2024).map { it.toString() }
- val monthItems = (1..12).map { it.toString() }
-
- Box(
- modifier = Modifier.fillMaxWidth(),
- ) {
+ Box(modifier = Modifier.fillMaxWidth()) {
Box(
modifier = Modifier
.fillMaxWidth()
@@ -90,7 +89,9 @@ fun OrbitYearMonthPicker(
)
Row(
- modifier = Modifier.fillMaxWidth().padding(horizontal = screenWidth * 0.1f),
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = screenWidth * 0.1f),
verticalAlignment = Alignment.CenterVertically,
) {
OrbitPickerItem(
@@ -142,9 +143,6 @@ fun OrbitYearMonthPicker(
}
}
-/**
- * ํน์ ์ฐ๋์ ์์ ๋ฐ๋ฅธ ์ต๋ ์ผ ์๋ฅผ ๋ฐํ.
- */
private fun getMaxDaysInMonth(year: Int, month: Int): Int {
return when (month) {
1, 3, 5, 7, 8, 10, 12 -> 31
@@ -154,9 +152,6 @@ private fun getMaxDaysInMonth(year: Int, month: Int): Int {
}
}
-/**
- * ์ค๋
๊ณ์ฐ
- */
private fun isLeapYear(year: Int): Boolean {
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
}
diff --git a/core/ui/src/main/java/com/yapp/ui/component/timepicker/PickerState.kt b/core/ui/src/main/java/com/yapp/ui/component/timepicker/PickerState.kt
index 120e3398..2e8b9793 100644
--- a/core/ui/src/main/java/com/yapp/ui/component/timepicker/PickerState.kt
+++ b/core/ui/src/main/java/com/yapp/ui/component/timepicker/PickerState.kt
@@ -4,16 +4,29 @@ import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
-class PickerState(
+class PickerState(
val lazyListState: LazyListState,
- var selectedItem: String,
- var startIndex: Int,
-)
+ val initialIndex: Int,
+ private val items: List,
+) {
+ private val _selectedIndex = MutableStateFlow(initialIndex)
+ val selectedIndex: StateFlow
+ get() = _selectedIndex
+
+ val selectedItem: T
+ get() = items.getOrElse(_selectedIndex.value) { items.first() }
+
+ fun updateSelectedIndex(newIndex: Int) {
+ _selectedIndex.value = newIndex.coerceIn(0, items.size - 1)
+ }
+}
@Composable
-fun rememberPickerState(
+fun rememberPickerState(
lazyListState: LazyListState = rememberLazyListState(),
- selectedItem: String = "",
- startIndex: Int = 0,
-): PickerState = remember { PickerState(lazyListState, selectedItem, startIndex) }
+ initialIndex: Int = 0,
+ items: List,
+): PickerState = remember { PickerState(lazyListState, initialIndex, items) }
diff --git a/data/build.gradle.kts b/data/build.gradle.kts
index f2c52f14..354e0002 100644
--- a/data/build.gradle.kts
+++ b/data/build.gradle.kts
@@ -10,20 +10,16 @@ android {
}
dependencies {
- implementation(projects.domain)
implementation(projects.core.network)
+ implementation(projects.core.database)
implementation(projects.core.datastore)
+
+ implementation(projects.domain)
implementation(projects.core.media)
implementation(projects.core.remoteconfig)
- ksp(libs.androidx.room.compiler)
- implementation(libs.androidx.room.ktx)
- implementation(libs.androidx.room.runtime)
- implementation(libs.androidx.room.paging)
-
implementation(libs.kotlinx.serialization.json)
implementation(libs.retrofit.core)
implementation(libs.retrofit.kotlin.serialization)
implementation(libs.okhttp.logging)
- implementation(libs.androidx.datastore)
}
diff --git a/data/src/main/java/com/yapp/data/remote/di/RepositoryModule.kt b/data/src/main/java/com/yapp/data/di/RepositoryModule.kt
similarity index 67%
rename from data/src/main/java/com/yapp/data/remote/di/RepositoryModule.kt
rename to data/src/main/java/com/yapp/data/di/RepositoryModule.kt
index ed92d2db..8e3cc519 100644
--- a/data/src/main/java/com/yapp/data/remote/di/RepositoryModule.kt
+++ b/data/src/main/java/com/yapp/data/di/RepositoryModule.kt
@@ -1,11 +1,11 @@
-package com.yapp.data.remote.di
+package com.yapp.data.di
-import com.yapp.data.remote.repositoryimpl.DummyRepositoryImpl
-import com.yapp.data.remote.repositoryimpl.FortuneRepositoryImpl
-import com.yapp.data.remote.repositoryimpl.RemoteConfigRepositoryImpl
-import com.yapp.data.remote.repositoryimpl.SignUpRepositoryImpl
-import com.yapp.data.remote.repositoryimpl.UserInfoRepositoryImpl
-import com.yapp.domain.repository.DummyRepository
+import com.yapp.data.repositoryimpl.AlarmRepositoryImpl
+import com.yapp.data.repositoryimpl.FortuneRepositoryImpl
+import com.yapp.data.repositoryimpl.RemoteConfigRepositoryImpl
+import com.yapp.data.repositoryimpl.SignUpRepositoryImpl
+import com.yapp.data.repositoryimpl.UserInfoRepositoryImpl
+import com.yapp.domain.repository.AlarmRepository
import com.yapp.domain.repository.FortuneRepository
import com.yapp.domain.repository.RemoteConfigRepository
import com.yapp.domain.repository.SignUpRepository
@@ -21,15 +21,15 @@ import javax.inject.Singleton
abstract class RepositoryModule {
@Binds
@Singleton
- abstract fun bindsDummyRepository(
- dummyRepository: DummyRepositoryImpl,
- ): DummyRepository
+ abstract fun bindsAlarmRepository(
+ alarmRepository: AlarmRepositoryImpl,
+ ): AlarmRepository
@Binds
@Singleton
- abstract fun bindsSignUpRepository(
- signUpRepository: SignUpRepositoryImpl,
- ): SignUpRepository
+ abstract fun bindsFortuneRepository(
+ fortuneRepository: FortuneRepositoryImpl,
+ ): FortuneRepository
@Binds
@Singleton
@@ -39,9 +39,9 @@ abstract class RepositoryModule {
@Binds
@Singleton
- abstract fun bindsFortuneRepository(
- fortuneRepository: FortuneRepositoryImpl,
- ): FortuneRepository
+ abstract fun bindsSignUpRepository(
+ signUpRepository: SignUpRepositoryImpl,
+ ): SignUpRepository
@Binds
@Singleton
diff --git a/data/src/main/java/com/yapp/data/local/datasource/AlarmLocalDataSource.kt b/data/src/main/java/com/yapp/data/local/datasource/AlarmLocalDataSource.kt
index f41b0caa..8ff592d2 100644
--- a/data/src/main/java/com/yapp/data/local/datasource/AlarmLocalDataSource.kt
+++ b/data/src/main/java/com/yapp/data/local/datasource/AlarmLocalDataSource.kt
@@ -1,14 +1,12 @@
package com.yapp.data.local.datasource
-import com.yapp.data.local.AlarmEntity
+import com.yapp.database.AlarmEntity
import com.yapp.domain.model.Alarm
import kotlinx.coroutines.flow.Flow
interface AlarmLocalDataSource {
fun getAllAlarms(): Flow>
- fun getPagedAlarms(limit: Int, offset: Int): Flow>
- fun getAlarmsByTime(hour: Int, minute: Int, isAm: Boolean): Flow>
- fun getAlarmCount(): Flow
+ fun getAlarmsByTime(hour: Int, minute: Int): Flow>
suspend fun insertAlarm(alarm: AlarmEntity): Long
suspend fun updateAlarm(alarm: AlarmEntity): Int
suspend fun updateAlarmActive(id: Long, active: Boolean): Int
diff --git a/data/src/main/java/com/yapp/data/local/datasource/AlarmLocalDataSourceImpl.kt b/data/src/main/java/com/yapp/data/local/datasource/AlarmLocalDataSourceImpl.kt
index d84acfa8..03fecd55 100644
--- a/data/src/main/java/com/yapp/data/local/datasource/AlarmLocalDataSourceImpl.kt
+++ b/data/src/main/java/com/yapp/data/local/datasource/AlarmLocalDataSourceImpl.kt
@@ -1,8 +1,8 @@
package com.yapp.data.local.datasource
-import com.yapp.data.local.AlarmDao
-import com.yapp.data.local.AlarmEntity
-import com.yapp.data.local.toDomain
+import com.yapp.database.AlarmDao
+import com.yapp.database.AlarmEntity
+import com.yapp.database.toDomain
import com.yapp.domain.model.Alarm
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
@@ -16,24 +16,12 @@ class AlarmLocalDataSourceImpl @Inject constructor(
.map { alarmEntities -> alarmEntities.map { it.toDomain() } }
}
- override fun getPagedAlarms(
- limit: Int,
- offset: Int,
- ): Flow> {
- return alarmDao.getPagedAlarms(limit, offset)
- .map { alarmEntities -> alarmEntities.map { it.toDomain() } }
- }
-
- override fun getAlarmsByTime(hour: Int, minute: Int, isAm: Boolean): Flow> {
- return alarmDao.getAlarmsByTime(hour, minute, isAm).map { alarmEntities ->
+ override fun getAlarmsByTime(hour: Int, minute: Int): Flow> {
+ return alarmDao.getAlarmsByTime(hour, minute).map { alarmEntities ->
alarmEntities.map { it.toDomain() }
}
}
- override fun getAlarmCount(): Flow {
- return alarmDao.getAlarmCount()
- }
-
override suspend fun insertAlarm(alarm: AlarmEntity): Long {
return alarmDao.insertAlarm(alarm)
}
diff --git a/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSource.kt b/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSource.kt
new file mode 100644
index 00000000..4e519ddb
--- /dev/null
+++ b/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSource.kt
@@ -0,0 +1,27 @@
+package com.yapp.data.local.datasource
+
+import com.yapp.domain.model.FortuneCreateStatus
+import kotlinx.coroutines.flow.Flow
+
+interface FortuneLocalDataSource {
+ val fortuneIdFlow: Flow
+ val fortuneDateEpochFlow: Flow
+ val fortuneImageIdFlow: Flow
+ val fortuneScoreFlow: Flow
+ val hasUnseenFortuneFlow: Flow
+ val shouldShowFortuneToolTipFlow: Flow
+ val isFirstAlarmDismissedTodayFlow: Flow
+
+ val fortuneCreateStatusFlow: Flow
+
+ suspend fun markFortuneCreating()
+ suspend fun markFortuneCreated(fortuneId: Long)
+ suspend fun markFortuneFailed()
+ suspend fun markFortuneSeen()
+ suspend fun markFortuneTooltipShown()
+ suspend fun saveFortuneImageId(imageResId: Int)
+ suspend fun saveFortuneScore(score: Int)
+ suspend fun markFirstAlarmDismissedToday()
+
+ suspend fun clearFortuneData()
+}
diff --git a/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSourceImpl.kt b/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSourceImpl.kt
new file mode 100644
index 00000000..b8ab799f
--- /dev/null
+++ b/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSourceImpl.kt
@@ -0,0 +1,73 @@
+package com.yapp.data.local.datasource
+
+import com.yapp.datastore.UserPreferences
+import com.yapp.domain.model.FortuneCreateStatus
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import java.time.LocalDate
+import javax.inject.Inject
+
+class FortuneLocalDataSourceImpl @Inject constructor(
+ private val userPreferences: UserPreferences,
+) : FortuneLocalDataSource {
+
+ override val fortuneIdFlow = userPreferences.fortuneIdFlow
+ override val fortuneDateEpochFlow = userPreferences.fortuneDateEpochFlow
+ override val fortuneImageIdFlow = userPreferences.fortuneImageIdFlow
+ override val fortuneScoreFlow = userPreferences.fortuneScoreFlow
+ override val hasUnseenFortuneFlow = userPreferences.hasUnseenFortuneFlow
+ override val shouldShowFortuneToolTipFlow = userPreferences.shouldShowFortuneToolTipFlow
+ override val isFirstAlarmDismissedTodayFlow = userPreferences.isFirstAlarmDismissedTodayFlow
+
+ override val fortuneCreateStatusFlow = combine(
+ userPreferences.fortuneIdFlow,
+ userPreferences.fortuneDateEpochFlow,
+ userPreferences.isFortuneCreatingFlow,
+ userPreferences.isFortuneFailedFlow,
+ ) { fortuneId, fortuneDate, isCreating, isFailed ->
+ when {
+ isFailed -> FortuneCreateStatus.Failure
+ isCreating -> FortuneCreateStatus.Creating
+ fortuneId != null && fortuneDate == todayEpoch() -> FortuneCreateStatus.Success(fortuneId)
+ else -> FortuneCreateStatus.Idle
+ }
+ }.distinctUntilChanged()
+
+ private fun todayEpoch(): Long = LocalDate.now().toEpochDay()
+
+ override suspend fun markFortuneCreating() {
+ userPreferences.markFortuneCreating()
+ }
+
+ override suspend fun markFortuneCreated(fortuneId: Long) {
+ userPreferences.markFortuneCreated(fortuneId)
+ }
+
+ override suspend fun markFortuneFailed() {
+ userPreferences.markFortuneFailed()
+ }
+
+ override suspend fun markFortuneSeen() {
+ userPreferences.markFortuneSeen()
+ }
+
+ override suspend fun markFortuneTooltipShown() {
+ userPreferences.markFortuneTooltipShown()
+ }
+
+ override suspend fun saveFortuneImageId(imageResId: Int) {
+ userPreferences.saveFortuneImageId(imageResId)
+ }
+
+ override suspend fun saveFortuneScore(score: Int) {
+ userPreferences.saveFortuneScore(score)
+ }
+
+ override suspend fun markFirstAlarmDismissedToday() {
+ userPreferences.markFirstAlarmDismissedToday()
+ }
+
+ override suspend fun clearFortuneData() {
+ userPreferences.clearFortuneData()
+ }
+}
diff --git a/data/src/main/java/com/yapp/data/local/datasource/ImageLocalDataSource.kt b/data/src/main/java/com/yapp/data/local/datasource/ImageLocalDataSource.kt
deleted file mode 100644
index 607ee87a..00000000
--- a/data/src/main/java/com/yapp/data/local/datasource/ImageLocalDataSource.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package com.yapp.data.local.datasource
-
-interface ImageLocalDataSource {
- suspend fun saveImage(byteArray: ByteArray, fileName: String = "fortune_${System.currentTimeMillis()}.png"): Boolean
-}
diff --git a/data/src/main/java/com/yapp/data/local/datasource/UserLocalDataSource.kt b/data/src/main/java/com/yapp/data/local/datasource/UserLocalDataSource.kt
new file mode 100644
index 00000000..3ad851df
--- /dev/null
+++ b/data/src/main/java/com/yapp/data/local/datasource/UserLocalDataSource.kt
@@ -0,0 +1,18 @@
+package com.yapp.data.local.datasource
+
+import kotlinx.coroutines.flow.Flow
+
+interface UserLocalDataSource {
+ val userIdFlow: Flow
+ val userNameFlow: Flow
+ val onboardingCompletedFlow: Flow
+ val updateNoticeDontShowVersionFlow: Flow
+ val updateNoticeLastShownDateEpochFlow: Flow
+
+ suspend fun saveUserId(userId: Long)
+ suspend fun saveUserName(userName: String)
+ suspend fun setOnboardingCompleted()
+ suspend fun markUpdateNoticeDontShow(version: String)
+ suspend fun markUpdateNoticeShownToday()
+ suspend fun clearUserData()
+}
diff --git a/data/src/main/java/com/yapp/data/local/datasource/UserLocalDataSourceImpl.kt b/data/src/main/java/com/yapp/data/local/datasource/UserLocalDataSourceImpl.kt
new file mode 100644
index 00000000..187a7a59
--- /dev/null
+++ b/data/src/main/java/com/yapp/data/local/datasource/UserLocalDataSourceImpl.kt
@@ -0,0 +1,40 @@
+package com.yapp.data.local.datasource
+
+import com.yapp.datastore.UserPreferences
+import kotlinx.coroutines.flow.Flow
+import javax.inject.Inject
+
+class UserLocalDataSourceImpl @Inject constructor(
+ private val userPreferences: UserPreferences,
+) : UserLocalDataSource {
+
+ override val userIdFlow: Flow = userPreferences.userIdFlow
+ override val userNameFlow: Flow = userPreferences.userNameFlow
+ override val onboardingCompletedFlow: Flow = userPreferences.onboardingCompletedFlow
+ override val updateNoticeDontShowVersionFlow: Flow = userPreferences.updateNoticeDontShowVersionFlow
+ override val updateNoticeLastShownDateEpochFlow: Flow = userPreferences.updateNoticeLastShownDateEpochFlow
+
+ override suspend fun saveUserId(userId: Long) {
+ userPreferences.saveUserId(userId)
+ }
+
+ override suspend fun saveUserName(userName: String) {
+ userPreferences.saveUserName(userName)
+ }
+
+ override suspend fun setOnboardingCompleted() {
+ userPreferences.setOnboardingCompleted()
+ }
+
+ override suspend fun markUpdateNoticeDontShow(version: String) {
+ userPreferences.markUpdateNoticeDontShow(version)
+ }
+
+ override suspend fun markUpdateNoticeShownToday() {
+ userPreferences.markUpdateNoticeShownToday()
+ }
+
+ override suspend fun clearUserData() {
+ userPreferences.clearUserData()
+ }
+}
diff --git a/data/src/main/java/com/yapp/data/local/di/DataSourceModule.kt b/data/src/main/java/com/yapp/data/local/di/DataSourceModule.kt
index eb567b3e..4a1ad9b8 100644
--- a/data/src/main/java/com/yapp/data/local/di/DataSourceModule.kt
+++ b/data/src/main/java/com/yapp/data/local/di/DataSourceModule.kt
@@ -2,6 +2,10 @@ package com.yapp.data.local.di
import com.yapp.data.local.datasource.AlarmLocalDataSource
import com.yapp.data.local.datasource.AlarmLocalDataSourceImpl
+import com.yapp.data.local.datasource.FortuneLocalDataSource
+import com.yapp.data.local.datasource.FortuneLocalDataSourceImpl
+import com.yapp.data.local.datasource.UserLocalDataSource
+import com.yapp.data.local.datasource.UserLocalDataSourceImpl
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
@@ -16,4 +20,16 @@ abstract class DataSourceModule {
abstract fun bindsAlarmDataSource(
alarmLocalDataSource: AlarmLocalDataSourceImpl,
): AlarmLocalDataSource
+
+ @Binds
+ @Singleton
+ abstract fun bindsFortuneDataSource(
+ fortuneLocalDataSource: FortuneLocalDataSourceImpl,
+ ): FortuneLocalDataSource
+
+ @Binds
+ @Singleton
+ abstract fun bindsUserDataSource(
+ userLocalDataSource: UserLocalDataSourceImpl,
+ ): UserLocalDataSource
}
diff --git a/data/src/main/java/com/yapp/data/local/di/RepositoryModule.kt b/data/src/main/java/com/yapp/data/local/di/RepositoryModule.kt
deleted file mode 100644
index 3d7235c6..00000000
--- a/data/src/main/java/com/yapp/data/local/di/RepositoryModule.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.yapp.data.local.di
-
-import com.yapp.data.local.repositoryimpl.AlarmRepositoryImpl
-import com.yapp.data.local.repositoryimpl.ImageRepositoryImpl
-import com.yapp.domain.repository.AlarmRepository
-import com.yapp.domain.repository.ImageRepository
-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 bindsAlarmRepository(
- alarmRepository: AlarmRepositoryImpl,
- ): AlarmRepository
-
- @Binds
- @Singleton
- abstract fun bindsImageRepository(
- imageRepository: ImageRepositoryImpl,
- ): ImageRepository
-}
diff --git a/data/src/main/java/com/yapp/data/local/repositoryimpl/ImageRepositoryImpl.kt b/data/src/main/java/com/yapp/data/local/repositoryimpl/ImageRepositoryImpl.kt
deleted file mode 100644
index 86cba2cc..00000000
--- a/data/src/main/java/com/yapp/data/local/repositoryimpl/ImageRepositoryImpl.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.yapp.data.local.repositoryimpl
-
-import com.yapp.data.local.datasource.ImageLocalDataSource
-import com.yapp.domain.repository.ImageRepository
-import javax.inject.Inject
-
-class ImageRepositoryImpl @Inject constructor(
- private val imageLocalDataSource: ImageLocalDataSource,
-) : ImageRepository {
-
- override suspend fun saveImage(byteArray: ByteArray): Boolean {
- return imageLocalDataSource.saveImage(byteArray)
- }
-}
diff --git a/data/src/main/java/com/yapp/data/remote/datasource/DummyDataSource.kt b/data/src/main/java/com/yapp/data/remote/datasource/DummyDataSource.kt
deleted file mode 100644
index 2d12449b..00000000
--- a/data/src/main/java/com/yapp/data/remote/datasource/DummyDataSource.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.yapp.data.remote.datasource
-
-import com.yapp.data.remote.dto.request.RequestDummyDto
-import com.yapp.data.remote.dto.response.ResponseDummyDto
-import com.yapp.network.model.BaseResponse
-
-interface DummyDataSource {
- suspend fun fetchDummy(): BaseResponse
- suspend fun saveDummy(requestDummyDto: RequestDummyDto): BaseResponse
-}
diff --git a/data/src/main/java/com/yapp/data/remote/datasource/DummyDataSourceImpl.kt b/data/src/main/java/com/yapp/data/remote/datasource/DummyDataSourceImpl.kt
deleted file mode 100644
index 102bbdeb..00000000
--- a/data/src/main/java/com/yapp/data/remote/datasource/DummyDataSourceImpl.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.yapp.data.remote.datasource
-
-import com.yapp.data.remote.dto.request.RequestDummyDto
-import com.yapp.data.remote.dto.response.ResponseDummyDto
-import com.yapp.data.remote.service.DummyService
-import com.yapp.network.model.BaseResponse
-import javax.inject.Inject
-
-class DummyDataSourceImpl @Inject constructor(
- private val dummyService: DummyService,
-) : DummyDataSource {
- override suspend fun fetchDummy(): BaseResponse = dummyService.fetchDummy()
- override suspend fun saveDummy(requestDummyDto: RequestDummyDto): BaseResponse = dummyService.saveDummy(requestDummyDto)
-}
diff --git a/data/src/main/java/com/yapp/data/remote/datasource/FortuneDataSourceImpl.kt b/data/src/main/java/com/yapp/data/remote/datasource/FortuneDataSourceImpl.kt
index f549500d..d8dc2dd7 100644
--- a/data/src/main/java/com/yapp/data/remote/datasource/FortuneDataSourceImpl.kt
+++ b/data/src/main/java/com/yapp/data/remote/datasource/FortuneDataSourceImpl.kt
@@ -2,7 +2,7 @@ package com.yapp.data.remote.datasource
import com.yapp.data.remote.dto.response.FortuneResponse
import com.yapp.data.remote.service.ApiService
-import com.yapp.data.remote.utils.safeApiCall
+import com.yapp.network.utils.safeApiCall
import javax.inject.Inject
class FortuneDataSourceImpl @Inject constructor(
diff --git a/data/src/main/java/com/yapp/data/remote/datasource/SignUpDataSourceImpl.kt b/data/src/main/java/com/yapp/data/remote/datasource/SignUpDataSourceImpl.kt
index 2acfa4ff..d6023c3d 100644
--- a/data/src/main/java/com/yapp/data/remote/datasource/SignUpDataSourceImpl.kt
+++ b/data/src/main/java/com/yapp/data/remote/datasource/SignUpDataSourceImpl.kt
@@ -3,8 +3,8 @@ package com.yapp.data.remote.datasource
import android.util.Log
import com.yapp.data.remote.dto.request.SignUpRequest
import com.yapp.data.remote.service.ApiService
-import com.yapp.data.remote.utils.ApiError
-import com.yapp.data.remote.utils.safeApiCall
+import com.yapp.network.model.ApiError
+import com.yapp.network.utils.safeApiCall
import javax.inject.Inject
class SignUpDataSourceImpl @Inject constructor(
diff --git a/data/src/main/java/com/yapp/data/remote/datasource/UserInfoDataSourceImpl.kt b/data/src/main/java/com/yapp/data/remote/datasource/UserInfoDataSourceImpl.kt
index 3c6cb580..d81e9189 100644
--- a/data/src/main/java/com/yapp/data/remote/datasource/UserInfoDataSourceImpl.kt
+++ b/data/src/main/java/com/yapp/data/remote/datasource/UserInfoDataSourceImpl.kt
@@ -3,7 +3,7 @@ package com.yapp.data.remote.datasource
import com.yapp.data.remote.dto.request.UpdateUserInfoRequest
import com.yapp.data.remote.dto.response.UserResponse
import com.yapp.data.remote.service.ApiService
-import com.yapp.data.remote.utils.safeApiCall
+import com.yapp.network.utils.safeApiCall
import javax.inject.Inject
class UserInfoDataSourceImpl @Inject constructor(
diff --git a/data/src/main/java/com/yapp/data/remote/di/DataSourceModule.kt b/data/src/main/java/com/yapp/data/remote/di/DataSourceModule.kt
index e7f06d23..f30ecb5d 100644
--- a/data/src/main/java/com/yapp/data/remote/di/DataSourceModule.kt
+++ b/data/src/main/java/com/yapp/data/remote/di/DataSourceModule.kt
@@ -1,7 +1,5 @@
package com.yapp.data.remote.di
-import com.yapp.data.remote.datasource.DummyDataSource
-import com.yapp.data.remote.datasource.DummyDataSourceImpl
import com.yapp.data.remote.datasource.FortuneDataSource
import com.yapp.data.remote.datasource.FortuneDataSourceImpl
import com.yapp.data.remote.datasource.SignUpDataSource
@@ -17,11 +15,6 @@ import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
abstract class DataSourceModule {
- @Binds
- @Singleton
- abstract fun bindsDummyDataSource(
- dummyDataSource: DummyDataSourceImpl,
- ): DummyDataSource
@Binds
@Singleton
diff --git a/data/src/main/java/com/yapp/data/remote/di/ServiceModule.kt b/data/src/main/java/com/yapp/data/remote/di/ServiceModule.kt
index 8e5f451a..be0e97f5 100644
--- a/data/src/main/java/com/yapp/data/remote/di/ServiceModule.kt
+++ b/data/src/main/java/com/yapp/data/remote/di/ServiceModule.kt
@@ -1,8 +1,6 @@
package com.yapp.data.remote.di
import com.yapp.data.remote.service.ApiService
-import com.yapp.data.remote.service.DummyService
-import com.yapp.network.di.NoneAuth
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@@ -15,11 +13,6 @@ import javax.inject.Singleton
object ServiceModule {
@Provides
@Singleton
- fun providesDummyService(@NoneAuth retrofit: Retrofit): DummyService =
- retrofit.create(DummyService::class.java)
-
- @Provides
- @Singleton
- fun providesSignUpService(@NoneAuth retrofit: Retrofit): ApiService =
+ fun providesApiService(retrofit: Retrofit): ApiService =
retrofit.create(ApiService::class.java)
}
diff --git a/data/src/main/java/com/yapp/data/remote/dto/request/RequestDummyDto.kt b/data/src/main/java/com/yapp/data/remote/dto/request/RequestDummyDto.kt
deleted file mode 100644
index ff249f68..00000000
--- a/data/src/main/java/com/yapp/data/remote/dto/request/RequestDummyDto.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.yapp.data.remote.dto.request
-
-import com.yapp.domain.model.Dummy
-import kotlinx.serialization.SerialName
-import kotlinx.serialization.Serializable
-
-@Serializable
-data class RequestDummyDto(
- @SerialName("id") val id: Int,
- @SerialName("name") val name: String,
-)
-fun Dummy.toData() = RequestDummyDto(
- id = id,
- name = name,
-)
diff --git a/data/src/main/java/com/yapp/data/remote/dto/response/FortuneResponse.kt b/data/src/main/java/com/yapp/data/remote/dto/response/FortuneResponse.kt
index 925cc1c3..49709723 100644
--- a/data/src/main/java/com/yapp/data/remote/dto/response/FortuneResponse.kt
+++ b/data/src/main/java/com/yapp/data/remote/dto/response/FortuneResponse.kt
@@ -1,7 +1,7 @@
package com.yapp.data.remote.dto.response
-import com.yapp.domain.model.fortune.Fortune
-import com.yapp.domain.model.fortune.FortuneDetailModel
+import com.yapp.domain.model.Fortune
+import com.yapp.domain.model.FortuneDetailModel
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
diff --git a/data/src/main/java/com/yapp/data/remote/dto/response/ResponseDummyDto.kt b/data/src/main/java/com/yapp/data/remote/dto/response/ResponseDummyDto.kt
deleted file mode 100644
index 9fcfc078..00000000
--- a/data/src/main/java/com/yapp/data/remote/dto/response/ResponseDummyDto.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.yapp.data.remote.dto.response
-
-import com.yapp.domain.model.Dummy
-import kotlinx.serialization.SerialName
-import kotlinx.serialization.Serializable
-
-@Serializable
-data class ResponseDummyDto(
- @SerialName("id") val id: Int,
- @SerialName("name") val name: String,
-)
-fun ResponseDummyDto.toDomain() = Dummy(
- id = id,
- name = name,
-)
diff --git a/data/src/main/java/com/yapp/data/remote/repositoryimpl/DummyRepositoryImpl.kt b/data/src/main/java/com/yapp/data/remote/repositoryimpl/DummyRepositoryImpl.kt
deleted file mode 100644
index c581348a..00000000
--- a/data/src/main/java/com/yapp/data/remote/repositoryimpl/DummyRepositoryImpl.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.yapp.data.remote.repositoryimpl
-
-import com.yapp.data.remote.datasource.DummyDataSource
-import com.yapp.data.remote.dto.request.toData
-import com.yapp.data.remote.dto.response.toDomain
-import com.yapp.data.remote.utils.ApiError
-import com.yapp.data.remote.utils.safeApiCall
-import com.yapp.domain.model.Dummy
-import com.yapp.domain.repository.DummyRepository
-import javax.inject.Inject
-
-class DummyRepositoryImpl @Inject constructor(
- private val dummyDataSource: DummyDataSource,
-) : DummyRepository {
-
- override suspend fun fetchDummy(): Result = safeApiCall {
- dummyDataSource.fetchDummy().data?.toDomain()
- ?: return Result.failure(ApiError("No data found"))
- }
-
- override suspend fun saveDummy(dummy: Dummy): Result = safeApiCall {
- dummyDataSource.saveDummy(dummy.toData()).data
- ?: return Result.failure(ApiError("Save operation failed"))
- }
-}
diff --git a/data/src/main/java/com/yapp/data/remote/repositoryimpl/FortuneRepositoryImpl.kt b/data/src/main/java/com/yapp/data/remote/repositoryimpl/FortuneRepositoryImpl.kt
deleted file mode 100644
index b41e225a..00000000
--- a/data/src/main/java/com/yapp/data/remote/repositoryimpl/FortuneRepositoryImpl.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.yapp.data.remote.repositoryimpl
-
-import com.yapp.data.remote.datasource.FortuneDataSource
-import com.yapp.data.remote.dto.response.toDomain
-import com.yapp.domain.model.fortune.Fortune
-import com.yapp.domain.repository.FortuneRepository
-import javax.inject.Inject
-
-class FortuneRepositoryImpl @Inject constructor(
- private val fortuneDataSource: FortuneDataSource,
-) : FortuneRepository {
- override suspend fun postFortune(userId: Long): Result {
- return fortuneDataSource.postFortune(userId)
- .mapCatching { fortuneResponse ->
- fortuneResponse.toDomain()
- }
- }
- override suspend fun getFortune(fortuneId: Long): Result {
- return fortuneDataSource.getFortune(fortuneId)
- .mapCatching { fortuneResponse ->
- fortuneResponse.toDomain()
- }
- }
-}
diff --git a/data/src/main/java/com/yapp/data/remote/repositoryimpl/UserInfoRepositoryImpl.kt b/data/src/main/java/com/yapp/data/remote/repositoryimpl/UserInfoRepositoryImpl.kt
deleted file mode 100644
index c4720bb6..00000000
--- a/data/src/main/java/com/yapp/data/remote/repositoryimpl/UserInfoRepositoryImpl.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.yapp.data.remote.repositoryimpl
-
-import com.yapp.data.remote.datasource.UserInfoDataSource
-import com.yapp.data.remote.dto.request.UpdateUserInfoRequest.Companion.toUpdateRequest
-import com.yapp.data.remote.dto.response.toDomain
-import com.yapp.domain.model.EditUser
-import com.yapp.domain.model.User
-import com.yapp.domain.repository.UserInfoRepository
-import javax.inject.Inject
-
-class UserInfoRepositoryImpl @Inject constructor(
- private val userInfoDataSource: UserInfoDataSource,
-) : UserInfoRepository {
- override suspend fun getUserInfo(userId: Long): Result {
- return userInfoDataSource.getUserInfo(userId)
- .mapCatching { userResponse ->
- userResponse.toDomain()
- }
- }
-
- override suspend fun updateUserInfo(userId: Long, editUser: EditUser): Result {
- val request = editUser.toUpdateRequest()
- return userInfoDataSource.updateUserInfo(userId, request)
- .mapCatching {
- if (it) {
- Unit
- } else {
- throw Exception("Failed to update user info")
- }
- }
- }
-}
diff --git a/data/src/main/java/com/yapp/data/remote/service/DummyService.kt b/data/src/main/java/com/yapp/data/remote/service/DummyService.kt
deleted file mode 100644
index 066c49f3..00000000
--- a/data/src/main/java/com/yapp/data/remote/service/DummyService.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.yapp.data.remote.service
-
-import com.yapp.data.remote.dto.request.RequestDummyDto
-import com.yapp.data.remote.dto.response.ResponseDummyDto
-import com.yapp.network.model.BaseResponse
-
-interface DummyService {
- suspend fun fetchDummy(): BaseResponse
- suspend fun saveDummy(requestDummyDto: RequestDummyDto): BaseResponse
-}
diff --git a/data/src/main/java/com/yapp/data/remote/utils/ApiCallUtils.kt b/data/src/main/java/com/yapp/data/remote/utils/ApiCallUtils.kt
deleted file mode 100644
index b1efd325..00000000
--- a/data/src/main/java/com/yapp/data/remote/utils/ApiCallUtils.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.yapp.data.remote.utils
-
-import retrofit2.HttpException
-import java.io.IOException
-
-internal inline fun safeApiCall(action: () -> T): Result =
- runCatching(action).recoverCatching { exception ->
- when (exception) {
- is HttpException -> throw mapHttpException(exception)
- is IOException -> throw ApiError("๋คํธ์ํฌ ์ค๋ฅ ๋ฐ์")
- else -> throw exception
- }
- }
-
-private fun mapHttpException(exception: HttpException): ApiError {
- return when (exception.code()) {
- 400 -> ApiError("์๋ชป๋ ์์ฒญ")
- 401 -> ApiError("์ธ์ฆ์ด ํ์ํฉ๋๋ค")
- 403 -> ApiError("๊ถํ์ด ์์ต๋๋ค")
- 404 -> ApiError("์์ฒญํ ๋ฆฌ์์ค๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค")
- in 500..599 -> ApiError("์๋ฒ ์ค๋ฅ")
- else -> ApiError("์ ์ ์๋ ์๋ฒ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.")
- }
-}
diff --git a/data/src/main/java/com/yapp/data/local/repositoryimpl/AlarmRepositoryImpl.kt b/data/src/main/java/com/yapp/data/repositoryimpl/AlarmRepositoryImpl.kt
similarity index 86%
rename from data/src/main/java/com/yapp/data/local/repositoryimpl/AlarmRepositoryImpl.kt
rename to data/src/main/java/com/yapp/data/repositoryimpl/AlarmRepositoryImpl.kt
index 6f2a3109..50385b4f 100644
--- a/data/src/main/java/com/yapp/data/local/repositoryimpl/AlarmRepositoryImpl.kt
+++ b/data/src/main/java/com/yapp/data/repositoryimpl/AlarmRepositoryImpl.kt
@@ -1,8 +1,8 @@
-package com.yapp.data.local.repositoryimpl
+package com.yapp.data.repositoryimpl
import android.net.Uri
import com.yapp.data.local.datasource.AlarmLocalDataSource
-import com.yapp.data.local.toEntity
+import com.yapp.database.toEntity
import com.yapp.domain.model.Alarm
import com.yapp.domain.model.AlarmSound
import com.yapp.domain.repository.AlarmRepository
@@ -45,14 +45,8 @@ class AlarmRepositoryImpl @Inject constructor(
override fun getAllAlarms(): Flow> =
alarmLocalDataSource.getAllAlarms()
- override fun getPagedAlarms(limit: Int, offset: Int): Flow> =
- alarmLocalDataSource.getPagedAlarms(limit, offset)
-
- override fun getAlarmsByTime(hour: Int, minute: Int, isAm: Boolean): Flow> =
- alarmLocalDataSource.getAlarmsByTime(hour, minute, isAm)
-
- override fun getAlarmCount(): Flow =
- alarmLocalDataSource.getAlarmCount()
+ override fun getAlarmsByTime(hour: Int, minute: Int): Flow> =
+ alarmLocalDataSource.getAlarmsByTime(hour, minute)
override suspend fun insertAlarm(alarm: Alarm): Result = runCatching {
val alarmId = alarmLocalDataSource.insertAlarm(alarm.toEntity())
diff --git a/data/src/main/java/com/yapp/data/repositoryimpl/FortuneRepositoryImpl.kt b/data/src/main/java/com/yapp/data/repositoryimpl/FortuneRepositoryImpl.kt
new file mode 100644
index 00000000..1c761ba6
--- /dev/null
+++ b/data/src/main/java/com/yapp/data/repositoryimpl/FortuneRepositoryImpl.kt
@@ -0,0 +1,47 @@
+package com.yapp.data.repositoryimpl
+
+import com.yapp.data.local.datasource.FortuneLocalDataSource
+import com.yapp.data.remote.datasource.FortuneDataSource
+import com.yapp.data.remote.dto.response.toDomain
+import com.yapp.domain.model.Fortune
+import com.yapp.domain.model.FortuneCreateStatus
+import com.yapp.domain.repository.FortuneRepository
+import kotlinx.coroutines.flow.Flow
+import javax.inject.Inject
+
+class FortuneRepositoryImpl @Inject constructor(
+ private val fortuneLocalDataSource: FortuneLocalDataSource,
+ private val fortuneRemoteDataSource: FortuneDataSource,
+) : FortuneRepository {
+
+ override val fortuneIdFlow: Flow = fortuneLocalDataSource.fortuneIdFlow
+ override val fortuneDateEpochFlow: Flow = fortuneLocalDataSource.fortuneDateEpochFlow
+ override val fortuneImageIdFlow: Flow = fortuneLocalDataSource.fortuneImageIdFlow
+ override val fortuneScoreFlow: Flow = fortuneLocalDataSource.fortuneScoreFlow
+ override val hasUnseenFortuneFlow: Flow = fortuneLocalDataSource.hasUnseenFortuneFlow
+ override val shouldShowFortuneToolTipFlow: Flow = fortuneLocalDataSource.shouldShowFortuneToolTipFlow
+ override val isFirstAlarmDismissedTodayFlow: Flow = fortuneLocalDataSource.isFirstAlarmDismissedTodayFlow
+
+ override val fortuneCreateStatusFlow: Flow = fortuneLocalDataSource.fortuneCreateStatusFlow
+
+ override suspend fun markFortuneAsCreating() = fortuneLocalDataSource.markFortuneCreating()
+ override suspend fun markFortuneAsCreated(fortuneId: Long) = fortuneLocalDataSource.markFortuneCreated(fortuneId)
+ override suspend fun markFortuneAsFailed() = fortuneLocalDataSource.markFortuneFailed()
+ override suspend fun markFortuneSeen() = fortuneLocalDataSource.markFortuneSeen()
+ override suspend fun markFortuneTooltipShown() = fortuneLocalDataSource.markFortuneTooltipShown()
+ override suspend fun saveFortuneImageId(imageResId: Int) = fortuneLocalDataSource.saveFortuneImageId(imageResId)
+ override suspend fun saveFortuneScore(score: Int) = fortuneLocalDataSource.saveFortuneScore(score)
+ override suspend fun markFirstAlarmDismissedToday() = fortuneLocalDataSource.markFirstAlarmDismissedToday()
+
+ override suspend fun clearFortuneData() = fortuneLocalDataSource.clearFortuneData()
+
+ override suspend fun postFortune(userId: Long): Result {
+ return fortuneRemoteDataSource.postFortune(userId)
+ .mapCatching { it.toDomain() }
+ }
+
+ override suspend fun getFortune(fortuneId: Long): Result {
+ return fortuneRemoteDataSource.getFortune(fortuneId)
+ .mapCatching { it.toDomain() }
+ }
+}
diff --git a/data/src/main/java/com/yapp/data/remote/repositoryimpl/RemoteConfigRepositoryImpl.kt b/data/src/main/java/com/yapp/data/repositoryimpl/RemoteConfigRepositoryImpl.kt
similarity index 92%
rename from data/src/main/java/com/yapp/data/remote/repositoryimpl/RemoteConfigRepositoryImpl.kt
rename to data/src/main/java/com/yapp/data/repositoryimpl/RemoteConfigRepositoryImpl.kt
index e45ae5a1..46a14431 100644
--- a/data/src/main/java/com/yapp/data/remote/repositoryimpl/RemoteConfigRepositoryImpl.kt
+++ b/data/src/main/java/com/yapp/data/repositoryimpl/RemoteConfigRepositoryImpl.kt
@@ -1,4 +1,4 @@
-package com.yapp.data.remote.repositoryimpl
+package com.yapp.data.repositoryimpl
import com.yapp.domain.model.MissionType
import com.yapp.domain.repository.RemoteConfigRepository
diff --git a/data/src/main/java/com/yapp/data/remote/repositoryimpl/SignUpRepositoryImpl.kt b/data/src/main/java/com/yapp/data/repositoryimpl/SignUpRepositoryImpl.kt
similarity index 94%
rename from data/src/main/java/com/yapp/data/remote/repositoryimpl/SignUpRepositoryImpl.kt
rename to data/src/main/java/com/yapp/data/repositoryimpl/SignUpRepositoryImpl.kt
index 5977c5ae..2c593a9c 100644
--- a/data/src/main/java/com/yapp/data/remote/repositoryimpl/SignUpRepositoryImpl.kt
+++ b/data/src/main/java/com/yapp/data/repositoryimpl/SignUpRepositoryImpl.kt
@@ -1,4 +1,4 @@
-package com.yapp.data.remote.repositoryimpl
+package com.yapp.data.repositoryimpl
import android.util.Log
import com.yapp.data.remote.datasource.SignUpDataSource
diff --git a/data/src/main/java/com/yapp/data/repositoryimpl/UserInfoRepositoryImpl.kt b/data/src/main/java/com/yapp/data/repositoryimpl/UserInfoRepositoryImpl.kt
new file mode 100644
index 00000000..818e232d
--- /dev/null
+++ b/data/src/main/java/com/yapp/data/repositoryimpl/UserInfoRepositoryImpl.kt
@@ -0,0 +1,48 @@
+package com.yapp.data.repositoryimpl
+
+import com.yapp.data.local.datasource.UserLocalDataSource
+import com.yapp.data.remote.datasource.UserInfoDataSource
+import com.yapp.data.remote.dto.request.UpdateUserInfoRequest.Companion.toUpdateRequest
+import com.yapp.data.remote.dto.response.toDomain
+import com.yapp.domain.model.EditUser
+import com.yapp.domain.model.User
+import com.yapp.domain.repository.UserInfoRepository
+import kotlinx.coroutines.flow.Flow
+import javax.inject.Inject
+
+class UserInfoRepositoryImpl @Inject constructor(
+ private val userLocalDataSource: UserLocalDataSource,
+ private val userInfoDataSource: UserInfoDataSource,
+) : UserInfoRepository {
+ override val userIdFlow: Flow = userLocalDataSource.userIdFlow
+ override val userNameFlow: Flow = userLocalDataSource.userNameFlow
+ override val onboardingCompletedFlow: Flow = userLocalDataSource.onboardingCompletedFlow
+ override val updateNoticeDontShowVersionFlow: Flow = userLocalDataSource.updateNoticeDontShowVersionFlow
+ override val updateNoticeLastShownDateEpochFlow: Flow = userLocalDataSource.updateNoticeLastShownDateEpochFlow
+
+ override suspend fun saveUserId(userId: Long) = userLocalDataSource.saveUserId(userId)
+ override suspend fun saveUserName(userName: String) = userLocalDataSource.saveUserName(userName)
+ override suspend fun setOnboardingCompleted() = userLocalDataSource.setOnboardingCompleted()
+ override suspend fun markUpdateNoticeDontShow(version: String) = userLocalDataSource.markUpdateNoticeDontShow(version)
+ override suspend fun markUpdateNoticeShownToday() = userLocalDataSource.markUpdateNoticeShownToday()
+ override suspend fun clearUserData() = userLocalDataSource.clearUserData()
+
+ override suspend fun getUserInfo(userId: Long): Result {
+ return userInfoDataSource.getUserInfo(userId)
+ .mapCatching { userResponse ->
+ userResponse.toDomain()
+ }
+ }
+
+ override suspend fun updateUserInfo(userId: Long, editUser: EditUser): Result {
+ val request = editUser.toUpdateRequest()
+ return userInfoDataSource.updateUserInfo(userId, request)
+ .mapCatching {
+ if (it) {
+ Unit
+ } else {
+ throw Exception("Failed to update user info")
+ }
+ }
+ }
+}
diff --git a/data/src/test/kotlin/com/yapp/data/FortuneDataSourceImplTest.kt b/data/src/test/kotlin/com/yapp/data/FortuneDataSourceImplTest.kt
new file mode 100644
index 00000000..b7d9d0f4
--- /dev/null
+++ b/data/src/test/kotlin/com/yapp/data/FortuneDataSourceImplTest.kt
@@ -0,0 +1,83 @@
+package com.yapp.data
+
+import com.yapp.data.remote.datasource.FortuneDataSourceImpl
+import com.yapp.data.remote.dto.response.FortuneResponse
+import com.yapp.data.remote.service.ApiService
+import io.mockk.coEvery
+import io.mockk.coVerify
+import io.mockk.mockk
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.*
+import org.junit.Before
+import org.junit.Test
+
+class FortuneDataSourceImplTest {
+
+ private lateinit var dataSource: FortuneDataSourceImpl
+ private val apiService: ApiService = mockk()
+
+ @Before
+ fun setup() {
+ dataSource = FortuneDataSourceImpl(apiService)
+ }
+
+ @Test
+ fun `์ด์ธ ๋ฑ๋ก์ ์ฑ๊ณตํ๋ฉด ์ฑ๊ณต Result๋ฅผ ๋ฐํํ๋ค`() = runTest {
+ // Given
+ val userId = 1L
+ val mockResponse = mockk()
+ coEvery { apiService.postFortune(userId) } returns mockResponse
+
+ // When
+ val result = dataSource.postFortune(userId)
+
+ // Then
+ assertTrue(result.isSuccess)
+ assertEquals(mockResponse, result.getOrNull())
+ coVerify { apiService.postFortune(userId) }
+ }
+
+ @Test
+ fun `์ด์ธ ๋ฑ๋ก ์ค ์์ธ๊ฐ ๋ฐ์ํ๋ฉด ์คํจ Result๋ฅผ ๋ฐํํ๋ค`() = runTest {
+ // Given
+ val userId = 1L
+ coEvery { apiService.postFortune(userId) } throws RuntimeException("Network Error")
+
+ // When
+ val result = dataSource.postFortune(userId)
+
+ // Then
+ assertTrue(result.isFailure)
+ coVerify { apiService.postFortune(userId) }
+ }
+
+ @Test
+ fun `์ด์ธ ์กฐํ์ ์ฑ๊ณตํ๋ฉด ์ฑ๊ณต Result๋ฅผ ๋ฐํํ๋ค`() = runTest {
+ // Given
+ val fortuneId = 10L
+ val mockResponse = mockk()
+ coEvery { apiService.getFortune(fortuneId) } returns mockResponse
+
+ // When
+ val result = dataSource.getFortune(fortuneId)
+
+ // Then
+ assertTrue(result.isSuccess)
+ assertEquals(mockResponse, result.getOrNull())
+ coVerify { apiService.getFortune(fortuneId) }
+ }
+
+ @Test
+ fun `์ด์ธ ์กฐํ ์ค ์์ธ๊ฐ ๋ฐ์ํ๋ฉด ์คํจ Result๋ฅผ ๋ฐํํ๋ค`() = runTest {
+ // Given
+ val fortuneId = 10L
+ coEvery { apiService.getFortune(fortuneId) } throws RuntimeException("Network Error")
+
+ // When
+ val result = dataSource.getFortune(fortuneId)
+
+ // Then
+ assertTrue(result.isFailure)
+ coVerify { apiService.getFortune(fortuneId) }
+ }
+}
diff --git a/data/src/test/kotlin/com/yapp/data/FortuneMapperTest.kt b/data/src/test/kotlin/com/yapp/data/FortuneMapperTest.kt
new file mode 100644
index 00000000..6c3313b7
--- /dev/null
+++ b/data/src/test/kotlin/com/yapp/data/FortuneMapperTest.kt
@@ -0,0 +1,57 @@
+package com.yapp.data
+
+import com.yapp.data.remote.dto.response.FortuneDetail
+import com.yapp.data.remote.dto.response.FortuneResponse
+import com.yapp.data.remote.dto.response.toDomain
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class FortuneMapperTest {
+
+ @Test
+ fun `FortuneResponse๋ฅผ ๋๋ฉ์ธ ๋ชจ๋ธ๋ก ๋งคํํ๋ฉด ์ฌ๋ฐ๋ฅด๊ฒ ๋ณํ๋๋ค`() {
+ val response = dummyFortuneResponse()
+ val domain = response.toDomain()
+
+ assertEquals(response.id, domain.id)
+ assertEquals(response.dailyFortune, domain.dailyFortuneTitle)
+ assertEquals(response.dailyFortuneDescription, domain.dailyFortuneDescription)
+ assertEquals(response.avgFortuneScore, domain.avgFortuneScore)
+ assertEquals(response.studyCareerFortune.toDomain(), domain.studyCareerFortune)
+ assertEquals(response.luckyFood, domain.luckyFood)
+ }
+
+ @Test
+ fun `FortuneDetail์ ๋๋ฉ์ธ ๋ชจ๋ธ๋ก ๋งคํํ๋ฉด ์ฌ๋ฐ๋ฅด๊ฒ ๋ณํ๋๋ค`() {
+ val detail = FortuneDetail(score = 85, title = "Success", description = "Great things happen")
+ val domain = detail.toDomain()
+
+ assertEquals(85, domain.score)
+ assertEquals("Success", domain.title)
+ assertEquals("Great things happen", domain.description)
+ }
+
+ private fun dummyFortuneResponse() = FortuneResponse(
+ id = 123,
+ dailyFortune = "Today is your lucky day",
+ dailyFortuneDescription = "You'll find success in your endeavors.",
+ avgFortuneScore = 88,
+ studyCareerFortune = dummyDetail(),
+ wealthFortune = dummyDetail(),
+ healthFortune = dummyDetail(),
+ loveFortune = dummyDetail(),
+ luckyOutfitTop = "T-shirt",
+ luckyOutfitBottom = "Shorts",
+ luckyOutfitShoes = "Sneakers",
+ luckyOutfitAccessory = "Bracelet",
+ unluckyColor = "Gray",
+ luckyColor = "Yellow",
+ luckyFood = "Sushi"
+ )
+
+ private fun dummyDetail() = FortuneDetail(
+ score = 90,
+ title = "High Energy",
+ description = "You will feel energetic all day."
+ )
+}
diff --git a/data/src/test/kotlin/com/yapp/data/FortuneRepositoryImplTest.kt b/data/src/test/kotlin/com/yapp/data/FortuneRepositoryImplTest.kt
new file mode 100644
index 00000000..7abaf7b3
--- /dev/null
+++ b/data/src/test/kotlin/com/yapp/data/FortuneRepositoryImplTest.kt
@@ -0,0 +1,69 @@
+package com.yapp.data
+
+import com.yapp.data.local.datasource.FortuneLocalDataSource
+import com.yapp.data.remote.datasource.FortuneDataSource
+import com.yapp.data.remote.dto.response.FortuneDetail
+import com.yapp.data.remote.dto.response.FortuneResponse
+import com.yapp.data.remote.dto.response.toDomain
+import com.yapp.data.repositoryimpl.FortuneRepositoryImpl
+import io.mockk.coEvery
+import io.mockk.mockk
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class FortuneRepositoryImplTest {
+
+ private val remoteDataSource = mockk()
+ private val localDataSource = mockk(relaxed = true)
+
+ private val repository = FortuneRepositoryImpl(
+ fortuneRemoteDataSource = remoteDataSource,
+ fortuneLocalDataSource = localDataSource,
+ )
+
+ @Test
+ fun `์ด์ธ ์์ฒญ์ ์ฑ๊ณตํ๋ฉด ๋๋ฉ์ธ ๋ชจ๋ธ๋ก ๋ฐํ๋๋ค`() = runTest {
+ val response = dummyFortuneResponse()
+ coEvery { remoteDataSource.postFortune(1L) } returns Result.success(response)
+
+ val result = repository.postFortune(1L)
+
+ assert(result.isSuccess)
+ assertEquals(response.toDomain(), result.getOrNull())
+ }
+
+ @Test
+ fun `์ด์ธ ์์ธ ์กฐํ์ ์คํจํ๋ฉด ์คํจ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ๋ค`() = runTest {
+ val exception = RuntimeException("Not found")
+ coEvery { remoteDataSource.getFortune(2L) } returns Result.failure(exception)
+
+ val result = repository.getFortune(2L)
+
+ assert(result.isFailure)
+ }
+
+ private fun dummyFortuneResponse() = FortuneResponse(
+ id = 1L,
+ dailyFortune = "Good luck",
+ dailyFortuneDescription = "You will be lucky today",
+ avgFortuneScore = 90,
+ studyCareerFortune = dummyDetail(),
+ wealthFortune = dummyDetail(),
+ healthFortune = dummyDetail(),
+ loveFortune = dummyDetail(),
+ luckyOutfitTop = "Hoodie",
+ luckyOutfitBottom = "Jeans",
+ luckyOutfitShoes = "Sneakers",
+ luckyOutfitAccessory = "Watch",
+ unluckyColor = "Black",
+ luckyColor = "White",
+ luckyFood = "Pizza",
+ )
+
+ private fun dummyDetail() = FortuneDetail(
+ score = 100,
+ title = "Title",
+ description = "Description"
+ )
+}
diff --git a/domain/src/main/java/com/yapp/domain/MissionMode.kt b/domain/src/main/java/com/yapp/domain/MissionMode.kt
new file mode 100644
index 00000000..b047c7c8
--- /dev/null
+++ b/domain/src/main/java/com/yapp/domain/MissionMode.kt
@@ -0,0 +1,13 @@
+package com.yapp.domain
+
+enum class MissionMode {
+ REAL,
+ PREVIEW,
+ ;
+
+ companion object {
+ fun fromRaw(raw: String?): MissionMode {
+ return raw?.let { entries.find { it.name == raw } } ?: REAL
+ }
+ }
+}
diff --git a/domain/src/main/java/com/yapp/domain/model/Alarm.kt b/domain/src/main/java/com/yapp/domain/model/Alarm.kt
index f04d4148..14079633 100644
--- a/domain/src/main/java/com/yapp/domain/model/Alarm.kt
+++ b/domain/src/main/java/com/yapp/domain/model/Alarm.kt
@@ -12,8 +12,6 @@ import kotlinx.serialization.json.Json
data class Alarm(
val id: Long = 0,
- val isAm: Boolean = true,
-
val hour: Int = 6,
val minute: Int = 0,
val second: Int = 0,
@@ -35,6 +33,9 @@ data class Alarm(
val soundVolume: Int = 70,
val isAlarmActive: Boolean = true,
+
+ val missionType: MissionType = MissionType.TAP,
+ val missionCount: Int = 10,
) : Parcelable {
companion object {
@@ -62,14 +63,7 @@ fun Alarm.copyFrom(source: Alarm): Alarm {
}
fun Alarm.toTimeString(): String {
- val displayHour = if (isAm && hour == 12) {
- 0 // ์ค์ 12์๋ 0์ผ๋ก ํ์
- } else if (!isAm && hour != 12) {
- hour + 12 // ์คํ 1์~11์์๋ 12๋ฅผ ๋ํจ
- } else {
- hour // ์ค์ 1์~11์ ๋ฐ ์คํ 12์๋ ๊ทธ๋๋ก ์ฌ์ฉ
- }
- val formattedHour = displayHour.toString().padStart(2, '0')
+ val formattedHour = hour.toString().padStart(2, '0')
val formattedMinute = minute.toString().padStart(2, '0')
return "$formattedHour:$formattedMinute"
diff --git a/domain/src/main/java/com/yapp/domain/model/AlarmDay.kt b/domain/src/main/java/com/yapp/domain/model/AlarmDay.kt
index beaead69..7f349ed5 100644
--- a/domain/src/main/java/com/yapp/domain/model/AlarmDay.kt
+++ b/domain/src/main/java/com/yapp/domain/model/AlarmDay.kt
@@ -1,5 +1,7 @@
package com.yapp.domain.model
+import java.time.DayOfWeek
+
enum class AlarmDay(val bitValue: Int) {
SUN(0b0000001), // 1
MON(0b0000010), // 2
@@ -11,8 +13,13 @@ enum class AlarmDay(val bitValue: Int) {
;
}
-fun AlarmDay.toDayOfWeek(): java.time.DayOfWeek {
- return java.time.DayOfWeek.of(((this.ordinal + 6) % 7) + 1)
+fun AlarmDay.toDayOfWeek(): DayOfWeek {
+ return DayOfWeek.of(((this.ordinal + 6) % 7) + 1)
+}
+
+fun DayOfWeek.toAlarmDay(): AlarmDay {
+ val index = (this.value % 7)
+ return AlarmDay.entries[index]
}
fun Set.toRepeatDays(): Int {
diff --git a/domain/src/main/java/com/yapp/domain/model/Dummy.kt b/domain/src/main/java/com/yapp/domain/model/Dummy.kt
deleted file mode 100644
index 9a450d6f..00000000
--- a/domain/src/main/java/com/yapp/domain/model/Dummy.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package com.yapp.domain.model
-
-data class Dummy(
- val id: Int,
- val name: String,
-)
diff --git a/domain/src/main/java/com/yapp/domain/model/fortune/Fortune.kt b/domain/src/main/java/com/yapp/domain/model/Fortune.kt
similarity index 94%
rename from domain/src/main/java/com/yapp/domain/model/fortune/Fortune.kt
rename to domain/src/main/java/com/yapp/domain/model/Fortune.kt
index 0a15638f..0af841cc 100644
--- a/domain/src/main/java/com/yapp/domain/model/fortune/Fortune.kt
+++ b/domain/src/main/java/com/yapp/domain/model/Fortune.kt
@@ -1,4 +1,4 @@
-package com.yapp.domain.model.fortune
+package com.yapp.domain.model
data class Fortune(
val id: Long,
diff --git a/domain/src/main/java/com/yapp/domain/model/FortuneCreateStatus.kt b/domain/src/main/java/com/yapp/domain/model/FortuneCreateStatus.kt
new file mode 100644
index 00000000..27ae9ad0
--- /dev/null
+++ b/domain/src/main/java/com/yapp/domain/model/FortuneCreateStatus.kt
@@ -0,0 +1,8 @@
+package com.yapp.domain.model
+
+sealed class FortuneCreateStatus {
+ data object Idle : FortuneCreateStatus()
+ data object Creating : FortuneCreateStatus()
+ data class Success(val fortuneId: Long) : FortuneCreateStatus()
+ data object Failure : FortuneCreateStatus()
+}
diff --git a/domain/src/main/java/com/yapp/domain/model/MissionMode.kt b/domain/src/main/java/com/yapp/domain/model/MissionMode.kt
new file mode 100644
index 00000000..16009fc2
--- /dev/null
+++ b/domain/src/main/java/com/yapp/domain/model/MissionMode.kt
@@ -0,0 +1,13 @@
+package com.yapp.domain.model
+
+enum class MissionMode {
+ REAL,
+ PREVIEW,
+ ;
+
+ companion object {
+ fun fromRaw(raw: String?): MissionMode {
+ return raw?.let { entries.find { it.name == raw } } ?: REAL
+ }
+ }
+}
diff --git a/domain/src/main/java/com/yapp/domain/model/MissionType.kt b/domain/src/main/java/com/yapp/domain/model/MissionType.kt
index 45388ee6..bcfdff3c 100644
--- a/domain/src/main/java/com/yapp/domain/model/MissionType.kt
+++ b/domain/src/main/java/com/yapp/domain/model/MissionType.kt
@@ -1,17 +1,21 @@
package com.yapp.domain.model
-sealed class MissionType {
- data object Shake : MissionType()
- data object Click : MissionType()
+enum class MissionType(val value: Int) {
+ NONE(0),
+ TAP(1),
+ SHAKE(2),
+ ;
companion object {
+ fun fromInt(value: Int): MissionType {
+ return MissionType.entries.find { it.value == value } ?: NONE
+ }
+
fun fromRemoteValue(value: String): MissionType {
return when (value) {
- "tap_mission" -> Click
- "shake_mission" -> Shake
- else -> {
- Click
- }
+ "tap_mission" -> TAP
+ "shake_mission" -> SHAKE
+ else -> NONE
}
}
}
diff --git a/domain/src/main/java/com/yapp/domain/repository/AlarmRepository.kt b/domain/src/main/java/com/yapp/domain/repository/AlarmRepository.kt
index d3a7ae83..60123473 100644
--- a/domain/src/main/java/com/yapp/domain/repository/AlarmRepository.kt
+++ b/domain/src/main/java/com/yapp/domain/repository/AlarmRepository.kt
@@ -13,9 +13,7 @@ interface AlarmRepository {
fun updateAlarmVolume(volume: Int)
fun releaseSoundPlayer()
fun getAllAlarms(): Flow>
- fun getPagedAlarms(limit: Int, offset: Int): Flow>
- fun getAlarmsByTime(hour: Int, minute: Int, isAm: Boolean): Flow>
- fun getAlarmCount(): Flow
+ fun getAlarmsByTime(hour: Int, minute: Int): Flow>
suspend fun insertAlarm(alarm: Alarm): Result
suspend fun updateAlarm(alarm: Alarm): Result
suspend fun updateAlarmActive(id: Long, active: Boolean): Result
diff --git a/domain/src/main/java/com/yapp/domain/repository/DummyRepository.kt b/domain/src/main/java/com/yapp/domain/repository/DummyRepository.kt
deleted file mode 100644
index c64fe8b0..00000000
--- a/domain/src/main/java/com/yapp/domain/repository/DummyRepository.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.yapp.domain.repository
-
-import com.yapp.domain.model.Dummy
-
-interface DummyRepository {
- suspend fun fetchDummy(): Result
- suspend fun saveDummy(dummy: Dummy): Result
-}
diff --git a/domain/src/main/java/com/yapp/domain/repository/FortuneRepository.kt b/domain/src/main/java/com/yapp/domain/repository/FortuneRepository.kt
index efbabfc1..372fd5fe 100644
--- a/domain/src/main/java/com/yapp/domain/repository/FortuneRepository.kt
+++ b/domain/src/main/java/com/yapp/domain/repository/FortuneRepository.kt
@@ -1,8 +1,31 @@
package com.yapp.domain.repository
-import com.yapp.domain.model.fortune.Fortune
+import com.yapp.domain.model.Fortune
+import com.yapp.domain.model.FortuneCreateStatus
+import kotlinx.coroutines.flow.Flow
interface FortuneRepository {
+ val fortuneIdFlow: Flow
+ val fortuneDateEpochFlow: Flow
+ val fortuneImageIdFlow: Flow
+ val fortuneScoreFlow: Flow
+ val hasUnseenFortuneFlow: Flow
+ val shouldShowFortuneToolTipFlow: Flow
+ val isFirstAlarmDismissedTodayFlow: Flow
+
+ val fortuneCreateStatusFlow: Flow
+
+ suspend fun markFortuneAsCreating()
+ suspend fun markFortuneAsCreated(fortuneId: Long)
+ suspend fun markFortuneAsFailed()
+ suspend fun markFortuneSeen()
+ suspend fun markFortuneTooltipShown()
+ suspend fun saveFortuneImageId(imageResId: Int)
+ suspend fun saveFortuneScore(score: Int)
+ suspend fun markFirstAlarmDismissedToday()
+
+ suspend fun clearFortuneData()
+
suspend fun postFortune(userId: Long): Result
suspend fun getFortune(fortuneId: Long): Result
}
diff --git a/domain/src/main/java/com/yapp/domain/repository/ImageRepository.kt b/domain/src/main/java/com/yapp/domain/repository/ImageRepository.kt
deleted file mode 100644
index 9abddf8a..00000000
--- a/domain/src/main/java/com/yapp/domain/repository/ImageRepository.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package com.yapp.domain.repository
-
-interface ImageRepository {
- suspend fun saveImage(byteArray: ByteArray): Boolean
-}
diff --git a/domain/src/main/java/com/yapp/domain/repository/UserInfoRepository.kt b/domain/src/main/java/com/yapp/domain/repository/UserInfoRepository.kt
index 34a6580a..a9df412e 100644
--- a/domain/src/main/java/com/yapp/domain/repository/UserInfoRepository.kt
+++ b/domain/src/main/java/com/yapp/domain/repository/UserInfoRepository.kt
@@ -2,8 +2,22 @@ package com.yapp.domain.repository
import com.yapp.domain.model.EditUser
import com.yapp.domain.model.User
+import kotlinx.coroutines.flow.Flow
interface UserInfoRepository {
+ val userIdFlow: Flow
+ val userNameFlow: Flow
+ val onboardingCompletedFlow: Flow
+ val updateNoticeDontShowVersionFlow: Flow
+ val updateNoticeLastShownDateEpochFlow: Flow
+
+ suspend fun saveUserId(userId: Long)
+ suspend fun saveUserName(userName: String)
+ suspend fun setOnboardingCompleted()
+ suspend fun markUpdateNoticeDontShow(version: String)
+ suspend fun markUpdateNoticeShownToday()
+ suspend fun clearUserData()
+
suspend fun getUserInfo(userId: Long): Result
suspend fun updateUserInfo(userId: Long, editUser: EditUser): Result
}
diff --git a/domain/src/main/java/com/yapp/domain/scheduler/AlarmScheduler.kt b/domain/src/main/java/com/yapp/domain/scheduler/AlarmScheduler.kt
new file mode 100644
index 00000000..1656ac30
--- /dev/null
+++ b/domain/src/main/java/com/yapp/domain/scheduler/AlarmScheduler.kt
@@ -0,0 +1,8 @@
+package com.yapp.domain.scheduler
+
+import com.yapp.domain.model.Alarm
+
+interface AlarmScheduler {
+ fun scheduleAlarm(alarm: Alarm)
+ fun unScheduleAlarm(alarm: Alarm)
+}
diff --git a/domain/src/main/java/com/yapp/domain/usecase/AlarmUseCase.kt b/domain/src/main/java/com/yapp/domain/usecase/AlarmUseCase.kt
index 2411beeb..86720460 100644
--- a/domain/src/main/java/com/yapp/domain/usecase/AlarmUseCase.kt
+++ b/domain/src/main/java/com/yapp/domain/usecase/AlarmUseCase.kt
@@ -4,11 +4,13 @@ import android.net.Uri
import com.yapp.domain.model.Alarm
import com.yapp.domain.model.AlarmSound
import com.yapp.domain.repository.AlarmRepository
+import com.yapp.domain.scheduler.AlarmScheduler
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
class AlarmUseCase @Inject constructor(
private val alarmRepository: AlarmRepository,
+ private val alarmScheduler: AlarmScheduler,
) {
suspend fun getAlarmSounds(): Result> = alarmRepository.getAlarmSounds()
fun initializeSoundPlayer(uri: Uri) = alarmRepository.initializeSoundPlayer(uri)
@@ -17,12 +19,13 @@ class AlarmUseCase @Inject constructor(
fun updateAlarmVolume(volume: Int) = alarmRepository.updateAlarmVolume(volume)
fun releaseSoundPlayer() = alarmRepository.releaseSoundPlayer()
fun getAllAlarms(): Flow> = alarmRepository.getAllAlarms()
- fun getPagedAlarms(limit: Int, offset: Int): Flow> = alarmRepository.getPagedAlarms(limit, offset)
- fun getAlarmsByTime(hour: Int, minute: Int, isAm: Boolean): Flow> = alarmRepository.getAlarmsByTime(hour, minute, isAm)
- fun getAlarmCount(): Flow = alarmRepository.getAlarmCount()
+ fun getAlarmsByTime(hour: Int, minute: Int): Flow> = alarmRepository.getAlarmsByTime(hour, minute)
suspend fun insertAlarm(alarm: Alarm): Result = alarmRepository.insertAlarm(alarm)
suspend fun updateAlarm(alarm: Alarm): Result = alarmRepository.updateAlarm(alarm)
suspend fun updateAlarmActive(id: Long, active: Boolean): Result = alarmRepository.updateAlarmActive(id, active)
suspend fun getAlarm(id: Long): Result = alarmRepository.getAlarm(id)
suspend fun deleteAlarm(id: Long): Result = alarmRepository.deleteAlarm(id)
+
+ fun scheduleAlarm(alarm: Alarm) = alarmScheduler.scheduleAlarm(alarm)
+ fun unScheduleAlarm(alarm: Alarm) = alarmScheduler.unScheduleAlarm(alarm)
}
diff --git a/domain/src/main/java/com/yapp/domain/usecase/DummyUseCase.kt b/domain/src/main/java/com/yapp/domain/usecase/DummyUseCase.kt
deleted file mode 100644
index a3584da6..00000000
--- a/domain/src/main/java/com/yapp/domain/usecase/DummyUseCase.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.yapp.domain.usecase
-
-import com.yapp.domain.model.Dummy
-import com.yapp.domain.repository.DummyRepository
-import javax.inject.Inject
-
-class DummyUseCase @Inject constructor(
- private val dummyRepository: DummyRepository,
-) {
- suspend fun fetch(): Result = dummyRepository.fetchDummy()
- suspend fun save(dummy: Dummy): Result = dummyRepository.saveDummy(dummy)
-}
diff --git a/feature/alarm-interaction/build.gradle.kts b/feature/alarm-interaction/build.gradle.kts
index efc6eeec..22e53709 100644
--- a/feature/alarm-interaction/build.gradle.kts
+++ b/feature/alarm-interaction/build.gradle.kts
@@ -15,7 +15,6 @@ dependencies {
implementation(projects.core.alarm)
implementation(projects.core.media)
implementation(projects.domain)
- implementation(projects.core.datastore)
implementation(libs.orbit.core)
implementation(libs.orbit.compose)
implementation(libs.orbit.viewmodel)
diff --git a/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/AlarmInteractionActivity.kt b/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/AlarmInteractionActivity.kt
index 5860eb6d..e95a93b2 100644
--- a/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/AlarmInteractionActivity.kt
+++ b/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/AlarmInteractionActivity.kt
@@ -7,14 +7,11 @@ import android.content.IntentFilter
import android.content.pm.ActivityInfo
import android.os.Build
import android.os.Bundle
-import android.util.Log
import androidx.activity.ComponentActivity
-import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
-import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.navigationBarsPadding
-import androidx.compose.material3.Surface
import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.Modifier
import androidx.core.util.Consumer
@@ -23,8 +20,8 @@ import com.yapp.alarm.AlarmConstants
import com.yapp.alarm.receivers.AlarmInteractionActivityReceiver
import com.yapp.common.navigation.rememberOrbitNavigator
import com.yapp.common.navigation.route.AlarmInteractionBaseRoute
-import com.yapp.designsystem.theme.OrbitTheme
import com.yapp.domain.model.Alarm
+import com.yapp.ui.component.navigation.NavigationBarScrim
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
@@ -48,35 +45,23 @@ class AlarmInteractionActivity : ComponentActivity() {
registerAlarmInteractionActivityCloseReceiver()
- enableEdgeToEdge(
- statusBarStyle = SystemBarStyle.light(
- android.graphics.Color.TRANSPARENT,
- android.graphics.Color.TRANSPARENT,
- ),
- navigationBarStyle = SystemBarStyle.light(
- android.graphics.Color.BLACK,
- android.graphics.Color.BLACK,
- ),
- )
+ enableEdgeToEdge()
setContent {
val navigator = rememberOrbitNavigator()
- Surface(
- color = OrbitTheme.colors.gray_900,
- modifier = Modifier
- .fillMaxSize()
- .navigationBarsPadding(),
- ) {
+ Box {
NavHost(
+ modifier = Modifier.navigationBarsPadding(),
navController = navigator.navController,
startDestination = AlarmInteractionBaseRoute,
- modifier = Modifier.navigationBarsPadding(),
) {
alarmInteractionNavGraph(
navigator = navigator,
alarm = alarm,
)
}
+
+ NavigationBarScrim()
}
DisposableEffect(this, navigator.navController) {
@@ -87,7 +72,6 @@ class AlarmInteractionActivity : ComponentActivity() {
@Suppress("DEPRECATION")
newIntent.getParcelableExtra(AlarmConstants.EXTRA_ALARM)
}
- Log.d("AlarmInteractionActivity", "New Intent: $newIntent")
newAlarm?.let { alarm ->
navigator.navigateToAlarmAction(alarm = alarm)
}
diff --git a/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/AlarmInteractionNavGraph.kt b/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/AlarmInteractionNavGraph.kt
index bc8d0035..eb025a86 100644
--- a/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/AlarmInteractionNavGraph.kt
+++ b/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/AlarmInteractionNavGraph.kt
@@ -48,14 +48,6 @@ fun NavGraphBuilder.alarmInteractionNavGraph(
}
},
)
- } ?: run {
- navigator.navigateToHome(
- navOptions {
- popUpTo(AlarmInteractionBaseRoute) {
- inclusive = true
- }
- },
- )
}
}
}
@@ -71,9 +63,7 @@ fun NavGraphBuilder.alarmInteractionNavGraph(
composable(
typeMap = mapOf(typeOf() to AlarmArgType),
) {
- AlarmSnoozeTimerRoute(
- navigator = navigator,
- )
+ AlarmSnoozeTimerRoute()
}
}
}
diff --git a/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionContract.kt b/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionContract.kt
index 470f89c3..9eee8bad 100644
--- a/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionContract.kt
+++ b/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionContract.kt
@@ -1,7 +1,6 @@
package com.yapp.alarm.interaction.action
import com.yapp.domain.model.Alarm
-import com.yapp.ui.base.SideEffect
import com.yapp.ui.base.UiState
class AlarmActionContract {
@@ -15,7 +14,7 @@ class AlarmActionContract {
val snoozeEnabled: Boolean = true,
val snoozeInterval: Int = 5,
val snoozeCount: Int = 5,
- val isFirstMission: Boolean? = null,
+ val shouldShowMissionStart: Boolean? = null,
) : UiState
sealed class Action {
diff --git a/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionScreen.kt b/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionScreen.kt
index 4db7ed7b..8ff13cb9 100644
--- a/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionScreen.kt
+++ b/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionScreen.kt
@@ -17,7 +17,6 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -36,6 +35,7 @@ import com.yapp.ui.component.button.OrbitButton
import com.yapp.ui.component.lottie.LottieAnimation
import com.yapp.ui.utils.heightForScreenPercentage
import feature.alarm.interaction.R
+import org.orbitmvi.orbit.compose.collectSideEffect
import java.util.Locale
@Composable
@@ -44,30 +44,33 @@ internal fun AlarmActionRoute(
navigator: OrbitNavigator,
) {
val state by viewModel.container.stateFlow.collectAsStateWithLifecycle()
- val sideEffect = viewModel.container.sideEffectFlow
- LaunchedEffect(sideEffect) {
- sideEffect.collect { action ->
- when (action) {
- is AlarmActionContract.SideEffect.NavigateToAlarmSnooze -> {
- navigator.navigateToAlarmSnoozeTimer(action.alarm)
- }
- }
- }
+ viewModel.collectSideEffect {
+ handleSideEffect(it, navigator)
}
AlarmActionScreen(
- stateProvider = { state },
- eventDispatcher = viewModel::processAction,
+ state = state,
+ processAction = viewModel::processAction,
)
}
+private fun handleSideEffect(
+ sideEffect: AlarmActionContract.SideEffect,
+ navigator: OrbitNavigator,
+) {
+ when (sideEffect) {
+ is AlarmActionContract.SideEffect.NavigateToAlarmSnooze -> {
+ navigator.navigateToAlarmSnoozeTimer(sideEffect.alarm)
+ }
+ }
+}
+
@Composable
internal fun AlarmActionScreen(
- stateProvider: () -> AlarmActionContract.State,
- eventDispatcher: (AlarmActionContract.Action) -> Unit,
+ state: AlarmActionContract.State,
+ processAction: (AlarmActionContract.Action) -> Unit,
) {
- val state = stateProvider()
val context = LocalContext.current
if (state.initialLoading) {
@@ -81,10 +84,10 @@ internal fun AlarmActionScreen(
snoozeEnabled = state.snoozeEnabled,
snoozeInterval = state.snoozeInterval,
snoozeCount = state.snoozeCount,
- isFirstMission = state.isFirstMission,
- onSnoozeClick = { eventDispatcher(AlarmActionContract.Action.Snooze) },
+ isFirstMission = state.shouldShowMissionStart,
+ onSnoozeClick = { processAction(AlarmActionContract.Action.Snooze) },
onDismissClick = {
- eventDispatcher(AlarmActionContract.Action.Dismiss)
+ processAction(AlarmActionContract.Action.Dismiss)
(context as? androidx.activity.ComponentActivity)?.finish()
},
)
@@ -121,74 +124,72 @@ private fun AlarmActionContent(
onSnoozeClick: () -> Unit,
onDismissClick: () -> Unit,
) {
- Box(modifier = Modifier.statusBarsPadding()) {
- Column(
- modifier = Modifier
- .fillMaxSize()
- .background(
- color = Color(0xFF496381),
- ),
- horizontalAlignment = Alignment.CenterHorizontally,
- ) {
- Spacer(
- modifier = Modifier.heightForScreenPercentage(
- 0.17f,
- ),
- )
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(
+ color = Color(0xFF496381),
+ ),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Spacer(
+ modifier = Modifier.heightForScreenPercentage(
+ 0.17f,
+ ),
+ )
- AlarmTime(
- isAm = isAm,
- hour = hour,
- minute = minute,
- todayDate = todayDate,
- )
+ AlarmTime(
+ isAm = isAm,
+ hour = hour,
+ minute = minute,
+ todayDate = todayDate,
+ )
- Spacer(modifier = Modifier.height(102.dp))
+ Spacer(modifier = Modifier.height(102.dp))
- Icon(
- painter = painterResource(id = core.designsystem.R.drawable.ic_alarm_action_character),
- tint = Color(0xFF07203E),
- contentDescription = "Alarm Action Character",
- )
+ Icon(
+ painter = painterResource(id = core.designsystem.R.drawable.ic_alarm_action_character),
+ tint = Color(0xFF07203E),
+ contentDescription = "Alarm Action Character",
+ )
- Spacer(modifier = Modifier.height(56.dp))
+ Spacer(modifier = Modifier.height(56.dp))
- if (snoozeEnabled && snoozeCount != 0) {
- AlarmSnoozeButton(
- snoozeInterval = snoozeInterval,
- snoozeCount = snoozeCount,
- onSnoozeClick = onSnoozeClick,
- )
- } else {
- Spacer(modifier = Modifier.height(54.dp))
- }
+ if (snoozeEnabled && snoozeCount != 0) {
+ AlarmSnoozeButton(
+ snoozeInterval = snoozeInterval,
+ snoozeCount = snoozeCount,
+ onSnoozeClick = onSnoozeClick,
+ )
+ } else {
+ Spacer(modifier = Modifier.height(54.dp))
+ }
- Spacer(modifier = Modifier.weight(1f))
+ Spacer(modifier = Modifier.weight(1f))
- if (isFirstMission != null) {
- OrbitButton(
- label = if (isFirstMission) {
- stringResource(id = R.string.alarm_off_mission_start_btn)
- } else {
- stringResource(id = R.string.alarm_off_btn)
- },
- enabled = true,
- modifier = Modifier
- .padding(
- start = 40.dp,
- end = 40.dp,
- bottom = 48.dp,
- )
- .height(62.dp),
- onClick = onDismissClick,
- )
- } else {
- Spacer(modifier = Modifier.height(62.dp))
- }
+ if (isFirstMission != null) {
+ OrbitButton(
+ label = if (isFirstMission) {
+ stringResource(id = R.string.alarm_off_mission_start_btn)
+ } else {
+ stringResource(id = R.string.alarm_off_btn)
+ },
+ enabled = true,
+ modifier = Modifier
+ .padding(
+ start = 40.dp,
+ end = 40.dp,
+ bottom = 48.dp,
+ )
+ .height(62.dp),
+ onClick = onDismissClick,
+ )
+ } else {
+ Spacer(modifier = Modifier.height(62.dp))
}
-
- AdsBanner()
}
+
+ AdsBanner(modifier = Modifier.statusBarsPadding())
}
@Composable
@@ -298,18 +299,16 @@ private fun AlarmSnoozeButton(
internal fun AlarmActionScreenPreview() {
OrbitTheme {
AlarmActionScreen(
- stateProvider = {
- AlarmActionContract.State(
- initialLoading = false,
- isAm = true,
- hour = 10,
- minute = 30,
- todayDate = "10์ 10์ผ ์์์ผ",
- snoozeInterval = 5,
- snoozeCount = -1,
- )
- },
- eventDispatcher = {},
+ state = AlarmActionContract.State(
+ initialLoading = false,
+ isAm = true,
+ hour = 10,
+ minute = 30,
+ todayDate = "10์ 10์ผ ์์์ผ",
+ snoozeInterval = 5,
+ snoozeCount = -1,
+ ),
+ processAction = {},
)
}
}
diff --git a/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionViewModel.kt b/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionViewModel.kt
index 57e6cd8c..49182b15 100644
--- a/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionViewModel.kt
+++ b/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionViewModel.kt
@@ -2,20 +2,21 @@ package com.yapp.alarm.interaction.action
import android.app.Application
import androidx.lifecycle.SavedStateHandle
-import androidx.lifecycle.viewModelScope
+import androidx.lifecycle.ViewModel
import com.yapp.alarm.pendingIntent.interaction.createAlarmDismissIntent
import com.yapp.alarm.pendingIntent.interaction.createAlarmSnoozeIntent
-import com.yapp.datastore.UserPreferences
import com.yapp.domain.model.Alarm
-import com.yapp.ui.base.BaseViewModel
+import com.yapp.domain.model.MissionType
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.firstOrNull
-import kotlinx.coroutines.isActive
-import kotlinx.coroutines.launch
+import org.orbitmvi.orbit.Container
+import org.orbitmvi.orbit.ContainerHost
+import org.orbitmvi.orbit.syntax.simple.intent
+import org.orbitmvi.orbit.syntax.simple.postSideEffect
+import org.orbitmvi.orbit.syntax.simple.reduce
+import org.orbitmvi.orbit.viewmodel.container
import java.time.LocalDate
import java.time.LocalTime
-import java.time.format.DateTimeFormatter
import java.time.format.TextStyle
import java.util.Locale
import javax.inject.Inject
@@ -23,81 +24,76 @@ import javax.inject.Inject
@HiltViewModel
class AlarmActionViewModel @Inject constructor(
private val app: Application,
- private val userPreferences: UserPreferences,
savedStateHandle: SavedStateHandle,
-) : BaseViewModel(
- AlarmActionContract.State(),
-) {
+) : ViewModel(), ContainerHost {
+
+ override val container: Container = container(
+ initialState = AlarmActionContract.State(),
+ ) {
+ fetchShouldShowMissionStart()
+ initializeAlarmState()
+ startClock()
+ }
+
private val alarmJson: String? = savedStateHandle.get("alarm")
private val alarm: Alarm? = alarmJson?.let { Alarm.fromJson(it) }
- init {
- fetchIsFirstMission()
- updateState {
- copy(
+ fun processAction(action: AlarmActionContract.Action) {
+ when (action) {
+ is AlarmActionContract.Action.Snooze -> snooze()
+ is AlarmActionContract.Action.Dismiss -> dismiss()
+ }
+ }
+
+ private fun initializeAlarmState() = intent {
+ reduce {
+ state.copy(
snoozeEnabled = alarm?.isSnoozeEnabled ?: false,
snoozeCount = alarm?.snoozeCount ?: 5,
snoozeInterval = alarm?.snoozeInterval ?: 5,
)
}
-
- startClock()
}
- private fun fetchIsFirstMission() {
- viewModelScope.launch {
- val fortuneDate = userPreferences.fortuneDateFlow.firstOrNull()
- val todayDate = LocalDate.now().format(DateTimeFormatter.ISO_DATE)
- val isFirstMission = fortuneDate != todayDate
-
- updateState {
- copy(isFirstMission = isFirstMission)
- }
+ private fun fetchShouldShowMissionStart() = intent {
+ reduce {
+ state.copy(shouldShowMissionStart = (alarm?.missionType ?: MissionType.NONE) != MissionType.NONE)
}
}
- private fun startClock() {
- viewModelScope.launch {
- while (isActive) {
- val now = LocalTime.now()
- val today = LocalDate.now()
- val dayOfWeek = today.dayOfWeek.getDisplayName(TextStyle.FULL, Locale.KOREAN)
-
- updateState {
- copy(
- isAm = now.hour < 12,
- hour = if (now.hour % 12 == 0) 12 else now.hour % 12,
- minute = now.minute,
- todayDate = "${today.monthValue}์ ${today.dayOfMonth}์ผ $dayOfWeek",
- initialLoading = false,
- )
- }
+ private fun startClock() = intent {
+ while (true) {
+ val now = LocalTime.now()
+ val today = LocalDate.now()
+ val dayOfWeek = today.dayOfWeek.getDisplayName(TextStyle.FULL, Locale.KOREAN)
- delay(1000L)
+ reduce {
+ state.copy(
+ isAm = now.hour < 12,
+ hour = if (now.hour % 12 == 0) 12 else now.hour % 12,
+ minute = now.minute,
+ todayDate = "${today.monthValue}์ ${today.dayOfMonth}์ผ $dayOfWeek",
+ initialLoading = false,
+ )
}
- }
- }
- fun processAction(action: AlarmActionContract.Action) {
- when (action) {
- is AlarmActionContract.Action.Snooze -> snooze()
- is AlarmActionContract.Action.Dismiss -> dismiss()
+ delay(1000L)
}
}
- private fun snooze() {
+ private fun snooze() = intent {
sendAlarmSnoozeEventToAlarmReceiver()
- updateState {
- copy(
- snoozeCount = if (currentState.snoozeCount == -1) {
- currentState.snoozeCount
+ reduce {
+ state.copy(
+ snoozeCount = if (state.snoozeCount == -1) {
+ state.snoozeCount
} else {
- currentState.snoozeCount - 1
+ state.snoozeCount - 1
},
)
}
alarm?.let {
- emitSideEffect(AlarmActionContract.SideEffect.NavigateToAlarmSnooze(it))
+ postSideEffect(AlarmActionContract.SideEffect.NavigateToAlarmSnooze(it))
}
}
@@ -116,10 +112,12 @@ class AlarmActionViewModel @Inject constructor(
}
private fun sendAlarmDismissEventToAlarmReceiver() {
- alarm?.id?.let { id ->
+ alarm?.let { alarm ->
val alarmDismissIntent = createAlarmDismissIntent(
context = app,
- notificationId = id,
+ notificationId = alarm.id,
+ missionType = alarm.missionType.value,
+ missionCount = alarm.missionCount,
)
app.sendBroadcast(alarmDismissIntent)
}
diff --git a/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/snooze/AlarmSnoozeTimerScreen.kt b/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/snooze/AlarmSnoozeTimerScreen.kt
index e914f506..da09090f 100644
--- a/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/snooze/AlarmSnoozeTimerScreen.kt
+++ b/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/snooze/AlarmSnoozeTimerScreen.kt
@@ -19,7 +19,6 @@ import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -42,7 +41,6 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.yapp.common.navigation.OrbitNavigator
import com.yapp.designsystem.theme.OrbitTheme
import com.yapp.ui.component.lottie.LottieAnimation
import com.yapp.ui.utils.heightForScreenPercentage
@@ -51,27 +49,20 @@ import feature.alarm.interaction.R
@Composable
internal fun AlarmSnoozeTimerRoute(
viewModel: AlarmSnoozeTimerViewModel = hiltViewModel(),
- navigator: OrbitNavigator,
) {
val state by viewModel.container.stateFlow.collectAsStateWithLifecycle()
- val sideEffect = viewModel.container.sideEffectFlow
-
- LaunchedEffect(sideEffect) {
- sideEffect.collect { }
- }
AlarmSnoozeTimerScreen(
- stateProvider = { state },
- eventDispatcher = viewModel::processAction,
+ state = state,
+ processAction = viewModel::processAction,
)
}
@Composable
internal fun AlarmSnoozeTimerScreen(
- stateProvider: () -> AlarmSnoozeTimerContract.State,
- eventDispatcher: (AlarmSnoozeTimerContract.Action) -> Unit,
+ state: AlarmSnoozeTimerContract.State,
+ processAction: (AlarmSnoozeTimerContract.Action) -> Unit,
) {
- val state = stateProvider()
val context = LocalContext.current
if (state.initialLoading) {
@@ -82,7 +73,7 @@ internal fun AlarmSnoozeTimerScreen(
totalSeconds = state.totalSeconds,
isFirstMission = state.isFirstMission,
onDismissClick = {
- eventDispatcher(AlarmSnoozeTimerContract.Action.Dismiss)
+ processAction(AlarmSnoozeTimerContract.Action.Dismiss)
(context as? ComponentActivity)?.finish()
},
)
@@ -304,8 +295,8 @@ private fun AlarmOffButton(
internal fun PreviewAlarmSnoozeTimerScreen() {
OrbitTheme {
AlarmSnoozeTimerScreen(
- stateProvider = { AlarmSnoozeTimerContract.State() },
- eventDispatcher = {},
+ state = AlarmSnoozeTimerContract.State(),
+ processAction = {},
)
}
}
diff --git a/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/snooze/AlarmSnoozeTimerViewModel.kt b/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/snooze/AlarmSnoozeTimerViewModel.kt
index 6076cf8c..05b6c798 100644
--- a/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/snooze/AlarmSnoozeTimerViewModel.kt
+++ b/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/snooze/AlarmSnoozeTimerViewModel.kt
@@ -2,20 +2,21 @@ package com.yapp.alarm.interaction.snooze
import android.app.Application
import androidx.lifecycle.SavedStateHandle
-import androidx.lifecycle.viewModelScope
+import androidx.lifecycle.ViewModel
import com.yapp.alarm.pendingIntent.interaction.createAlarmDismissIntent
-import com.yapp.datastore.UserPreferences
import com.yapp.domain.model.Alarm
-import com.yapp.ui.base.BaseViewModel
+import com.yapp.domain.repository.FortuneRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.firstOrNull
-import kotlinx.coroutines.isActive
-import kotlinx.coroutines.launch
+import org.orbitmvi.orbit.Container
+import org.orbitmvi.orbit.ContainerHost
+import org.orbitmvi.orbit.syntax.simple.intent
+import org.orbitmvi.orbit.syntax.simple.reduce
+import org.orbitmvi.orbit.viewmodel.container
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.ZoneId
-import java.time.format.DateTimeFormatter
import javax.inject.Inject
import kotlin.math.max
@@ -23,67 +24,64 @@ import kotlin.math.max
class AlarmSnoozeTimerViewModel @Inject constructor(
private val app: Application,
savedStateHandle: SavedStateHandle,
- private val userPreferences: UserPreferences,
-) : BaseViewModel(
- AlarmSnoozeTimerContract.State(),
-) {
- private val alarmJson: String? = savedStateHandle.get("alarm")
- private val alarm: Alarm? = alarmJson?.let { Alarm.fromJson(it) }
+ private val fortuneRepository: FortuneRepository,
+) : ViewModel(), ContainerHost {
- init {
+ override val container: Container = container(
+ initialState = AlarmSnoozeTimerContract.State(),
+ ) {
fetchIsFirstMission()
startClock()
}
- private fun fetchIsFirstMission() {
- viewModelScope.launch {
- val fortuneDate = userPreferences.fortuneDateFlow.firstOrNull()
- val todayDate = LocalDate.now().format(DateTimeFormatter.ISO_DATE)
- val isFirstMission = fortuneDate != todayDate
+ private val alarmJson: String? = savedStateHandle.get("alarm")
+ private val alarm: Alarm? = alarmJson?.let { Alarm.fromJson(it) }
- updateState {
- copy(isFirstMission = isFirstMission)
- }
+ fun processAction(action: AlarmSnoozeTimerContract.Action) {
+ when (action) {
+ is AlarmSnoozeTimerContract.Action.Dismiss -> dismiss()
}
}
- private fun startClock() {
- viewModelScope.launch {
- val nowMillis = System.currentTimeMillis()
- val nextSnoozeTimeMillis = alarm?.let { getNextSnoozeAlarmTimeMillis(it.snoozeInterval) } ?: nowMillis
- val remainingMillis = max(0, nextSnoozeTimeMillis - nowMillis)
- val remainingSeconds = (remainingMillis / 1000).toInt()
-
- updateState {
- copy(
- remainingSeconds = remainingSeconds,
- totalSeconds = remainingSeconds,
- alarmTimeStamp = nextSnoozeTimeMillis / 1000,
- initialLoading = true,
- )
- }.join()
+ private fun fetchIsFirstMission() = intent {
+ val fortuneDate = fortuneRepository.fortuneDateEpochFlow.firstOrNull()
+ val todayDate = LocalDate.now().toEpochDay()
+ val isFirstMission = fortuneDate != todayDate
- while (isActive) {
- val currentTime = System.currentTimeMillis() / 1000
- val remaining = max(0, currentState.alarmTimeStamp - currentTime)
+ reduce {
+ state.copy(isFirstMission = isFirstMission)
+ }
+ }
- updateState {
- copy(
- remainingSeconds = remaining.toInt(),
- initialLoading = false,
- )
- }
+ private fun startClock() = intent {
+ val nowMillis = System.currentTimeMillis()
+ val nextSnoozeTimeMillis = alarm?.let { getNextSnoozeAlarmTimeMillis(it.snoozeInterval) } ?: nowMillis
+ val remainingMillis = max(0, nextSnoozeTimeMillis - nowMillis)
+ val remainingSeconds = (remainingMillis / 1000).toInt()
+
+ reduce {
+ state.copy(
+ remainingSeconds = remainingSeconds,
+ totalSeconds = remainingSeconds,
+ alarmTimeStamp = nextSnoozeTimeMillis / 1000,
+ initialLoading = true,
+ )
+ }
- if (remaining.toInt() == 0) break
+ while (true) {
+ val currentTime = System.currentTimeMillis() / 1000
+ val remaining = max(0, state.alarmTimeStamp - currentTime)
- delay(1000L)
+ reduce {
+ state.copy(
+ remainingSeconds = remaining.toInt(),
+ initialLoading = false,
+ )
}
- }
- }
- fun processAction(action: AlarmSnoozeTimerContract.Action) {
- when (action) {
- is AlarmSnoozeTimerContract.Action.Dismiss -> dismiss()
+ if (remaining.toInt() == 0) break
+
+ delay(1000L)
}
}
@@ -92,10 +90,12 @@ class AlarmSnoozeTimerViewModel @Inject constructor(
}
private fun sendAlarmDismissEventToAlarmReceiver() {
- alarm?.id?.let { id ->
+ alarm?.let { alarm ->
val alarmDismissIntent = createAlarmDismissIntent(
context = app,
- notificationId = id,
+ notificationId = alarm.id,
+ missionType = alarm.missionType.value,
+ missionCount = alarm.missionCount,
)
app.sendBroadcast(alarmDismissIntent)
}
diff --git a/feature/fortune/build.gradle.kts b/feature/fortune/build.gradle.kts
index ac291d36..543510e8 100644
--- a/feature/fortune/build.gradle.kts
+++ b/feature/fortune/build.gradle.kts
@@ -12,11 +12,14 @@ dependencies {
implementation(projects.core.ui)
implementation(projects.core.common)
implementation(projects.core.analytics)
- implementation(projects.core.datastore)
+ implementation(projects.core.alarm)
implementation(libs.orbit.core)
implementation(libs.orbit.compose)
implementation(libs.orbit.viewmodel)
implementation(libs.coil.compose)
+ implementation(libs.androidx.work.runtime)
+ testImplementation(libs.androidx.work.testing)
+ androidTestImplementation(libs.androidx.work.testing)
implementation(projects.domain)
implementation(projects.core.media)
}
diff --git a/feature/fortune/src/main/java/com/yapp/fortune/FortuneNavGraph.kt b/feature/fortune/src/main/java/com/yapp/fortune/FortuneNavGraph.kt
index 5f1b5133..0a7b62ce 100644
--- a/feature/fortune/src/main/java/com/yapp/fortune/FortuneNavGraph.kt
+++ b/feature/fortune/src/main/java/com/yapp/fortune/FortuneNavGraph.kt
@@ -5,6 +5,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
+import androidx.navigation.navDeepLink
import androidx.navigation.navOptions
import androidx.navigation.navigation
import com.yapp.common.navigation.OrbitNavigator
@@ -19,7 +20,11 @@ fun NavGraphBuilder.fortuneNavGraph(
snackBarHostState: SnackbarHostState,
) {
navigation(startDestination = FortuneDestination.Fortune) {
- composable { backStackEntry ->
+ composable(
+ deepLinks = listOf(
+ navDeepLink { uriPattern = "orbitapp://fortune" },
+ ),
+ ) { backStackEntry ->
val viewModel = backStackEntry.sharedHiltViewModel(navigator.navController)
val coroutineScope = rememberCoroutineScope()
diff --git a/feature/fortune/src/main/java/com/yapp/fortune/FortuneRewardScreen.kt b/feature/fortune/src/main/java/com/yapp/fortune/FortuneRewardScreen.kt
index 868464e8..778a3726 100644
--- a/feature/fortune/src/main/java/com/yapp/fortune/FortuneRewardScreen.kt
+++ b/feature/fortune/src/main/java/com/yapp/fortune/FortuneRewardScreen.kt
@@ -55,15 +55,15 @@ fun FortuneRewardRoute(
FortuneRewardScreen(
state = state,
- onCloseClick = { viewModel.onAction(FortuneContract.Action.NavigateToHome) },
- onCompleteClick = { viewModel.onAction(FortuneContract.Action.NavigateToHome) },
+ onCloseClick = { viewModel.processAction(FortuneContract.Action.NavigateToHome) },
+ onCompleteClick = { viewModel.processAction(FortuneContract.Action.NavigateToHome) },
onSaveImage = {
analyticsHelper.logEvent(
AnalyticsEvent(
type = "fortune_talisman_save",
),
)
- viewModel.onAction(FortuneContract.Action.SaveImage(it))
+ viewModel.processAction(FortuneContract.Action.SaveImage(it))
},
)
}
diff --git a/feature/fortune/src/main/java/com/yapp/fortune/FortuneScreen.kt b/feature/fortune/src/main/java/com/yapp/fortune/FortuneScreen.kt
index 0b7357bc..1eb97abc 100644
--- a/feature/fortune/src/main/java/com/yapp/fortune/FortuneScreen.kt
+++ b/feature/fortune/src/main/java/com/yapp/fortune/FortuneScreen.kt
@@ -3,11 +3,13 @@ package com.yapp.fortune
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
-import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.runtime.Composable
@@ -15,6 +17,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableLongStateOf
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
@@ -34,6 +37,7 @@ import com.yapp.fortune.component.FortuneTopAppBar
import com.yapp.fortune.component.SlidingIndicator
import com.yapp.fortune.page.FortunePager
import com.yapp.ui.component.lottie.LottieAnimation
+import kotlinx.coroutines.delay
import java.math.BigDecimal
import java.math.RoundingMode
@@ -105,15 +109,15 @@ fun FortuneRoute(
}
if (state.currentStep != pagerState.currentPage) {
- viewModel.onAction(FortuneContract.Action.UpdateStep(pagerState.currentPage))
+ viewModel.processAction(FortuneContract.Action.UpdateStep(pagerState.currentPage))
}
}
FortuneScreen(
state = state,
pagerState = pagerState,
- onNextStep = { viewModel.onAction(FortuneContract.Action.NextStep) },
- onNavigateToHome = { viewModel.onAction(FortuneContract.Action.NavigateToHome) },
+ onNextStep = { viewModel.processAction(FortuneContract.Action.NextStep) },
+ onNavigateToHome = { viewModel.processAction(FortuneContract.Action.NavigateToHome) },
onCloseClick = {
analyticsHelper.logEvent(
AnalyticsEvent(
@@ -123,7 +127,7 @@ fun FortuneRoute(
),
),
)
- viewModel.onAction(FortuneContract.Action.NavigateToHome)
+ viewModel.processAction(FortuneContract.Action.NavigateToHome)
},
)
}
@@ -184,21 +188,49 @@ fun FortuneScreen(
@Composable
fun FortuneLoadingScreen() {
+ var isDelivering by remember { mutableStateOf(false) }
+
+ LaunchedEffect(Unit) {
+ while (true) {
+ delay(2000)
+ isDelivering = !isDelivering
+ }
+ }
+
Box(
modifier = Modifier
.fillMaxSize()
.background(OrbitTheme.colors.gray_900.copy(alpha = 0.7f)),
contentAlignment = Alignment.Center,
) {
- LottieAnimation(
- modifier = Modifier
- .size(70.dp),
- resId = core.designsystem.R.raw.star_loading,
- )
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(6.dp),
+ ) {
+ val imageRes = if (isDelivering) {
+ core.designsystem.R.drawable.ic_fortune_delivering_speech_bubble
+ } else {
+ core.designsystem.R.drawable.ic_fortune_waiting_speech_bubble
+ }
+ Image(
+ painter = painterResource(id = imageRes),
+ contentDescription = null,
+ )
+
+ LottieAnimation(
+ modifier = Modifier
+ .width(375.dp)
+ .height(267.dp),
+ resId = core.designsystem.R.raw.fortune_loading,
+ )
+ }
}
}
@Composable
@Preview
-fun FortuneRoutePreview() {
+private fun FortuneLoadingScreenPreview() {
+ OrbitTheme {
+ FortuneLoadingScreen()
+ }
}
diff --git a/feature/fortune/src/main/java/com/yapp/fortune/FortuneViewModel.kt b/feature/fortune/src/main/java/com/yapp/fortune/FortuneViewModel.kt
index b4890bfb..4a83a561 100644
--- a/feature/fortune/src/main/java/com/yapp/fortune/FortuneViewModel.kt
+++ b/feature/fortune/src/main/java/com/yapp/fortune/FortuneViewModel.kt
@@ -3,108 +3,136 @@ package com.yapp.fortune
import android.app.Application
import android.util.Log
import androidx.annotation.DrawableRes
-import androidx.lifecycle.viewModelScope
-import com.yapp.datastore.UserPreferences
+import androidx.lifecycle.ViewModel
+import com.yapp.domain.model.FortuneCreateStatus
import com.yapp.domain.repository.FortuneRepository
-import com.yapp.domain.repository.ImageRepository
import com.yapp.fortune.page.toFortunePages
import com.yapp.media.decoder.ImageUtils
-import com.yapp.ui.base.BaseViewModel
+import com.yapp.media.storage.ImageSaver
import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
-import kotlinx.coroutines.launch
+import org.orbitmvi.orbit.Container
+import org.orbitmvi.orbit.ContainerHost
import org.orbitmvi.orbit.syntax.simple.intent
import org.orbitmvi.orbit.syntax.simple.postSideEffect
import org.orbitmvi.orbit.syntax.simple.reduce
-import java.time.LocalDate
-import java.time.format.DateTimeFormatter
+import org.orbitmvi.orbit.viewmodel.container
import javax.inject.Inject
@HiltViewModel
class FortuneViewModel @Inject constructor(
private val application: Application,
private val fortuneRepository: FortuneRepository,
- private val imageRepository: ImageRepository,
- private val userPreferences: UserPreferences,
-) : BaseViewModel(
- FortuneContract.State(),
-) {
-
- init {
- viewModelScope.launch {
- val fortuneId = userPreferences.fortuneIdFlow.firstOrNull()
- val firstDismissedAlarmId = userPreferences.firstDismissedAlarmIdFlow.firstOrNull()
- val fortuneDate = userPreferences.fortuneDateFlow.firstOrNull()
- fortuneId?.let { getFortune(it, firstDismissedAlarmId, fortuneDate) }
+ private val imageSaver: ImageSaver,
+) : ViewModel(), ContainerHost {
+
+ override val container: Container = container(
+ initialState = FortuneContract.State(),
+ ) {
+ observeFortune()
+ }
+
+ fun processAction(action: FortuneContract.Action) {
+ when (action) {
+ is FortuneContract.Action.NextStep -> {
+ moveToNextStep()
+ }
+ is FortuneContract.Action.UpdateStep -> {
+ updateStep(action.step)
+ }
+ is FortuneContract.Action.NavigateToHome -> {
+ navigateToHome()
+ }
+ is FortuneContract.Action.SaveImage -> {
+ saveImage(action.resId)
+ }
}
}
- private fun getFortune(fortuneId: Long, firstDismissedAlarmId: Long?, fortuneDate: String?) = intent {
- updateState { copy(isLoading = true) }
+
+ private fun observeFortune() = intent {
+ fortuneRepository.fortuneCreateStatusFlow.collect { status ->
+ when (status) {
+ is FortuneCreateStatus.Creating -> {
+ reduce { state.copy(isLoading = true) }
+ }
+
+ is FortuneCreateStatus.Success -> {
+ fetchAndUpdateFortune(
+ fortuneId = status.fortuneId,
+ isFirstAlarmDismissedToday = fortuneRepository.isFirstAlarmDismissedTodayFlow.first(),
+ )
+ }
+
+ is FortuneCreateStatus.Failure, FortuneCreateStatus.Idle -> {
+ postSideEffect(FortuneContract.SideEffect.NavigateToHome)
+ }
+ }
+ }
+ }
+
+ private fun fetchAndUpdateFortune(
+ fortuneId: Long,
+ isFirstAlarmDismissedToday: Boolean,
+ ) = intent {
+ reduce { state.copy(isLoading = true) }
fortuneRepository.getFortune(fortuneId).onSuccess { fortune ->
- val savedImageId = userPreferences.fortuneImageIdFlow.firstOrNull()
+ val savedImageId = fortuneRepository.fortuneImageIdFlow.firstOrNull()
val imageId = savedImageId ?: getRandomImage()
val formattedTitle = fortune.dailyFortuneTitle.replace(",", ",\n").trim()
- val todayDate = LocalDate.now().format(DateTimeFormatter.ISO_DATE)
- val hasReward = (fortuneDate == todayDate) && (firstDismissedAlarmId != null)
- updateState {
- copy(
+
+ fortuneRepository.markFortuneSeen()
+
+ reduce {
+ state.copy(
isLoading = false,
dailyFortuneTitle = formattedTitle,
dailyFortuneDescription = fortune.dailyFortuneDescription,
avgFortuneScore = fortune.avgFortuneScore,
fortunePages = fortune.toFortunePages(),
fortuneImageId = imageId,
- hasReward = hasReward,
+ hasReward = isFirstAlarmDismissedToday,
)
}
}.onFailure { error ->
Log.e("FortuneViewModel", "์ด์ธ ๋ฐ์ดํฐ ์์ฒญ ์คํจ: ${error.message}")
- updateState { copy(isLoading = false) }
+ reduce { state.copy(isLoading = false) }
}
}
- fun saveFortuneImageIdIfNeeded(imageId: Int) = viewModelScope.launch {
- val savedImageId = userPreferences.fortuneImageIdFlow.firstOrNull()
+ fun saveFortuneImageIdIfNeeded(imageId: Int) = intent {
+ val savedImageId = fortuneRepository.fortuneImageIdFlow.firstOrNull()
if (savedImageId == null || savedImageId != imageId) {
- userPreferences.saveFortuneImageId(imageId)
+ fortuneRepository.saveFortuneImageId(imageId)
}
}
- fun onAction(action: FortuneContract.Action) = intent {
- when (action) {
- is FortuneContract.Action.NextStep -> {
- if (state.hasReward) {
- postSideEffect(FortuneContract.SideEffect.NavigateToFortuneReward)
- } else {
- reduce { state.copy(currentStep = (state.currentStep + 1).coerceAtMost(5)) }
- }
- }
- is FortuneContract.Action.UpdateStep -> {
- reduce { state.copy(currentStep = action.step) }
- }
- is FortuneContract.Action.NavigateToHome -> {
- navigateToHome()
- }
- is FortuneContract.Action.SaveImage -> {
- saveImage(action.resId)
- }
+ private fun moveToNextStep() = intent {
+ if (state.hasReward) {
+ postSideEffect(FortuneContract.SideEffect.NavigateToFortuneReward)
+ } else {
+ reduce { state.copy(currentStep = (state.currentStep + 1).coerceAtMost(5)) }
}
}
- private fun navigateToHome() {
- emitSideEffect(FortuneContract.SideEffect.NavigateToHome)
+ private fun updateStep(step: Int) = intent {
+ reduce { state.copy(currentStep = step) }
+ }
+
+ private fun navigateToHome() = intent {
+ postSideEffect(FortuneContract.SideEffect.NavigateToHome)
}
- private fun saveImage(@DrawableRes resId: Int) = viewModelScope.launch {
+ private fun saveImage(@DrawableRes resId: Int) = intent {
val bitmap = ImageUtils.getBitmapFromResource(application, resId)
val byteArray = ImageUtils.bitmapToByteArray(bitmap)
- val isSuccess = imageRepository.saveImage(byteArray)
+ val isSuccess = imageSaver.saveImage(byteArray, "fortune_${System.currentTimeMillis()}.png")
if (isSuccess) {
- emitSideEffect(
+ postSideEffect(
FortuneContract.SideEffect.ShowSnackBar(
message = "์จ๋ฒ์ ์ ์ฅ๋์์ต๋๋ค.",
iconRes = core.designsystem.R.drawable.ic_check_green,
diff --git a/feature/fortune/src/main/java/com/yapp/fortune/di/SchedulerModule.kt b/feature/fortune/src/main/java/com/yapp/fortune/di/SchedulerModule.kt
new file mode 100644
index 00000000..47fbd0b0
--- /dev/null
+++ b/feature/fortune/src/main/java/com/yapp/fortune/di/SchedulerModule.kt
@@ -0,0 +1,19 @@
+package com.yapp.fortune.di
+
+import com.yapp.alarm.scheduler.PostFortuneTaskScheduler
+import com.yapp.fortune.scheduler.WorkManagerPostFortuneTaskScheduler
+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 SchedulerModule {
+ @Binds
+ @Singleton
+ abstract fun bindsPostFortuneTaskScheduler(
+ postFortuneTaskScheduler: WorkManagerPostFortuneTaskScheduler,
+ ): PostFortuneTaskScheduler
+}
diff --git a/feature/fortune/src/main/java/com/yapp/fortune/page/FortunePageData.kt b/feature/fortune/src/main/java/com/yapp/fortune/page/FortunePageData.kt
index e7070276..4ae64ca9 100644
--- a/feature/fortune/src/main/java/com/yapp/fortune/page/FortunePageData.kt
+++ b/feature/fortune/src/main/java/com/yapp/fortune/page/FortunePageData.kt
@@ -1,6 +1,6 @@
package com.yapp.fortune.page
-import com.yapp.domain.model.fortune.Fortune
+import com.yapp.domain.model.Fortune
data class FortunePageData(
val title: String,
diff --git a/feature/fortune/src/main/java/com/yapp/fortune/page/FortunePager.kt b/feature/fortune/src/main/java/com/yapp/fortune/page/FortunePager.kt
index 08b7cfc5..aa1dc78b 100644
--- a/feature/fortune/src/main/java/com/yapp/fortune/page/FortunePager.kt
+++ b/feature/fortune/src/main/java/com/yapp/fortune/page/FortunePager.kt
@@ -51,11 +51,13 @@ fun FortunePager(
val index = (page - 1).coerceIn(0, state.fortunePages.lastIndex)
FortunePageLayout(state.fortunePages[index])
}
+
5 -> FortuneCompletePage(
hasReward = state.hasReward,
onCompleteClick = onNextStep,
onNavigateToHome = onNavigateToHome,
)
+
else -> {}
}
}
diff --git a/feature/fortune/src/main/java/com/yapp/fortune/scheduler/WorkManagerPostFortuneTaskScheduler.kt b/feature/fortune/src/main/java/com/yapp/fortune/scheduler/WorkManagerPostFortuneTaskScheduler.kt
new file mode 100644
index 00000000..36e49c4f
--- /dev/null
+++ b/feature/fortune/src/main/java/com/yapp/fortune/scheduler/WorkManagerPostFortuneTaskScheduler.kt
@@ -0,0 +1,32 @@
+package com.yapp.fortune.scheduler
+
+import android.content.Context
+import androidx.work.BackoffPolicy
+import androidx.work.Constraints
+import androidx.work.ExistingWorkPolicy
+import androidx.work.NetworkType
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.WorkManager
+import com.yapp.alarm.scheduler.PostFortuneTaskScheduler
+import com.yapp.fortune.worker.PostFortuneWorker
+import dagger.hilt.android.qualifiers.ApplicationContext
+import java.time.LocalDate
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+
+class WorkManagerPostFortuneTaskScheduler @Inject constructor(
+ @ApplicationContext private val context: Context,
+) : PostFortuneTaskScheduler {
+ override fun enqueueOnceForToday() {
+ val name = "post_fortune_${LocalDate.now()}"
+ val constraints = Constraints.Builder()
+ .setRequiredNetworkType(NetworkType.CONNECTED)
+ .build()
+ val req = OneTimeWorkRequestBuilder()
+ .setConstraints(constraints)
+ .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 15, TimeUnit.SECONDS)
+ .build()
+ WorkManager.getInstance(context)
+ .enqueueUniqueWork(name, ExistingWorkPolicy.KEEP, req)
+ }
+}
diff --git a/feature/fortune/src/main/java/com/yapp/fortune/worker/PostFortuneWorker.kt b/feature/fortune/src/main/java/com/yapp/fortune/worker/PostFortuneWorker.kt
new file mode 100644
index 00000000..2dd14f72
--- /dev/null
+++ b/feature/fortune/src/main/java/com/yapp/fortune/worker/PostFortuneWorker.kt
@@ -0,0 +1,63 @@
+package com.yapp.fortune.worker
+
+import android.content.Context
+import androidx.hilt.work.HiltWorker
+import androidx.work.CoroutineWorker
+import androidx.work.WorkerParameters
+import com.yapp.domain.model.FortuneCreateStatus
+import com.yapp.domain.repository.FortuneRepository
+import com.yapp.domain.repository.UserInfoRepository
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.firstOrNull
+
+@HiltWorker
+class PostFortuneWorker @AssistedInject constructor(
+ @Assisted appContext: Context,
+ @Assisted params: WorkerParameters,
+ private val fortuneRepository: FortuneRepository,
+ private val userInfoRepository: UserInfoRepository,
+) : CoroutineWorker(appContext, params) {
+
+ override suspend fun doWork(): Result {
+ when (fortuneRepository.fortuneCreateStatusFlow.first()) {
+ is FortuneCreateStatus.Creating,
+ is FortuneCreateStatus.Success,
+ -> {
+ return Result.success()
+ }
+ FortuneCreateStatus.Failure,
+ FortuneCreateStatus.Idle,
+ -> {
+ val userId = userInfoRepository.userIdFlow.firstOrNull()
+ ?: run {
+ // ์ฌ์ฉ์ ์์ผ๋ฉด ์คํจ ์ํ ํ์ ํ ์คํจ ๋ฐํ
+ fortuneRepository.markFortuneAsFailed()
+ return Result.failure()
+ }
+
+ return try {
+ fortuneRepository.markFortuneAsCreating()
+
+ val result = fortuneRepository.postFortune(userId)
+ result.fold(
+ onSuccess = { fortune ->
+ fortuneRepository.markFortuneAsCreated(fortune.id)
+ fortuneRepository.saveFortuneScore(fortune.avgFortuneScore)
+ Result.success()
+ },
+ onFailure = {
+ fortuneRepository.markFortuneAsFailed()
+ // WM ๋ฐฑ์คํ ๊ท์น์ ๋ฐ๋ผ ์ฌ์๋
+ Result.retry()
+ },
+ )
+ } catch (_: Throwable) {
+ fortuneRepository.markFortuneAsFailed()
+ Result.retry()
+ }
+ }
+ }
+ }
+}
diff --git a/feature/home/build.gradle.kts b/feature/home/build.gradle.kts
index b8a4b12d..0b90300c 100644
--- a/feature/home/build.gradle.kts
+++ b/feature/home/build.gradle.kts
@@ -12,13 +12,12 @@ dependencies {
implementation(projects.core.ui)
implementation(projects.core.common)
implementation(projects.core.analytics)
- implementation(projects.core.alarm)
implementation(projects.core.media)
- implementation(projects.core.datastore)
implementation(projects.domain)
implementation(libs.orbit.core)
implementation(libs.orbit.compose)
implementation(libs.orbit.viewmodel)
implementation(libs.androidx.material.android)
implementation(libs.androidx.annotation)
+ implementation(libs.coil.compose)
}
diff --git a/feature/home/src/main/AndroidManifest.xml b/feature/home/src/main/AndroidManifest.xml
index 8bdb7e14..1c6dcf4e 100644
--- a/feature/home/src/main/AndroidManifest.xml
+++ b/feature/home/src/main/AndroidManifest.xml
@@ -1,4 +1,4 @@
-
+
diff --git a/feature/home/src/main/java/com/yapp/alarm/addedit/AlarmAddEditScreen.kt b/feature/home/src/main/java/com/yapp/alarm/addedit/AlarmAddEditScreen.kt
deleted file mode 100644
index d479e3f6..00000000
--- a/feature/home/src/main/java/com/yapp/alarm/addedit/AlarmAddEditScreen.kt
+++ /dev/null
@@ -1,699 +0,0 @@
-package com.yapp.alarm.addedit
-
-import android.net.Uri
-import androidx.activity.compose.BackHandler
-import androidx.compose.foundation.background
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.interaction.collectIsPressedAsState
-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.foundation.layout.statusBarsPadding
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.ripple.RippleAlpha
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.Icon
-import androidx.compose.material3.LocalRippleConfiguration
-import androidx.compose.material3.RippleConfiguration
-import androidx.compose.material3.SnackbarHostState
-import androidx.compose.material3.SnackbarResult
-import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
-import androidx.compose.material3.rememberModalBottomSheetState
-import androidx.compose.material3.ripple
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalConfiguration
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringResource
-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 com.yapp.alarm.component.AlarmCheckItem
-import com.yapp.alarm.component.AlarmDayButton
-import com.yapp.alarm.component.bottomsheet.AlarmSnoozeBottomSheet
-import com.yapp.alarm.component.bottomsheet.AlarmSoundBottomSheet
-import com.yapp.alarm.getLabelStringRes
-import com.yapp.common.navigation.OrbitNavigator
-import com.yapp.designsystem.theme.OrbitTheme
-import com.yapp.domain.model.AlarmDay
-import com.yapp.domain.model.AlarmSound
-import com.yapp.home.ADD_ALARM_RESULT_KEY
-import com.yapp.home.DELETE_ALARM_RESULT_KEY
-import com.yapp.home.UPDATE_ALARM_RESULT_KEY
-import com.yapp.ui.component.button.OrbitButton
-import com.yapp.ui.component.dialog.OrbitDialog
-import com.yapp.ui.component.lottie.LottieAnimation
-import com.yapp.ui.component.snackbar.showCustomSnackBar
-import com.yapp.ui.component.switch.OrbitSwitch
-import com.yapp.ui.component.timepicker.OrbitPicker
-import feature.home.R
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.launch
-
-@Composable
-fun AlarmAddEditRoute(
- viewModel: AlarmAddEditViewModel = hiltViewModel(),
- navigator: OrbitNavigator,
- snackBarHostState: SnackbarHostState,
-) {
- val state by viewModel.container.stateFlow.collectAsStateWithLifecycle()
- val sideEffect = viewModel.container.sideEffectFlow
-
- val coroutineScope = rememberCoroutineScope()
-
- LaunchedEffect(sideEffect) {
- sideEffect.collectLatest { effect ->
- when (effect) {
- is AlarmAddEditContract.SideEffect.NavigateBack -> {
- navigator.navigateBack()
- }
- is AlarmAddEditContract.SideEffect.SaveAlarm -> {
- navigator.navController.previousBackStackEntry
- ?.savedStateHandle
- ?.set(ADD_ALARM_RESULT_KEY, effect.id)
- navigator.navController.popBackStack()
- }
- is AlarmAddEditContract.SideEffect.UpdateAlarm -> {
- navigator.navController.previousBackStackEntry
- ?.savedStateHandle
- ?.set(UPDATE_ALARM_RESULT_KEY, effect.id)
- navigator.navigateBack()
- }
- is AlarmAddEditContract.SideEffect.DeleteAlarm -> {
- navigator.navController.previousBackStackEntry
- ?.savedStateHandle
- ?.set(DELETE_ALARM_RESULT_KEY, effect.id)
- navigator.navigateBack()
- }
- is AlarmAddEditContract.SideEffect.ShowSnackBar -> {
- val result = showCustomSnackBar(
- scope = coroutineScope,
- snackBarHostState = snackBarHostState,
- message = effect.message,
- actionLabel = effect.label,
- iconRes = effect.iconRes,
- bottomPadding = effect.bottomPadding,
- durationMillis = effect.durationMillis,
- )
-
- when (result) {
- SnackbarResult.ActionPerformed -> effect.onAction()
- SnackbarResult.Dismissed -> effect.onDismiss()
- }
- }
- }
- }
- }
-
- AlarmAddEditScreen(
- stateProvider = { state },
- eventDispatcher = viewModel::processAction,
- )
-}
-
-@Composable
-fun AlarmAddEditScreen(
- stateProvider: () -> AlarmAddEditContract.State,
- eventDispatcher: (AlarmAddEditContract.Action) -> Unit,
-) {
- val state = stateProvider()
-
- if (state.initialLoading) {
- AlarmAddEditLoadingScreen()
- } else {
- AlarmAddEditContent(
- state = state,
- eventDispatcher = eventDispatcher,
- )
- }
-}
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun AlarmAddEditContent(
- state: AlarmAddEditContract.State,
- eventDispatcher: (AlarmAddEditContract.Action) -> Unit,
-) {
- BackHandler {
- eventDispatcher(AlarmAddEditContract.Action.CheckUnsavedChangesBeforeExit)
- }
-
- val snoozeState = state.snoozeState
- val snoozeBottomSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
- val soundBottomSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
- val scope = rememberCoroutineScope()
-
- Column(
- modifier = Modifier.fillMaxSize(),
- horizontalAlignment = Alignment.CenterHorizontally,
- ) {
- AlarmAddEditTopBar(
- mode = state.mode,
- title = state.timeState.alarmMessage,
- onBack = { eventDispatcher(AlarmAddEditContract.Action.CheckUnsavedChangesBeforeExit) },
- onDelete = { eventDispatcher(AlarmAddEditContract.Action.ShowDeleteDialog) },
- )
- Box(
- modifier = Modifier.weight(1f),
- contentAlignment = Alignment.Center,
- ) {
- OrbitPicker(
- initialAmPm = state.timeState.initialAmPm,
- initialHour = state.timeState.initialHour,
- initialMinute = state.timeState.initialMinute,
- ) { amPm, hour, minute ->
- eventDispatcher(AlarmAddEditContract.Action.SetAlarmTime(amPm, hour, minute))
- }
- }
- AlarmAddEditSettingsSection(
- modifier = Modifier.padding(horizontal = 20.dp),
- state = state,
- processAction = eventDispatcher,
- )
- Spacer(modifier = Modifier.height(24.dp))
- OrbitButton(
- label = stringResource(R.string.alarm_add_edit_save),
- onClick = { eventDispatcher(AlarmAddEditContract.Action.SaveAlarm) },
- enabled = true,
- modifier = Modifier
- .padding(
- start = 20.dp,
- end = 20.dp,
- bottom = 12.dp,
- ),
- )
- }
-
- AlarmSnoozeBottomSheet(
- snoozeEnabled = snoozeState.isSnoozeEnabled,
- snoozeIntervalIndex = snoozeState.snoozeIntervalIndex,
- snoozeCountIndex = snoozeState.snoozeCountIndex,
- snoozeIntervals = snoozeState.snoozeIntervals,
- snoozeCounts = snoozeState.snoozeCounts,
- onSnoozeToggle = { eventDispatcher(AlarmAddEditContract.Action.ToggleSnoozeOption) },
- onIntervalSelected = { index ->
- eventDispatcher(
- AlarmAddEditContract.Action.SetSnoozeInterval(
- index,
- ),
- )
- },
- onCountSelected = { index ->
- eventDispatcher(
- AlarmAddEditContract.Action.SetSnoozeRepeatCount(
- index,
- ),
- )
- },
- onComplete = {
- scope.launch {
- snoozeBottomSheetState.hide()
- }.invokeOnCompletion {
- eventDispatcher(AlarmAddEditContract.Action.ToggleBottomSheet(AlarmAddEditContract.BottomSheetType.SnoozeSetting))
- }
- },
- isSheetOpen = state.bottomSheetState == AlarmAddEditContract.BottomSheetType.SnoozeSetting,
- onDismiss = {
- scope.launch {
- snoozeBottomSheetState.hide()
- }.invokeOnCompletion {
- eventDispatcher(AlarmAddEditContract.Action.ToggleBottomSheet(AlarmAddEditContract.BottomSheetType.SnoozeSetting))
- }
- },
- )
-
- AlarmSoundBottomSheet(
- vibrationEnabled = state.soundState.isVibrationEnabled,
- soundEnabled = state.soundState.isSoundEnabled,
- soundVolume = state.soundState.soundVolume,
- soundIndex = state.soundState.soundIndex,
- sounds = state.soundState.sounds,
- onVibrationToggle = { eventDispatcher(AlarmAddEditContract.Action.ToggleVibrationOption) },
- onSoundToggle = { eventDispatcher(AlarmAddEditContract.Action.ToggleSoundOption) },
- onVolumeChanged = { eventDispatcher(AlarmAddEditContract.Action.AdjustSoundVolume(it)) },
- onSoundSelected = { eventDispatcher(AlarmAddEditContract.Action.SelectAlarmSound(it)) },
- onComplete = {
- scope.launch {
- soundBottomSheetState.hide()
- }.invokeOnCompletion {
- eventDispatcher(AlarmAddEditContract.Action.ToggleBottomSheet(AlarmAddEditContract.BottomSheetType.SoundSetting))
- }
- },
- isSheetOpen = state.bottomSheetState == AlarmAddEditContract.BottomSheetType.SoundSetting,
- onDismiss = {
- scope.launch {
- soundBottomSheetState.hide()
- }.invokeOnCompletion {
- eventDispatcher(AlarmAddEditContract.Action.ToggleBottomSheet(AlarmAddEditContract.BottomSheetType.SoundSetting))
- }
- },
- )
-
- if (state.isDeleteDialogVisible) {
- OrbitDialog(
- title = stringResource(id = R.string.alarm_delete_dialog_title),
- message = stringResource(id = R.string.alarm_delete_dialog_message),
- confirmText = stringResource(id = R.string.alarm_delete_dialog_btn_delete),
- cancelText = stringResource(id = R.string.alarm_delete_dialog_btn_cancel),
- onConfirm = {
- eventDispatcher(AlarmAddEditContract.Action.DeleteAlarm)
- },
- onCancel = {
- eventDispatcher(AlarmAddEditContract.Action.HideDeleteDialog)
- },
- )
- }
-
- if (state.isUnsavedChangesDialogVisible) {
- OrbitDialog(
- title = stringResource(id = R.string.alarm_unsaved_changes_dialog_title),
- message = stringResource(id = R.string.alarm_unsaved_changes_dialog_message),
- confirmText = stringResource(id = R.string.alarm_unsaved_changes_dialog_btn_discard),
- cancelText = stringResource(id = R.string.alarm_unsaved_changes_dialog_btn_cancel),
- onConfirm = {
- eventDispatcher(AlarmAddEditContract.Action.NavigateBack)
- },
- onCancel = {
- eventDispatcher(AlarmAddEditContract.Action.HideUnsavedChangesDialog)
- },
- )
- }
-}
-
-@Composable
-private fun AlarmAddEditLoadingScreen() {
- Box(
- modifier = Modifier.fillMaxSize(),
- contentAlignment = Alignment.Center,
- ) {
- LottieAnimation(
- modifier = Modifier
- .size(70.dp)
- .align(Alignment.Center),
- resId = core.designsystem.R.raw.star_loading,
- )
- }
-}
-
-@Composable
-private fun AlarmAddEditTopBar(
- mode: AlarmAddEditContract.EditMode = AlarmAddEditContract.EditMode.ADD,
- title: String,
- onBack: () -> Unit,
- onDelete: () -> Unit,
-) {
- Box(
- modifier = Modifier
- .fillMaxWidth()
- .statusBarsPadding()
- .height(56.dp),
- contentAlignment = Alignment.Center,
- ) {
- Icon(
- painter = painterResource(id = core.designsystem.R.drawable.ic_back),
- contentDescription = "Back",
- tint = OrbitTheme.colors.white,
- modifier = Modifier
- .clickable(onClick = onBack)
- .padding(start = 20.dp)
- .align(Alignment.CenterStart),
- )
-
- Text(
- title,
- style = OrbitTheme.typography.body1SemiBold,
- color = OrbitTheme.colors.white,
- )
-
- if (mode == AlarmAddEditContract.EditMode.EDIT) {
- DeleteAlarmButton(
- modifier = Modifier
- .align(Alignment.CenterEnd)
- .padding(end = 20.dp),
- ) {
- onDelete()
- }
- }
- }
-}
-
-@Composable
-private fun DeleteAlarmButton(
- modifier: Modifier = Modifier,
- onDelete: () -> Unit,
-) {
- val interactionSource = remember { MutableInteractionSource() }
- val isPressed = interactionSource.collectIsPressedAsState().value
-
- Surface(
- onClick = onDelete,
- modifier = modifier,
- shape = RoundedCornerShape(8.dp),
- interactionSource = interactionSource,
- color = if (isPressed) OrbitTheme.colors.gray_800 else Color.Transparent,
- ) {
- Text(
- text = stringResource(id = R.string.alarm_add_edit_delete),
- style = OrbitTheme.typography.body1Medium,
- color = OrbitTheme.colors.alert,
- modifier = Modifier
- .padding(
- horizontal = 8.dp,
- vertical = 4.dp,
- ),
- )
- }
-}
-
-@Composable
-private fun AlarmAddEditSettingsSection(
- modifier: Modifier = Modifier,
- state: AlarmAddEditContract.State,
- processAction: (AlarmAddEditContract.Action) -> Unit,
-) {
- Column(
- modifier = modifier
- .fillMaxWidth()
- .background(
- color = OrbitTheme.colors.gray_800,
- shape = RoundedCornerShape(12.dp),
- )
- .clip(
- shape = RoundedCornerShape(12.dp),
- ),
- ) {
- AlarmAddEditSelectDaysSection(
- state = state.daySelectionState,
- processAction = processAction,
- )
- AlarmAddEditDisableHolidaySwitch(
- state = state.holidayState,
- processAction = processAction,
- )
- Spacer(
- modifier = Modifier
- .fillMaxWidth()
- .height(1.dp)
- .padding(horizontal = 20.dp)
- .background(OrbitTheme.colors.gray_700),
- )
-
- AlarmAddEditSettingItem(
- label = stringResource(id = R.string.alarm_add_edit_alarm_snooze),
- description = if (state.snoozeState.isSnoozeEnabled) {
- stringResource(
- id = R.string.alarm_add_edit_alarm_selected_option,
- state.snoozeState.snoozeIntervals[state.snoozeState.snoozeIntervalIndex],
- state.snoozeState.snoozeCounts[state.snoozeState.snoozeCountIndex],
- )
- } else {
- stringResource(id = R.string.alarm_add_edit_alarm_selected_option_none)
- },
- onClick = {
- processAction(
- AlarmAddEditContract.Action.ToggleBottomSheet(
- AlarmAddEditContract.BottomSheetType.SnoozeSetting,
- ),
- )
- },
- )
- Spacer(
- modifier = Modifier
- .fillMaxWidth()
- .height(1.dp)
- .padding(horizontal = 20.dp)
- .background(OrbitTheme.colors.gray_700),
- )
- AlarmAddEditSettingItem(
- label = stringResource(id = R.string.alarm_add_edit_sound),
- description = when {
- state.soundState.isSoundEnabled && state.soundState.isVibrationEnabled -> {
- "${stringResource(id = R.string.alarm_add_edit_vibration)}, ${
- state.soundState.sounds.getOrElse(state.soundState.soundIndex) {
- AlarmSound("", Uri.EMPTY)
- }.title
- }"
- }
-
- state.soundState.isSoundEnabled -> state.soundState.sounds.getOrElse(state.soundState.soundIndex) {
- AlarmSound(
- "",
- Uri.EMPTY,
- )
- }.title
-
- state.soundState.isVibrationEnabled -> stringResource(id = R.string.alarm_add_edit_vibration)
- else -> stringResource(id = R.string.alarm_add_edit_alarm_selected_option_none)
- },
- onClick = {
- processAction(
- AlarmAddEditContract.Action.ToggleBottomSheet(
- AlarmAddEditContract.BottomSheetType.SoundSetting,
- ),
- )
- },
- )
- }
-}
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-private fun AlarmAddEditSettingItem(
- label: String,
- description: String,
- onClick: () -> Unit,
-) {
- val interactionSource = remember { MutableInteractionSource() }
-
- CompositionLocalProvider(
- LocalRippleConfiguration provides RippleConfiguration(
- rippleAlpha = RippleAlpha(
- pressedAlpha = 1f,
- focusedAlpha = 1f,
- hoveredAlpha = 1f,
- draggedAlpha = 1f,
- ),
- ),
- ) {
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .clickable(
- interactionSource = interactionSource,
- indication = ripple(
- color = OrbitTheme.colors.gray_700,
- ),
- ) {
- onClick()
- }
- .padding(
- horizontal = 20.dp,
- vertical = 14.dp,
- ),
- verticalAlignment = Alignment.CenterVertically,
- ) {
- Text(
- label,
- modifier = Modifier.width(80.dp),
- style = OrbitTheme.typography.body1SemiBold,
- color = OrbitTheme.colors.white,
- )
- Text(
- description,
- modifier = Modifier.weight(1f),
- style = OrbitTheme.typography.body2Regular,
- color = OrbitTheme.colors.gray_50,
- maxLines = 1,
- overflow = TextOverflow.Ellipsis,
- textAlign = TextAlign.End,
- )
- Icon(
- painter = painterResource(id = core.designsystem.R.drawable.ic_arrow_right),
- contentDescription = "Arrow",
- tint = OrbitTheme.colors.gray_300,
- )
- }
- }
-}
-
-@Composable
-private fun AlarmAddEditSelectDaysSection(
- state: AlarmAddEditContract.AlarmDaySelectionState,
- processAction: (AlarmAddEditContract.Action) -> Unit,
-) {
- val configuration = LocalConfiguration.current
- val screenWidthDp = configuration.screenWidthDp.dp
-
- Column(
- modifier = Modifier.padding(horizontal = 20.dp, vertical = 16.dp),
- ) {
- Row(
- modifier = Modifier.fillMaxWidth(),
- verticalAlignment = Alignment.CenterVertically,
- ) {
- Text(
- text = stringResource(id = R.string.alarm_add_edit_repeat),
- style = OrbitTheme.typography.body1SemiBold,
- color = OrbitTheme.colors.white,
- )
-
- Spacer(modifier = Modifier.weight(1f))
-
- AlarmCheckItem(
- label = stringResource(id = R.string.alarm_add_edit_weekdays),
- isPressed = state.isWeekdaysChecked,
- onClick = {
- processAction(AlarmAddEditContract.Action.ToggleWeekdaysSelection)
- },
- )
- Spacer(modifier = Modifier.width(2.dp))
- AlarmCheckItem(
- label = stringResource(id = R.string.alarm_add_edit_weekends),
- isPressed = state.isWeekendsChecked,
- onClick = {
- processAction(AlarmAddEditContract.Action.ToggleWeekendsSelection)
- },
- )
- }
-
- Spacer(modifier = Modifier.height(12.dp))
-
- Row(
- modifier = Modifier.fillMaxWidth(),
- horizontalArrangement = Arrangement.SpaceBetween,
- ) {
- state.days.forEach { day ->
- AlarmDayButton(
- modifier = Modifier.size(
- if (screenWidthDp > 360.dp) 36.dp else 34.dp,
- ),
- label = stringResource(id = day.getLabelStringRes()),
- isPressed = state.selectedDays.contains(day),
- onClick = {
- processAction(AlarmAddEditContract.Action.ToggleSpecificDaySelection(day))
- },
- )
- }
- }
- }
-}
-
-@Composable
-private fun AlarmAddEditDisableHolidaySwitch(
- state: AlarmAddEditContract.AlarmHolidayState,
- processAction: (AlarmAddEditContract.Action) -> Unit,
-) {
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .padding(
- start = 20.dp,
- end = 20.dp,
- bottom = 16.dp,
- ),
- verticalAlignment = Alignment.CenterVertically,
- ) {
- Icon(
- painter = painterResource(id = core.designsystem.R.drawable.ic_holiday),
- contentDescription = "Holiday",
- tint = OrbitTheme.colors.gray_400,
- modifier = Modifier.padding(end = 4.dp),
- )
- Text(
- text = stringResource(id = R.string.alarm_add_edit_disable_holiday),
- style = OrbitTheme.typography.label1Medium,
- color = OrbitTheme.colors.gray_400,
- )
-
- Spacer(modifier = Modifier.weight(1f))
-
- OrbitSwitch(
- isChecked = state.isDisableHolidayChecked,
- isEnabled = state.isDisableHolidayEnabled,
- onClick = {
- processAction(AlarmAddEditContract.Action.ToggleHolidaySkipOption)
- },
- )
- }
-}
-
-@Preview
-@Composable
-fun AlarmAddEditSettingsSectionPreview() {
- AlarmAddEditSettingsSection(
- state = AlarmAddEditContract.State(
- timeState = AlarmAddEditContract.AlarmTimeState(
- currentAmPm = "AM",
- currentHour = 9,
- currentMinute = 30,
- ),
- daySelectionState = AlarmAddEditContract.AlarmDaySelectionState(
- isWeekdaysChecked = true,
- isWeekendsChecked = false,
- selectedDays = setOf(AlarmDay.MON, AlarmDay.TUE),
- days = AlarmDay.entries.toSet(),
- ),
- holidayState = AlarmAddEditContract.AlarmHolidayState(
- isDisableHolidayChecked = false,
- ),
- ),
- processAction = { },
- )
-}
-
-@Preview
-@Composable
-fun AlarmAddEditSettingItemPreview() {
- AlarmAddEditSettingItem(
- label = "์๋ ๋ฏธ๋ฃจ๊ธฐ",
- description = "5๋ถ, ๋ฌดํ",
- onClick = { },
- )
-}
-
-@Preview
-@Composable
-fun AlarmAddEditScreenPreview() {
- AlarmAddEditScreen(
- stateProvider = {
- AlarmAddEditContract.State(
- timeState = AlarmAddEditContract.AlarmTimeState(
- currentAmPm = "AM",
- currentHour = 9,
- currentMinute = 30,
- ),
- daySelectionState = AlarmAddEditContract.AlarmDaySelectionState(
- isWeekdaysChecked = true,
- isWeekendsChecked = false,
- selectedDays = setOf(AlarmDay.MON, AlarmDay.TUE),
- days = AlarmDay.entries.toSet(),
- ),
- holidayState = AlarmAddEditContract.AlarmHolidayState(
- isDisableHolidayChecked = false,
- ),
- )
- },
- eventDispatcher = { },
- )
-}
diff --git a/feature/home/src/main/java/com/yapp/alarm/addedit/AlarmAddEditViewModel.kt b/feature/home/src/main/java/com/yapp/alarm/addedit/AlarmAddEditViewModel.kt
deleted file mode 100644
index 08713ebd..00000000
--- a/feature/home/src/main/java/com/yapp/alarm/addedit/AlarmAddEditViewModel.kt
+++ /dev/null
@@ -1,554 +0,0 @@
-package com.yapp.alarm.addedit
-
-import android.util.Log
-import androidx.compose.ui.unit.dp
-import androidx.lifecycle.SavedStateHandle
-import androidx.lifecycle.viewModelScope
-import com.yapp.alarm.AlarmHelper
-import com.yapp.analytics.AnalyticsEvent
-import com.yapp.analytics.AnalyticsHelper
-import com.yapp.common.util.ResourceProvider
-import com.yapp.domain.model.Alarm
-import com.yapp.domain.model.AlarmDay
-import com.yapp.domain.model.AlarmSound
-import com.yapp.domain.model.copyFrom
-import com.yapp.domain.model.toAlarmDayNames
-import com.yapp.domain.model.toAlarmDays
-import com.yapp.domain.model.toDayOfWeek
-import com.yapp.domain.usecase.AlarmUseCase
-import com.yapp.media.haptic.HapticFeedbackManager
-import com.yapp.media.haptic.HapticType
-import com.yapp.ui.base.BaseViewModel
-import dagger.hilt.android.lifecycle.HiltViewModel
-import feature.home.R
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.launch
-import java.time.LocalTime
-import javax.inject.Inject
-
-@HiltViewModel
-class AlarmAddEditViewModel @Inject constructor(
- private val analyticsHelper: AnalyticsHelper,
- private val alarmUseCase: AlarmUseCase,
- private val resourceProvider: ResourceProvider,
- private val hapticFeedbackManager: HapticFeedbackManager,
- private val alarmHelper: AlarmHelper,
- savedStateHandle: SavedStateHandle,
-) : BaseViewModel(
- initialState = AlarmAddEditContract.State(),
-) {
- private val alarmId: Long = savedStateHandle.get("alarmId") ?: -1
-
- init {
- updateState { copy(mode = if (alarmId == -1L) AlarmAddEditContract.EditMode.ADD else AlarmAddEditContract.EditMode.EDIT) }
- initializeAlarmScreen()
- }
-
- private fun initializeAlarmScreen() = viewModelScope.launch {
- alarmUseCase.getAlarmSounds().onSuccess { sounds ->
- if (alarmId == -1L) {
- setupNewAlarmScreen(sounds)
- } else {
- loadExistingAlarm(sounds)
- }
- }.onFailure {
- Log.e("AlarmAddEditViewModel", "Failed to load alarm sounds", it)
- }
- }
-
- private fun setupNewAlarmScreen(sounds: List) {
- val defaultSoundIndex = sounds.indexOfFirst { it.title == "Homecoming" }.takeIf { it >= 0 } ?: 0
- val defaultSound = sounds[defaultSoundIndex]
-
- alarmUseCase.initializeSoundPlayer(defaultSound.uri)
-
- val now = LocalTime.now()
- val initialAmPm = if (now.hour < 12) "์ค์ " else "์คํ"
- val initialHour = if (now.hour == 0 || now.hour == 12) 12 else now.hour % 12
- val initialMinute = now.minute
-
- updateState {
- copy(
- initialLoading = false,
- timeState = timeState.copy(
- initialAmPm = initialAmPm,
- initialHour = "$initialHour",
- initialMinute = initialMinute.toString().padStart(2, '0'),
- currentAmPm = initialAmPm,
- currentHour = initialHour,
- currentMinute = initialMinute,
- alarmMessage = getAlarmMessage(initialAmPm, initialHour, initialMinute, emptySet()),
- ),
- soundState = soundState.copy(sounds = sounds, soundIndex = defaultSoundIndex),
- )
- }
- }
-
- private suspend fun loadExistingAlarm(sounds: List) {
- alarmUseCase.getAlarm(alarmId).onSuccess { alarm ->
- val repeatDays = alarm.repeatDays.toAlarmDays()
- val isAM = alarm.isAm
- val hour = alarm.hour
- val selectedSoundIndex = sounds.indexOfFirst { it.uri.toString() == alarm.soundUri }
- val selectedSound = sounds.getOrNull(selectedSoundIndex) ?: sounds.first()
-
- alarmUseCase.initializeSoundPlayer(selectedSound.uri)
-
- updateState {
- copy(
- initialLoading = false,
- timeState = timeState.copy(
- initialAmPm = if (isAM) "์ค์ " else "์คํ",
- initialHour = "$hour",
- initialMinute = alarm.minute.toString().padStart(2, '0'),
- currentAmPm = if (isAM) "์ค์ " else "์คํ",
- currentHour = hour,
- currentMinute = alarm.minute,
- alarmMessage = getAlarmMessage(if (isAM) "์ค์ " else "์คํ", hour, alarm.minute, repeatDays),
- ),
- daySelectionState = setupDaySelectionState(repeatDays),
- holidayState = holidayState.copy(
- isDisableHolidayEnabled = repeatDays.isNotEmpty(),
- isDisableHolidayChecked = alarm.isHolidayAlarmOff,
- ),
- snoozeState = setupSnoozeState(alarm),
- soundState = soundState.copy(
- isVibrationEnabled = alarm.isVibrationEnabled,
- isSoundEnabled = alarm.isSoundEnabled,
- soundVolume = alarm.soundVolume,
- sounds = sounds,
- soundIndex = selectedSoundIndex,
- ),
- )
- }
- }
- }
-
- private fun setupDaySelectionState(repeatDays: Set) = currentState.daySelectionState.copy(
- selectedDays = repeatDays,
- isWeekdaysChecked = repeatDays.containsAll(setOf(AlarmDay.MON, AlarmDay.TUE, AlarmDay.WED, AlarmDay.THU, AlarmDay.FRI)),
- isWeekendsChecked = repeatDays.containsAll(setOf(AlarmDay.SAT, AlarmDay.SUN)),
- )
-
- private fun setupSnoozeState(alarm: Alarm) = currentState.snoozeState.copy(
- isSnoozeEnabled = alarm.isSnoozeEnabled,
- snoozeIntervalIndex = findSnoozeIndex(alarm.snoozeInterval, currentState.snoozeState.snoozeIntervals),
- snoozeCountIndex = findSnoozeIndex(alarm.snoozeCount, currentState.snoozeState.snoozeCounts),
- )
-
- private fun findSnoozeIndex(value: Int, list: List): Int {
- return list.indexOfFirst {
- it == "๋ฌดํ" && value == -1 || it.filter { char -> char.isDigit() }.toIntOrNull() == value
- }.takeIf { it >= 0 } ?: 0
- }
-
- override fun onCleared() {
- super.onCleared()
- alarmUseCase.releaseSoundPlayer()
- }
-
- fun processAction(action: AlarmAddEditContract.Action) {
- when (action) {
- is AlarmAddEditContract.Action.CheckUnsavedChangesBeforeExit -> checkUnsavedChangesBeforeExit()
- is AlarmAddEditContract.Action.NavigateBack -> navigateBack()
- is AlarmAddEditContract.Action.SaveAlarm -> saveAlarm()
- is AlarmAddEditContract.Action.ShowDeleteDialog -> showDeleteDialog()
- is AlarmAddEditContract.Action.HideDeleteDialog -> hideDeleteDialog()
- is AlarmAddEditContract.Action.ShowUnsavedChangesDialog -> showUnsavedChangesDialog()
- is AlarmAddEditContract.Action.HideUnsavedChangesDialog -> hideUnsavedChangesDialog()
- is AlarmAddEditContract.Action.DeleteAlarm -> deleteAlarm()
- is AlarmAddEditContract.Action.SetAlarmTime -> setAlarmTime(action.amPm, action.hour, action.minute)
- is AlarmAddEditContract.Action.ToggleWeekdaysSelection -> toggleWeekdaysSelection()
- is AlarmAddEditContract.Action.ToggleWeekendsSelection -> toggleWeekendsSelection()
- is AlarmAddEditContract.Action.ToggleSpecificDaySelection -> toggleSpecificDaySelection(action.day)
- is AlarmAddEditContract.Action.ToggleHolidaySkipOption -> toggleHolidaySkipOption()
- is AlarmAddEditContract.Action.ToggleSnoozeOption -> toggleSnoozeOption()
- is AlarmAddEditContract.Action.SetSnoozeInterval -> setSnoozeInterval(action.index)
- is AlarmAddEditContract.Action.SetSnoozeRepeatCount -> setSnoozeRepeatCount(action.index)
- is AlarmAddEditContract.Action.ToggleVibrationOption -> toggleVibrationOption()
- is AlarmAddEditContract.Action.ToggleSoundOption -> toggleSoundOption()
- is AlarmAddEditContract.Action.AdjustSoundVolume -> adjustSoundVolume(action.volume)
- is AlarmAddEditContract.Action.SelectAlarmSound -> selectAlarmSound(action.index)
- is AlarmAddEditContract.Action.ToggleBottomSheet -> toggleBottomSheet(action.sheetType)
- }
- }
-
- private fun checkUnsavedChangesBeforeExit() {
- if (currentState.mode == AlarmAddEditContract.EditMode.ADD) {
- navigateBack()
- } else {
- val updatedAlarm = currentState.toAlarm()
- viewModelScope.launch {
- alarmUseCase.getAlarm(alarmId).onSuccess { existingAlarm ->
- if (updatedAlarm.copy(id = alarmId) != existingAlarm) {
- showUnsavedChangesDialog()
- } else {
- emitSideEffect(AlarmAddEditContract.SideEffect.NavigateBack)
- }
- }
- }
- }
- }
-
- private fun navigateBack() {
- emitSideEffect(AlarmAddEditContract.SideEffect.NavigateBack)
- }
-
- private fun saveAlarm() {
- val newAlarm = currentState.toAlarm()
-
- viewModelScope.launch {
- when (currentState.mode) {
- AlarmAddEditContract.EditMode.EDIT -> updateExistingAlarm(newAlarm)
- AlarmAddEditContract.EditMode.ADD -> checkAndCreateAlarm(newAlarm)
- }
- }
- }
-
- private suspend fun updateExistingAlarm(alarm: Alarm) {
- val updatedAlarm = alarm.copy(id = alarmId)
-
- alarmUseCase.getAlarm(alarmId).onSuccess { oldAlarm ->
- alarmHelper.unScheduleAlarm(oldAlarm)
- }
-
- alarmUseCase.updateAlarm(updatedAlarm)
- .onSuccess {
- alarmHelper.scheduleAlarm(updatedAlarm)
- emitSideEffect(AlarmAddEditContract.SideEffect.UpdateAlarm(it.id))
- }
- .onFailure {
- Log.e("AlarmAddEditViewModel", "Failed to update alarm", it)
- }
- }
-
- private suspend fun checkAndCreateAlarm(newAlarm: Alarm) {
- val timeMatchedAlarms = alarmUseCase.getAlarmsByTime(newAlarm.hour, newAlarm.minute, newAlarm.isAm)
- .first()
-
- when {
- timeMatchedAlarms.any { it.copy(id = 0) == newAlarm.copy(id = 0) } -> {
- showAlarmAlreadySetWarning()
- }
-
- timeMatchedAlarms.isNotEmpty() -> {
- val existingAlarm = timeMatchedAlarms.first()
- val updatedAlarm = existingAlarm.copyFrom(newAlarm).copy(id = existingAlarm.id)
- updateExistingAlarm(updatedAlarm)
- }
-
- else -> {
- createNewAlarm(newAlarm)
- }
- }
- }
-
- private fun showAlarmAlreadySetWarning() {
- emitSideEffect(
- AlarmAddEditContract.SideEffect.ShowSnackBar(
- message = resourceProvider.getString(R.string.alarm_already_set),
- iconRes = resourceProvider.getDrawable(core.designsystem.R.drawable.ic_alert),
- bottomPadding = 78.dp,
- onDismiss = { },
- onAction = { },
- ),
- )
- }
-
- private suspend fun createNewAlarm(alarm: Alarm) {
- alarmUseCase.insertAlarm(alarm)
- .onSuccess {
- analyticsHelper.logEvent(
- AnalyticsEvent(
- type = "alarm_create",
- properties = mapOf(
- AnalyticsEvent.AlarmPropertiesKeys.ALARM_ID to "${it.id}",
- AnalyticsEvent.AlarmPropertiesKeys.REPEAT_DAYS to it.repeatDays.toAlarmDayNames(),
- AnalyticsEvent.AlarmPropertiesKeys.SNOOZE_OPTION to listOf(it.snoozeInterval, it.snoozeCount),
- ),
- ),
- )
- alarmHelper.scheduleAlarm(it)
- emitSideEffect(AlarmAddEditContract.SideEffect.SaveAlarm(it.id))
- }
- .onFailure {
- Log.e("AlarmAddEditViewModel", "Failed to insert alarm", it)
- }
- }
-
- private fun setAlarmTime(amPm: String, hour: Int, minute: Int) {
- val newTimeState = currentState.timeState.copy(
- currentAmPm = amPm,
- currentHour = hour,
- currentMinute = minute,
- alarmMessage = getAlarmMessage(amPm, hour, minute, currentState.daySelectionState.selectedDays),
- )
-
- hapticFeedbackManager.performHapticFeedback(HapticType.LIGHT_TICK)
-
- updateState {
- copy(timeState = newTimeState)
- }
- }
-
- private fun showDeleteDialog() {
- updateState { copy(isDeleteDialogVisible = true) }
- }
-
- private fun hideDeleteDialog() {
- updateState { copy(isDeleteDialogVisible = false) }
- }
-
- private fun showUnsavedChangesDialog() {
- updateState { copy(isUnsavedChangesDialogVisible = true) }
- }
-
- private fun hideUnsavedChangesDialog() {
- updateState { copy(isUnsavedChangesDialogVisible = false) }
- }
-
- private fun deleteAlarm() {
- emitSideEffect(AlarmAddEditContract.SideEffect.DeleteAlarm(alarmId))
- }
-
- private fun toggleWeekdaysSelection() {
- val weekdays = setOf(AlarmDay.MON, AlarmDay.TUE, AlarmDay.WED, AlarmDay.THU, AlarmDay.FRI)
- val isChecked = !currentState.daySelectionState.isWeekdaysChecked
- val updatedDays = if (isChecked) {
- currentState.daySelectionState.selectedDays + weekdays
- } else {
- currentState.daySelectionState.selectedDays - weekdays
- }
- val newDayState = currentState.daySelectionState.copy(
- isWeekdaysChecked = isChecked,
- selectedDays = updatedDays,
- )
- updateState {
- copy(
- timeState = timeState.copy(
- alarmMessage = getAlarmMessage(timeState.currentAmPm, timeState.currentHour, timeState.currentMinute, newDayState.selectedDays),
- ),
- daySelectionState = newDayState,
- holidayState = holidayState.copy(
- isDisableHolidayEnabled = newDayState.selectedDays.isNotEmpty(),
- isDisableHolidayChecked = if (newDayState.selectedDays.isEmpty()) false else holidayState.isDisableHolidayChecked,
- ),
- )
- }
- }
-
- private fun toggleWeekendsSelection() {
- val weekends = setOf(AlarmDay.SAT, AlarmDay.SUN)
- val isChecked = !currentState.daySelectionState.isWeekendsChecked
- val updatedDays = if (isChecked) {
- currentState.daySelectionState.selectedDays + weekends
- } else {
- currentState.daySelectionState.selectedDays - weekends
- }
- val newDayState = currentState.daySelectionState.copy(
- isWeekendsChecked = isChecked,
- selectedDays = updatedDays,
- )
- updateState {
- copy(
- timeState = timeState.copy(
- alarmMessage = getAlarmMessage(timeState.currentAmPm, timeState.currentHour, timeState.currentMinute, newDayState.selectedDays),
- ),
- daySelectionState = newDayState,
- holidayState = holidayState.copy(
- isDisableHolidayEnabled = newDayState.selectedDays.isNotEmpty(),
- isDisableHolidayChecked = if (newDayState.selectedDays.isEmpty()) false else holidayState.isDisableHolidayChecked,
- ),
- )
- }
- }
-
- private fun toggleSpecificDaySelection(day: AlarmDay) {
- val updatedDays = if (day in currentState.daySelectionState.selectedDays) {
- currentState.daySelectionState.selectedDays - day
- } else {
- currentState.daySelectionState.selectedDays + day
- }
- val weekdays = setOf(AlarmDay.MON, AlarmDay.TUE, AlarmDay.WED, AlarmDay.THU, AlarmDay.FRI)
- val weekends = setOf(AlarmDay.SAT, AlarmDay.SUN)
-
- val newDayState = currentState.daySelectionState.copy(
- selectedDays = updatedDays,
- isWeekdaysChecked = updatedDays.containsAll(weekdays),
- isWeekendsChecked = updatedDays.containsAll(weekends),
- )
- updateState {
- copy(
- timeState = timeState.copy(
- alarmMessage = getAlarmMessage(timeState.currentAmPm, timeState.currentHour, timeState.currentMinute, newDayState.selectedDays),
- ),
- daySelectionState = newDayState,
- holidayState = holidayState.copy(
- isDisableHolidayEnabled = newDayState.selectedDays.isNotEmpty(),
- isDisableHolidayChecked = if (newDayState.selectedDays.isEmpty()) false else holidayState.isDisableHolidayChecked,
- ),
- )
- }
- }
-
- private fun toggleHolidaySkipOption() {
- val newHolidayState = currentState.holidayState.copy(
- isDisableHolidayChecked = !currentState.holidayState.isDisableHolidayChecked,
- )
-
- updateState {
- copy(holidayState = newHolidayState)
- }
-
- if (newHolidayState.isDisableHolidayChecked) {
- emitSideEffect(
- AlarmAddEditContract.SideEffect.ShowSnackBar(
- message = resourceProvider.getString(R.string.alarm_disabled_warning),
- label = resourceProvider.getString(R.string.alarm_delete_dialog_btn_cancel),
- iconRes = resourceProvider.getDrawable(core.designsystem.R.drawable.ic_check_green),
- bottomPadding = 78.dp,
- onDismiss = { },
- onAction = {
- updateState {
- copy(holidayState = holidayState.copy(isDisableHolidayChecked = false))
- }
- },
- ),
- )
- }
- }
-
- private fun toggleSnoozeOption() {
- val newSnoozeState = currentState.snoozeState.copy(
- isSnoozeEnabled = !currentState.snoozeState.isSnoozeEnabled,
- )
- updateState {
- copy(snoozeState = newSnoozeState)
- }
- }
-
- private fun setSnoozeInterval(index: Int) {
- val newSnoozeState = currentState.snoozeState.copy(snoozeIntervalIndex = index)
- updateState {
- copy(snoozeState = newSnoozeState)
- }
- }
-
- private fun setSnoozeRepeatCount(index: Int) {
- val newSnoozeState = currentState.snoozeState.copy(snoozeCountIndex = index)
- updateState {
- copy(snoozeState = newSnoozeState)
- }
- }
-
- private fun toggleVibrationOption() {
- val newSoundState = currentState.soundState.copy(isVibrationEnabled = !currentState.soundState.isVibrationEnabled)
-
- if (newSoundState.isVibrationEnabled) {
- hapticFeedbackManager.performHapticFeedback(HapticType.SUCCESS)
- }
- updateState {
- copy(soundState = newSoundState)
- }
- }
-
- private fun toggleSoundOption() {
- val newSoundState = currentState.soundState.copy(isSoundEnabled = !currentState.soundState.isSoundEnabled)
- if (!newSoundState.isSoundEnabled) {
- alarmUseCase.stopAlarmSound()
- }
- updateState {
- copy(soundState = newSoundState)
- }
- }
-
- private fun adjustSoundVolume(volume: Int) {
- val newSoundState = currentState.soundState.copy(soundVolume = volume)
- alarmUseCase.updateAlarmVolume(volume)
- updateState {
- copy(soundState = newSoundState)
- }
- }
-
- private fun selectAlarmSound(index: Int) {
- val newSoundState = currentState.soundState.copy(soundIndex = index)
- updateState {
- copy(soundState = newSoundState)
- }
-
- val selectedSound = currentState.soundState.sounds[index]
- alarmUseCase.initializeSoundPlayer(selectedSound.uri)
- alarmUseCase.playAlarmSound(currentState.soundState.soundVolume)
- }
-
- private fun toggleBottomSheet(sheetType: AlarmAddEditContract.BottomSheetType) {
- val newBottomSheetState = if (currentState.bottomSheetState == sheetType) {
- if (currentState.bottomSheetState == AlarmAddEditContract.BottomSheetType.SoundSetting) {
- alarmUseCase.stopAlarmSound()
- }
- null
- } else {
- sheetType
- }
- updateState {
- copy(bottomSheetState = newBottomSheetState)
- }
- }
-
- private fun getAlarmMessage(amPm: String, hour: Int, minute: Int, selectedDays: Set): String {
- val now = java.time.LocalDateTime.now()
- val alarmHour = convertTo24HourFormat(amPm, hour)
- val alarmTimeToday = now.toLocalDate().atTime(alarmHour, minute)
- val nextAlarmDateTime = calculateNextAlarmDateTime(now, alarmTimeToday, selectedDays)
- val duration = java.time.Duration.between(now, nextAlarmDateTime)
- val totalMinutes = duration.toMinutes()
- val days = totalMinutes / (24 * 60)
- val hours = (totalMinutes % (24 * 60)) / 60
- val minutes = totalMinutes % 60
-
- return when {
- days > 0 -> "${days}์ผ ${hours}์๊ฐ ํ์ ์ธ๋ ค์"
- hours > 0 -> "${hours}์๊ฐ ${minutes}๋ถ ํ์ ์ธ๋ ค์"
- minutes == 0L -> "๊ณง ์ธ๋ ค์"
- else -> "${minutes}๋ถ ํ์ ์ธ๋ ค์"
- }
- }
-
- private fun convertTo24HourFormat(amPm: String, hour: Int): Int = when {
- amPm == "์คํ" && hour != 12 -> hour + 12
- amPm == "์ค์ " && hour == 12 -> 0
- else -> hour
- }
-
- private fun calculateNextAlarmDateTime(
- now: java.time.LocalDateTime,
- alarmTimeToday: java.time.LocalDateTime,
- selectedDays: Set,
- ): java.time.LocalDateTime {
- if (selectedDays.isEmpty()) {
- return if (alarmTimeToday.isBefore(now)) {
- alarmTimeToday.plusDays(1)
- } else {
- alarmTimeToday
- }
- }
-
- val currentDayOfWeek = now.dayOfWeek.value
- val selectedDaysOfWeek = selectedDays.map { it.toDayOfWeek().value }.sorted()
-
- if (selectedDaysOfWeek.contains(currentDayOfWeek) && now.toLocalTime().isBefore(alarmTimeToday.toLocalTime())) {
- return alarmTimeToday
- }
-
- val nextDay = selectedDaysOfWeek.firstOrNull { it > currentDayOfWeek }
- ?: selectedDaysOfWeek.first()
- val daysToAdd = if (nextDay > currentDayOfWeek) {
- nextDay - currentDayOfWeek
- } else {
- 7 - (currentDayOfWeek - nextDay)
- }
-
- val nextAlarmDate = now.toLocalDate().plusDays(daysToAdd.toLong())
- return nextAlarmDate.atTime(alarmTimeToday.toLocalTime())
- }
-}
diff --git a/feature/home/src/main/java/com/yapp/alarm/component/bottomsheet/AlarmSnoozeBottomSheet.kt b/feature/home/src/main/java/com/yapp/alarm/component/bottomsheet/AlarmSnoozeBottomSheet.kt
deleted file mode 100644
index 8e2b9109..00000000
--- a/feature/home/src/main/java/com/yapp/alarm/component/bottomsheet/AlarmSnoozeBottomSheet.kt
+++ /dev/null
@@ -1,294 +0,0 @@
-package com.yapp.alarm.component.bottomsheet
-
-import androidx.compose.foundation.background
-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.ExperimentalMaterial3Api
-import androidx.compose.material3.Text
-import androidx.compose.material3.rememberModalBottomSheetState
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableIntStateOf
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-import com.yapp.designsystem.theme.OrbitTheme
-import com.yapp.ui.component.OrbitBottomSheet
-import com.yapp.ui.component.button.OrbitButton
-import com.yapp.ui.component.radiobutton.OrbitRadioButton
-import com.yapp.ui.component.switch.OrbitSwitch
-import feature.home.R
-import kotlinx.coroutines.launch
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-internal fun AlarmSnoozeBottomSheet(
- snoozeEnabled: Boolean,
- snoozeIntervalIndex: Int,
- snoozeIntervals: List,
- onIntervalSelected: (Int) -> Unit,
- snoozeCountIndex: Int,
- snoozeCounts: List,
- onSnoozeToggle: () -> Unit,
- onCountSelected: (Int) -> Unit,
- onComplete: () -> Unit,
- isSheetOpen: Boolean,
- onDismiss: () -> Unit,
-) {
- val scope = rememberCoroutineScope()
- val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
-
- OrbitBottomSheet(
- isSheetOpen = isSheetOpen,
- sheetState = sheetState,
- onDismissRequest = {
- scope.launch {
- sheetState.hide()
- }.invokeOnCompletion { onDismiss() }
- },
- ) {
- BottomSheetContent(
- isSnoozeEnabled = snoozeEnabled,
- snoozeIntervalIndex = snoozeIntervalIndex,
- snoozeIntervals = snoozeIntervals,
- onIntervalSelected = onIntervalSelected,
- snoozeCountIndex = snoozeCountIndex,
- snoozeCounts = snoozeCounts,
- onSnoozeToggle = onSnoozeToggle,
- onCountSelected = onCountSelected,
- onComplete = {
- scope.launch {
- sheetState.hide()
- }.invokeOnCompletion { onComplete() }
- },
- )
- }
-}
-
-@Composable
-private fun BottomSheetContent(
- isSnoozeEnabled: Boolean,
- snoozeIntervalIndex: Int,
- snoozeIntervals: List,
- onIntervalSelected: (Int) -> Unit,
- snoozeCountIndex: Int,
- snoozeCounts: List,
- onSnoozeToggle: () -> Unit,
- onCountSelected: (Int) -> Unit,
- onComplete: () -> Unit,
-) {
- Column(
- modifier = Modifier
- .fillMaxWidth()
- .padding(
- horizontal = 24.dp,
- vertical = 12.dp,
- ),
- horizontalAlignment = Alignment.CenterHorizontally,
- ) {
- Spacer(modifier = Modifier.height(6.dp))
- VibrationSection(isSnoozeEnabled, onSnoozeToggle)
- Spacer(modifier = Modifier.height(20.dp))
- SelectorSection(
- title = stringResource(id = R.string.alarm_add_edit_interval),
- selectedIndex = snoozeIntervalIndex,
- items = snoozeIntervals,
- enabled = isSnoozeEnabled,
- onItemSelected = onIntervalSelected,
- )
- Spacer(modifier = Modifier.height(32.dp))
- SelectorSection(
- title = stringResource(id = R.string.alarm_add_edit_repeat_count),
- selectedIndex = snoozeCountIndex,
- items = snoozeCounts,
- enabled = isSnoozeEnabled,
- onItemSelected = onCountSelected,
- )
- Spacer(modifier = Modifier.height(20.dp))
- if (isSnoozeEnabled) {
- AlarmSnoozeMessage(
- interval = snoozeIntervals[snoozeIntervalIndex],
- count = snoozeCounts[snoozeCountIndex],
- )
- }
- Spacer(modifier = Modifier.height(40.dp))
- OrbitButton(
- label = stringResource(id = R.string.alarm_add_edit_complete),
- enabled = true,
- containerColor = OrbitTheme.colors.gray_600,
- contentColor = OrbitTheme.colors.white,
- pressedContainerColor = OrbitTheme.colors.gray_500,
- pressedContentColor = OrbitTheme.colors.white.copy(alpha = 0.7f),
- onClick = onComplete,
- )
- }
-}
-
-@Composable
-private fun VibrationSection(snoozeEnabled: Boolean, onSnoozeToggle: () -> Unit) {
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .padding(vertical = 8.dp),
- verticalAlignment = Alignment.CenterVertically,
- ) {
- Text(
- text = stringResource(id = R.string.alarm_add_edit_alarm_snooze),
- style = OrbitTheme.typography.heading2SemiBold,
- color = OrbitTheme.colors.white,
- )
- Spacer(modifier = Modifier.weight(1f))
- OrbitSwitch(
- isChecked = snoozeEnabled,
- isEnabled = true,
- onClick = onSnoozeToggle,
- )
- }
-}
-
-@Composable
-private fun SelectorSection(
- title: String,
- selectedIndex: Int,
- items: List,
- enabled: Boolean,
- onItemSelected: (Int) -> Unit,
-) {
- Column {
- Text(
- text = title,
- style = OrbitTheme.typography.headline2Medium,
- color = OrbitTheme.colors.gray_50,
- )
- Spacer(modifier = Modifier.height(16.dp))
- SelectorItems(
- items = items,
- selectedIndex = selectedIndex,
- enabled = enabled,
- onItemSelected = onItemSelected,
- )
- }
-}
-
-@Composable
-private fun SelectorItems(
- items: List,
- selectedIndex: Int,
- enabled: Boolean,
- onItemSelected: (Int) -> Unit,
-) {
- Box {
- Column {
- Spacer(modifier = Modifier.height(7.dp))
- Spacer(
- modifier = Modifier
- .fillMaxWidth()
- .height(6.dp)
- .padding(horizontal = 6.dp)
- .background(
- if (enabled) {
- OrbitTheme.colors.gray_600
- } else {
- OrbitTheme.colors.gray_700
- },
- ),
- )
- }
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .padding(horizontal = 6.dp),
- horizontalArrangement = Arrangement.SpaceBetween,
- ) {
- items.forEachIndexed { index, item ->
- Column(horizontalAlignment = getAlignment(index, items.size)) {
- OrbitRadioButton(
- selected = index == selectedIndex,
- enabled = enabled,
- onClick = { if (enabled) onItemSelected(index) },
- )
- Spacer(modifier = Modifier.height(12.dp))
- Text(
- text = item,
- style = OrbitTheme.typography.body1Medium,
- color = OrbitTheme.colors.gray_50,
- )
- }
- }
- }
- }
-}
-
-private fun getAlignment(index: Int, size: Int): Alignment.Horizontal =
- when (index) {
- 0 -> Alignment.Start
- size - 1 -> Alignment.End
- else -> Alignment.CenterHorizontally
- }
-
-@Composable
-private fun AlarmSnoozeMessage(interval: String, count: String) {
- val formattedCount = if (count == stringResource(id = R.string.alarm_add_edit_repeat_count_infinite)) "${count}๋ฒ" else count
-
- Box(
- modifier = Modifier
- .background(
- color = OrbitTheme.colors.gray_700,
- shape = RoundedCornerShape(8.dp),
- )
- .padding(horizontal = 12.dp, vertical = 6.dp),
- contentAlignment = Alignment.Center,
- ) {
- Text(
- text = stringResource(id = R.string.alarm_add_edit_alarm_snooze_description, interval, formattedCount),
- style = OrbitTheme.typography.label1Medium,
- color = OrbitTheme.colors.main,
- )
- }
-}
-
-@Preview
-@Composable
-private fun AlarmSnoozeBottomSheetPreview() {
- var isSnoozeEnabled by remember { mutableStateOf(true) }
- var snoozeIntervalIndex by remember { mutableIntStateOf(2) }
- var snoozeCountIndex by remember { mutableIntStateOf(1) }
- var isSheetOpen by remember { mutableStateOf(true) }
-
- OrbitTheme {
- AlarmSnoozeBottomSheet(
- snoozeEnabled = isSnoozeEnabled,
- snoozeIntervalIndex = snoozeIntervalIndex,
- snoozeCountIndex = snoozeCountIndex,
- snoozeIntervals = listOf(1, 3, 5, 10, 15).map {
- stringResource(id = R.string.alarm_add_edit_interval_minute, it)
- },
- snoozeCounts = listOf(
- stringResource(id = R.string.alarm_add_edit_repeat_count_times, 1),
- stringResource(id = R.string.alarm_add_edit_repeat_count_times, 3),
- stringResource(id = R.string.alarm_add_edit_repeat_count_times, 5),
- stringResource(id = R.string.alarm_add_edit_repeat_count_times, 10),
- stringResource(id = R.string.alarm_add_edit_repeat_count_infinite),
- ),
- onSnoozeToggle = { isSnoozeEnabled = !isSnoozeEnabled },
- onIntervalSelected = { index -> snoozeIntervalIndex = index },
- onCountSelected = { index -> snoozeCountIndex = index },
- onComplete = { isSheetOpen = false },
- isSheetOpen = isSheetOpen,
- onDismiss = { isSheetOpen = false },
- )
- }
-}
diff --git a/feature/home/src/main/java/com/yapp/home/HomeContract.kt b/feature/home/src/main/java/com/yapp/home/HomeContract.kt
index ee3f5dd1..e4d15d19 100644
--- a/feature/home/src/main/java/com/yapp/home/HomeContract.kt
+++ b/feature/home/src/main/java/com/yapp/home/HomeContract.kt
@@ -3,7 +3,6 @@ package com.yapp.home
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.yapp.domain.model.Alarm
-import com.yapp.ui.base.SideEffect
import com.yapp.ui.base.UiState
sealed class HomeContract {
@@ -19,6 +18,7 @@ sealed class HomeContract {
val isDeleteDialogVisible: Boolean = false,
val isNoActivatedAlarmDialogVisible: Boolean = false,
val isNoDailyFortuneDialogVisible: Boolean = false,
+ val isUpdateNoticeVisible: Boolean = false,
val hasNewFortune: Boolean = false,
val isToolTipVisible: Boolean = false,
val pendingAlarmToggle: Pair? = null,
@@ -59,6 +59,8 @@ sealed class HomeContract {
data object ShowNoDailyFortuneDialog : Action()
data object HideNoDailyFortuneDialog : Action()
data object HideToolTip : Action()
+ data object OnClickDontShowAgain : Action()
+ data object HideUpdateNotice : Action()
data object RollbackPendingAlarmToggle : Action()
data object ConfirmDeletion : Action()
data class DeleteSingleAlarm(val alarmId: Long) : Action()
diff --git a/feature/home/src/main/java/com/yapp/home/HomeNavGraph.kt b/feature/home/src/main/java/com/yapp/home/HomeNavGraph.kt
index b044e9de..c46e3458 100644
--- a/feature/home/src/main/java/com/yapp/home/HomeNavGraph.kt
+++ b/feature/home/src/main/java/com/yapp/home/HomeNavGraph.kt
@@ -4,10 +4,11 @@ import androidx.compose.material3.SnackbarHostState
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import androidx.navigation.compose.navigation
-import com.yapp.alarm.addedit.AlarmAddEditRoute
import com.yapp.common.navigation.OrbitNavigator
import com.yapp.common.navigation.route.HomeBaseRoute
import com.yapp.common.navigation.route.HomeDestination
+import com.yapp.home.alarm.addedit.AlarmAddEditRoute
+import com.yapp.ui.component.bottomsheet.OrbitBottomSheetState
const val ADD_ALARM_RESULT_KEY = "addAlarmResult"
const val UPDATE_ALARM_RESULT_KEY = "updateAlarmResult"
@@ -15,6 +16,7 @@ const val DELETE_ALARM_RESULT_KEY = "deleteAlarmResult"
fun NavGraphBuilder.homeNavGraph(
navigator: OrbitNavigator,
+ bottomSheetState: OrbitBottomSheetState,
snackBarHostState: SnackbarHostState,
) {
navigation(
@@ -30,6 +32,7 @@ fun NavGraphBuilder.homeNavGraph(
composable {
AlarmAddEditRoute(
navigator = navigator,
+ bottomSheetState = bottomSheetState,
snackBarHostState = snackBarHostState,
)
}
diff --git a/feature/home/src/main/java/com/yapp/home/HomeScreen.kt b/feature/home/src/main/java/com/yapp/home/HomeScreen.kt
index b8a4dc46..10d74e52 100644
--- a/feature/home/src/main/java/com/yapp/home/HomeScreen.kt
+++ b/feature/home/src/main/java/com/yapp/home/HomeScreen.kt
@@ -1,5 +1,6 @@
package com.yapp.home
+import android.os.Build
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Canvas
@@ -15,12 +16,16 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
@@ -64,12 +69,13 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.yapp.alarm.component.AlarmListItem
-import com.yapp.alarm.component.AlarmListItemMenu
import com.yapp.common.navigation.OrbitNavigator
import com.yapp.designsystem.theme.OrbitTheme
import com.yapp.domain.model.Alarm
+import com.yapp.home.alarm.component.AlarmListItem
+import com.yapp.home.alarm.component.AlarmListItemMenu
import com.yapp.home.component.bottomsheet.AlarmListBottomSheet
+import com.yapp.home.component.bottomsheet.UpdateNoticeBottomSheet
import com.yapp.ui.component.dialog.OrbitDialog
import com.yapp.ui.component.lottie.LottieAnimation
import com.yapp.ui.component.snackbar.showCustomSnackBar
@@ -77,7 +83,8 @@ import com.yapp.ui.component.tooltip.OrbitToolTip
import com.yapp.ui.utils.heightForScreenPercentage
import com.yapp.ui.utils.toPx
import feature.home.R
-import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.CoroutineScope
+import org.orbitmvi.orbit.compose.collectSideEffect
@Composable
fun HomeRoute(
@@ -86,7 +93,6 @@ fun HomeRoute(
snackBarHostState: SnackbarHostState,
) {
val state by viewModel.container.stateFlow.collectAsStateWithLifecycle()
- val sideEffect = viewModel.container.sideEffectFlow
val coroutineScope = rememberCoroutineScope()
@@ -120,70 +126,75 @@ fun HomeRoute(
}
}
- LaunchedEffect(sideEffect) {
- sideEffect.collectLatest { effect ->
- when (effect) {
- is HomeContract.SideEffect.NavigateToAddAlarm -> {
- navigator.navigateToAddAlarm()
- }
+ viewModel.collectSideEffect {
+ handleSideEffect(it, navigator, snackBarHostState, coroutineScope)
+ }
- is HomeContract.SideEffect.NavigateToEditAlarm -> {
- navigator.navigateToEditAlarm(effect.alarmId)
- }
+ HomeScreen(
+ state = state,
+ processAction = viewModel::processAction,
+ )
+}
- is HomeContract.SideEffect.NavigateToFortune -> {
- navigator.navigateToFortune()
- }
+private suspend fun handleSideEffect(
+ sideEffect: HomeContract.SideEffect,
+ navigator: OrbitNavigator,
+ snackBarHostState: SnackbarHostState,
+ coroutineScope: CoroutineScope,
+) {
+ when (sideEffect) {
+ is HomeContract.SideEffect.NavigateToAddAlarm -> {
+ navigator.navigateToAddAlarm()
+ }
- is HomeContract.SideEffect.NavigateToSetting -> {
- navigator.navigateToSetting()
- }
+ is HomeContract.SideEffect.NavigateToEditAlarm -> {
+ navigator.navigateToEditAlarm(sideEffect.alarmId)
+ }
- is HomeContract.SideEffect.ShowSnackBar -> {
- val result = showCustomSnackBar(
- scope = coroutineScope,
- snackBarHostState = snackBarHostState,
- message = effect.message,
- actionLabel = effect.label,
- iconRes = effect.iconRes,
- bottomPadding = effect.bottomPadding,
- durationMillis = effect.durationMillis,
- )
+ is HomeContract.SideEffect.NavigateToFortune -> {
+ navigator.navigateToFortune()
+ }
- when (result) {
- SnackbarResult.ActionPerformed -> effect.onAction()
- SnackbarResult.Dismissed -> effect.onDismiss()
- }
- }
+ is HomeContract.SideEffect.NavigateToSetting -> {
+ navigator.navigateToSetting()
+ }
+
+ is HomeContract.SideEffect.ShowSnackBar -> {
+ val result = showCustomSnackBar(
+ scope = coroutineScope,
+ snackBarHostState = snackBarHostState,
+ message = sideEffect.message,
+ actionLabel = sideEffect.label,
+ iconRes = sideEffect.iconRes,
+ bottomPadding = sideEffect.bottomPadding,
+ durationMillis = sideEffect.durationMillis,
+ )
+
+ when (result) {
+ SnackbarResult.ActionPerformed -> sideEffect.onAction()
+ SnackbarResult.Dismissed -> sideEffect.onDismiss()
}
}
}
-
- HomeScreen(
- stateProvider = { state },
- eventDispatcher = viewModel::processAction,
- )
}
@Composable
fun HomeScreen(
- stateProvider: () -> HomeContract.State,
- eventDispatcher: (HomeContract.Action) -> Unit,
+ state: HomeContract.State,
+ processAction: (HomeContract.Action) -> Unit,
) {
- val state = stateProvider()
-
if (state.initialLoading) {
HomeLoadingScreen()
} else if (state.alarms.isEmpty()) {
HomeAlarmEmptyScreen(
onSettingClick = {
- eventDispatcher(HomeContract.Action.NavigateToSetting)
+ processAction(HomeContract.Action.NavigateToSetting)
},
onMailClick = {
- eventDispatcher(HomeContract.Action.ShowDailyFortune)
+ processAction(HomeContract.Action.ShowDailyFortune)
},
onAddClick = {
- eventDispatcher(HomeContract.Action.NavigateToAlarmCreation)
+ processAction(HomeContract.Action.NavigateToAlarmCreation)
},
hasNewFortune = state.hasNewFortune,
isTooltipVisible = state.isToolTipVisible,
@@ -191,7 +202,7 @@ fun HomeScreen(
} else {
HomeContent(
state = state,
- eventDispatcher = eventDispatcher,
+ processAction = processAction,
)
}
@@ -202,10 +213,10 @@ fun HomeScreen(
confirmText = stringResource(id = R.string.alarm_delete_dialog_btn_delete),
cancelText = stringResource(id = R.string.alarm_delete_dialog_btn_cancel),
onConfirm = {
- eventDispatcher(HomeContract.Action.ConfirmDeletion)
+ processAction(HomeContract.Action.ConfirmDeletion)
},
onCancel = {
- eventDispatcher(HomeContract.Action.HideDeleteDialog)
+ processAction(HomeContract.Action.HideDeleteDialog)
},
)
}
@@ -217,10 +228,10 @@ fun HomeScreen(
confirmText = stringResource(id = R.string.no_active_alarm_dialog_btn_confirm),
cancelText = stringResource(id = R.string.no_active_alarm_dialog_btn_cancel),
onConfirm = {
- eventDispatcher(HomeContract.Action.HideNoActivatedAlarmDialog)
+ processAction(HomeContract.Action.HideNoActivatedAlarmDialog)
},
onCancel = {
- eventDispatcher(HomeContract.Action.RollbackPendingAlarmToggle)
+ processAction(HomeContract.Action.RollbackPendingAlarmToggle)
},
)
}
@@ -231,7 +242,18 @@ fun HomeScreen(
message = stringResource(id = R.string.no_daily_fortune_dialog_message),
confirmText = stringResource(id = R.string.no_daily_fortune_dialog_btn_confirm),
onConfirm = {
- eventDispatcher(HomeContract.Action.HideNoDailyFortuneDialog)
+ processAction(HomeContract.Action.HideNoDailyFortuneDialog)
+ },
+ )
+ }
+
+ if (state.isUpdateNoticeVisible) {
+ UpdateNoticeBottomSheet(
+ onDontShowAgain = {
+ processAction(HomeContract.Action.OnClickDontShowAgain)
+ },
+ onClose = {
+ processAction(HomeContract.Action.HideUpdateNotice)
},
)
}
@@ -268,9 +290,11 @@ private fun HomeLoadingScreen() {
@Composable
private fun HomeContent(
state: HomeContract.State,
- eventDispatcher: (HomeContract.Action) -> Unit,
+ processAction: (HomeContract.Action) -> Unit,
) {
val screenHeight = LocalConfiguration.current.screenHeightDp.dp
+ val statusBarHeight = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
+ val navBarHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
var sheetHalfExpandHeight by remember { mutableStateOf(0.dp) }
val listState = rememberLazyListState()
@@ -278,7 +302,7 @@ private fun HomeContent(
LaunchedEffect(state.lastAddedAlarmIndex) {
state.lastAddedAlarmIndex?.let { index ->
listState.scrollToItem(index)
- eventDispatcher(HomeContract.Action.ResetLastAddedAlarmIndex)
+ processAction(HomeContract.Action.ResetLastAddedAlarmIndex)
}
}
@@ -288,7 +312,7 @@ private fun HomeContent(
interactionSource = remember { MutableInteractionSource() },
indication = null,
) {
- eventDispatcher(HomeContract.Action.HideToolTip)
+ processAction(HomeContract.Action.HideToolTip)
},
) {
if (state.activeItemMenu != null) {
@@ -299,7 +323,7 @@ private fun HomeContent(
interactionSource = remember { MutableInteractionSource() },
indication = null,
) {
- eventDispatcher(HomeContract.Action.HideItemMenu)
+ processAction(HomeContract.Action.HideItemMenu)
}
.zIndex(1f),
)
@@ -325,47 +349,50 @@ private fun HomeContent(
halfExpandedHeight = sheetHalfExpandHeight,
listState = listState,
onClickAlarm = { alarmId ->
- eventDispatcher(HomeContract.Action.EditAlarm(alarmId))
+ processAction(HomeContract.Action.EditAlarm(alarmId))
},
onLongPressAlarm = { alarmId, x, y ->
- eventDispatcher(HomeContract.Action.ShowItemMenu(alarmId, x, y))
+ processAction(HomeContract.Action.ShowItemMenu(alarmId, x, y))
},
onClickAdd = {
- eventDispatcher(HomeContract.Action.NavigateToAlarmCreation)
+ processAction(HomeContract.Action.NavigateToAlarmCreation)
},
onClickMore = {
if (state.dropdownMenuExpanded || state.sortDropDownMenuExpanded) {
- eventDispatcher(HomeContract.Action.HideDropDownMenu)
+ processAction(HomeContract.Action.HideDropDownMenu)
} else {
- eventDispatcher(HomeContract.Action.ShowDropDownMenu)
+ processAction(HomeContract.Action.ShowDropDownMenu)
}
},
onClickCheckAll = {
- eventDispatcher(HomeContract.Action.ToggleAllAlarmSelection)
+ processAction(HomeContract.Action.ToggleAllAlarmSelection)
},
onClickClose = {
- eventDispatcher(HomeContract.Action.ToggleMultiSelectionMode)
+ processAction(HomeContract.Action.ToggleMultiSelectionMode)
},
onClickEdit = {
- eventDispatcher(HomeContract.Action.ToggleMultiSelectionMode)
+ processAction(HomeContract.Action.ToggleMultiSelectionMode)
},
onClickSort = {
- eventDispatcher(HomeContract.Action.ShowSortDropDownMenu)
+ processAction(HomeContract.Action.ShowSortDropDownMenu)
},
onSetSortOrder = { sortOrder ->
- eventDispatcher(HomeContract.Action.SetSortOrder(sortOrder))
+ processAction(HomeContract.Action.SetSortOrder(sortOrder))
},
onDismissRequest = {
- eventDispatcher(HomeContract.Action.HideDropDownMenu)
+ processAction(HomeContract.Action.HideDropDownMenu)
},
onToggleSelect = { alarmId ->
- eventDispatcher(HomeContract.Action.ToggleAlarmSelection(alarmId))
+ processAction(HomeContract.Action.ToggleAlarmSelection(alarmId))
},
onToggleActive = { alarmId ->
- eventDispatcher(HomeContract.Action.ToggleAlarmActivation(alarmId))
+ processAction(HomeContract.Action.ToggleAlarmActivation(alarmId))
},
onSwipe = { alarmId ->
- eventDispatcher(HomeContract.Action.SwipeToDeleteAlarm(alarmId))
+ processAction(HomeContract.Action.SwipeToDeleteAlarm(alarmId))
+ },
+ onExpanded = {
+ processAction(HomeContract.Action.HideToolTip)
},
) {
Box(
@@ -384,7 +411,15 @@ private fun HomeContent(
.fillMaxWidth()
.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
- sheetHalfExpandHeight = screenHeight - placeable.height.toDp()
+ val contentHeight = placeable.height.toDp()
+
+ val offset = if (Build.VERSION.SDK_INT < 35) {
+ 0.dp
+ } else {
+ statusBarHeight + navBarHeight
+ }
+ sheetHalfExpandHeight = screenHeight - contentHeight - offset
+
layout(placeable.width, placeable.height) {
placeable.placeRelative(0, 0)
}
@@ -407,8 +442,8 @@ private fun HomeContent(
}
HomeTopBar(
- onSettingClick = { eventDispatcher(HomeContract.Action.NavigateToSetting) },
- onMailClick = { eventDispatcher(HomeContract.Action.ShowDailyFortune) },
+ onSettingClick = { processAction(HomeContract.Action.NavigateToSetting) },
+ onMailClick = { processAction(HomeContract.Action.ShowDailyFortune) },
hasNewFortune = state.hasNewFortune,
isShowTooltip = state.isToolTipVisible,
)
@@ -425,7 +460,7 @@ private fun HomeContent(
.padding(bottom = 26.dp),
selectedAlarmCount = state.selectedAlarmIds.size,
onClick = {
- eventDispatcher(HomeContract.Action.ShowDeleteDialog)
+ processAction(HomeContract.Action.ShowDeleteDialog)
},
)
}
@@ -438,7 +473,7 @@ private fun HomeContent(
activeItemMenuPosition = state.activeItemMenuPosition,
selectedAlarmIds = state.selectedAlarmIds,
onDelete = { alarmId ->
- eventDispatcher(HomeContract.Action.DeleteSingleAlarm(alarmId))
+ processAction(HomeContract.Action.DeleteSingleAlarm(alarmId))
},
)
}
@@ -529,7 +564,7 @@ private fun HomeTopBar(
}
@Composable
-fun HillWithGradient() {
+private fun HillWithGradient() {
val hillTopY = (LocalConfiguration.current.screenHeightDp.dp * 0.28f).toPx()
Canvas(
@@ -565,7 +600,7 @@ fun HillWithGradient() {
}
@Composable
-fun SkyImage() {
+private fun SkyImage() {
Image(
painter = painterResource(id = core.designsystem.R.drawable.ic_main_sky),
contentDescription = "IMG_MAIN_SKY",
@@ -898,7 +933,6 @@ private fun AlarmWithMenu(
swipeable = false,
selectable = false,
selected = selectedAlarmIds.contains(activeItemMenu.id),
- isAm = activeItemMenu.isAm,
hour = activeItemMenu.hour,
minute = activeItemMenu.minute,
isActive = activeItemMenu.isAlarmActive,
@@ -926,18 +960,16 @@ private fun AlarmWithMenu(
fun HomeScreenPreview() {
OrbitTheme {
HomeScreen(
- stateProvider = {
- HomeContract.State()
- .copy(
- initialLoading = false,
- alarms = listOf(
- Alarm(),
- ),
- activeItemMenu = 0L,
- activeItemMenuPosition = Pair(0f, 0f),
- )
- },
- eventDispatcher = {},
+ state = HomeContract.State()
+ .copy(
+ initialLoading = false,
+ alarms = listOf(
+ Alarm(),
+ ),
+ activeItemMenu = 0L,
+ activeItemMenuPosition = Pair(0f, 0f),
+ ),
+ processAction = {},
)
}
}
diff --git a/feature/home/src/main/java/com/yapp/home/HomeViewModel.kt b/feature/home/src/main/java/com/yapp/home/HomeViewModel.kt
index 9c5dccc6..57b74428 100644
--- a/feature/home/src/main/java/com/yapp/home/HomeViewModel.kt
+++ b/feature/home/src/main/java/com/yapp/home/HomeViewModel.kt
@@ -1,39 +1,55 @@
package com.yapp.home
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.NetworkCapabilities
import android.util.Log
-import androidx.lifecycle.viewModelScope
-import com.yapp.alarm.AlarmHelper
+import androidx.lifecycle.ViewModel
import com.yapp.common.util.ResourceProvider
-import com.yapp.datastore.UserPreferences
import com.yapp.domain.model.Alarm
-import com.yapp.domain.model.toAlarmDays
-import com.yapp.domain.model.toDayOfWeek
+import com.yapp.domain.repository.FortuneRepository
+import com.yapp.domain.repository.UserInfoRepository
import com.yapp.domain.usecase.AlarmUseCase
-import com.yapp.ui.base.BaseViewModel
+import com.yapp.home.util.AlarmDateTimeFormatter
import dagger.hilt.android.lifecycle.HiltViewModel
+import dagger.hilt.android.qualifiers.ApplicationContext
import feature.home.R
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
-import kotlinx.coroutines.launch
+import org.orbitmvi.orbit.Container
+import org.orbitmvi.orbit.ContainerHost
+import org.orbitmvi.orbit.syntax.simple.intent
+import org.orbitmvi.orbit.syntax.simple.postSideEffect
+import org.orbitmvi.orbit.syntax.simple.reduce
+import org.orbitmvi.orbit.syntax.simple.repeatOnSubscription
+import org.orbitmvi.orbit.viewmodel.container
import java.time.LocalDate
-import java.time.LocalDateTime
-import java.time.LocalTime
-import java.time.format.DateTimeFormatter
import javax.inject.Inject
+import javax.inject.Named
@HiltViewModel
class HomeViewModel @Inject constructor(
private val alarmUseCase: AlarmUseCase,
private val resourceProvider: ResourceProvider,
- private val alarmHelper: AlarmHelper,
- private val userPreferences: UserPreferences,
-) : BaseViewModel(
- initialState = HomeContract.State(),
-) {
- init {
- loadAllAlarms()
- loadDailyFortuneState()
- loadUserName()
+ private val alarmDateTimeFormatter: AlarmDateTimeFormatter,
+ private val fortuneRepository: FortuneRepository,
+ private val userInfoRepository: UserInfoRepository,
+ @Named("appVersion") private val appVersion: String,
+ @ApplicationContext private val context: Context,
+) : ViewModel(), ContainerHost {
+
+ override val container: Container = container(
+ initialState = HomeContract.State(),
+ ) {
+ intent {
+ repeatOnSubscription {
+ loadAllAlarms()
+ loadDailyFortuneState()
+ loadUserName()
+ loadUpdateNoticeVisibility()
+ }
+ }
}
fun processAction(action: HomeContract.Action) {
@@ -54,6 +70,8 @@ class HomeViewModel @Inject constructor(
HomeContract.Action.ShowNoDailyFortuneDialog -> showNoDailyFortuneDialog()
HomeContract.Action.HideNoDailyFortuneDialog -> hideNoDailyFortuneDialog()
HomeContract.Action.HideToolTip -> hideToolTip()
+ HomeContract.Action.HideUpdateNotice -> hideUpdateNotice()
+ HomeContract.Action.OnClickDontShowAgain -> setUpdateNoticeDontShowVersion()
HomeContract.Action.RollbackPendingAlarmToggle -> rollbackAlarmActivation()
HomeContract.Action.ConfirmDeletion -> confirmDeletion()
is HomeContract.Action.DeleteSingleAlarm -> deleteSingleAlarm(action.alarmId)
@@ -68,17 +86,17 @@ class HomeViewModel @Inject constructor(
}
}
- fun scrollToAddedAlarm(id: Long) {
- val newAlarmIndex = currentState.alarms.indexOfFirst { it.id == id }
- if (newAlarmIndex == -1) return
+ fun scrollToAddedAlarm(id: Long) = intent {
+ val newAlarmIndex = state.alarms.indexOfFirst { it.id == id }
+ if (newAlarmIndex == -1) return@intent
- updateState {
- copy(
+ reduce {
+ state.copy(
lastAddedAlarmIndex = newAlarmIndex,
)
}
- emitSideEffect(
+ postSideEffect(
HomeContract.SideEffect.ShowSnackBar(
message = resourceProvider.getString(R.string.alarm_added),
iconRes = resourceProvider.getDrawable(core.designsystem.R.drawable.ic_check_green),
@@ -88,164 +106,160 @@ class HomeViewModel @Inject constructor(
)
}
- fun scrollToUpdatedAlarm(id: Long) {
- val updatedAlarmIndex = currentState.alarms.indexOfFirst { it.id == id }
- if (updatedAlarmIndex == -1) return
+ fun scrollToUpdatedAlarm(id: Long) = intent {
+ val updatedAlarmIndex = state.alarms.indexOfFirst { it.id == id }
+ if (updatedAlarmIndex == -1) return@intent
- updateState {
- copy(
+ reduce {
+ state.copy(
lastAddedAlarmIndex = updatedAlarmIndex,
)
}
}
- private fun navigateToAlarmCreation() {
- emitSideEffect(HomeContract.SideEffect.NavigateToAddAlarm)
+ private fun navigateToAlarmCreation() = intent {
+ postSideEffect(HomeContract.SideEffect.NavigateToAddAlarm)
}
- private fun toggleMultiSelectionMode() {
- updateState {
- copy(
- isSelectionMode = !currentState.isSelectionMode,
+ private fun toggleMultiSelectionMode() = intent {
+ reduce {
+ state.copy(
+ isSelectionMode = !state.isSelectionMode,
selectedAlarmIds = emptySet(),
dropdownMenuExpanded = false,
)
}
}
- private fun showDropDownMenu() {
- updateState { copy(dropdownMenuExpanded = true) }
+ private fun showDropDownMenu() = intent {
+ reduce { state.copy(dropdownMenuExpanded = true) }
}
- private fun showSortDropDownMenu() {
- updateState {
- copy(
+ private fun showSortDropDownMenu() = intent {
+ reduce {
+ state.copy(
dropdownMenuExpanded = false,
sortDropDownMenuExpanded = true,
)
}
}
- private fun hideDropDownMenu() {
- updateState {
- copy(
+ private fun hideDropDownMenu() = intent {
+ reduce {
+ state.copy(
dropdownMenuExpanded = false,
sortDropDownMenuExpanded = false,
)
}
}
- private fun toggleAlarmSelection(alarmId: Long) {
- updateState {
- val updatedSelection = currentState.selectedAlarmIds.toMutableSet().apply {
+ private fun toggleAlarmSelection(alarmId: Long) = intent {
+ reduce {
+ val updatedSelection = state.selectedAlarmIds.toMutableSet().apply {
if (contains(alarmId)) remove(alarmId) else add(alarmId)
}
- copy(selectedAlarmIds = updatedSelection)
+ state.copy(selectedAlarmIds = updatedSelection)
}
}
- private fun toggleAllAlarmSelection() {
- updateState {
- val allIds = currentState.alarms.map { it.id }.toSet()
- val updatedSelection = if (currentState.selectedAlarmIds == allIds) emptySet() else allIds
- copy(selectedAlarmIds = updatedSelection)
+ private fun toggleAllAlarmSelection() = intent {
+ reduce {
+ val allIds = state.alarms.map { it.id }.toSet()
+ val updatedSelection = if (state.selectedAlarmIds == allIds) emptySet() else allIds
+ state.copy(selectedAlarmIds = updatedSelection)
}
}
- private fun toggleAlarmActivation(alarmId: Long) {
- viewModelScope.launch {
- val currentIndex = currentState.alarms.indexOfFirst { it.id == alarmId }
- if (currentIndex == -1) return@launch
-
- val currentAlarm = currentState.alarms[currentIndex]
- val previousState = currentAlarm.isAlarmActive // ๊ธฐ์กด ์ํ ์ ์ฅ
- val updatedAlarm = currentAlarm.copy(isAlarmActive = !currentAlarm.isAlarmActive)
-
- alarmUseCase.updateAlarmActive(alarmId, updatedAlarm.isAlarmActive).onSuccess {
- val updatedAlarms = currentState.alarms.toMutableList()
- updatedAlarms[currentIndex] = updatedAlarm
-
- val hasActivatedAlarm = updatedAlarms.any { it.isAlarmActive }
- updateState {
- copy(
- alarms = updatedAlarms,
- isNoActivatedAlarmDialogVisible = !hasActivatedAlarm,
- pendingAlarmToggle = if (!hasActivatedAlarm) alarmId to previousState else null,
- )
- }
-
- if (updatedAlarm.isAlarmActive) {
- alarmHelper.scheduleAlarm(updatedAlarm)
- } else {
- alarmHelper.unScheduleAlarm(updatedAlarm)
- }
- }.onFailure { error ->
- Log.e("HomeViewModel", "Failed to update alarm state", error)
+ private fun toggleAlarmActivation(alarmId: Long) = intent {
+ val currentIndex = state.alarms.indexOfFirst { it.id == alarmId }
+ if (currentIndex == -1) return@intent
+
+ val currentAlarm = state.alarms[currentIndex]
+ val previousState = currentAlarm.isAlarmActive // ๊ธฐ์กด ์ํ ์ ์ฅ
+ val updatedAlarm = currentAlarm.copy(isAlarmActive = !currentAlarm.isAlarmActive)
+
+ alarmUseCase.updateAlarmActive(alarmId, updatedAlarm.isAlarmActive).onSuccess {
+ val updatedAlarms = state.alarms.toMutableList()
+ updatedAlarms[currentIndex] = updatedAlarm
+
+ val hasActivatedAlarm = updatedAlarms.any { it.isAlarmActive }
+ reduce {
+ state.copy(
+ alarms = updatedAlarms,
+ isNoActivatedAlarmDialogVisible = !hasActivatedAlarm,
+ pendingAlarmToggle = if (!hasActivatedAlarm) alarmId to previousState else null,
+ )
}
+
+ if (updatedAlarm.isAlarmActive) {
+ alarmUseCase.scheduleAlarm(updatedAlarm)
+ } else {
+ alarmUseCase.unScheduleAlarm(updatedAlarm)
+ }
+ }.onFailure { error ->
+ Log.e("HomeViewModel", "Failed to update alarm state", error)
}
}
- private fun showDeleteDialog() {
- updateState { copy(isDeleteDialogVisible = true) }
+ private fun showDeleteDialog() = intent {
+ reduce { state.copy(isDeleteDialogVisible = true) }
}
- private fun hideDeleteDialog() {
- updateState { copy(isDeleteDialogVisible = false) }
+ private fun hideDeleteDialog() = intent {
+ reduce { state.copy(isDeleteDialogVisible = false) }
}
- private fun confirmDeletion() {
- deleteAlarms(currentState.selectedAlarmIds)
- updateState {
- copy(
+ private fun confirmDeletion() = intent {
+ deleteAlarms(state.selectedAlarmIds)
+ reduce {
+ state.copy(
selectedAlarmIds = emptySet(),
isDeleteDialogVisible = false,
)
}
}
- private fun showNoActivatedAlarmDialog() {
- updateState { copy(isNoActivatedAlarmDialogVisible = true) }
+ private fun showNoActivatedAlarmDialog() = intent {
+ reduce { state.copy(isNoActivatedAlarmDialogVisible = true) }
}
- private fun hideNoActivatedAlarmDialog() {
- updateState {
- copy(
+ private fun hideNoActivatedAlarmDialog() = intent {
+ reduce {
+ state.copy(
isNoActivatedAlarmDialogVisible = false,
pendingAlarmToggle = null,
)
}
}
- private fun rollbackAlarmActivation() {
- val pendingAlarm = currentState.pendingAlarmToggle ?: return
+ private fun rollbackAlarmActivation() = intent {
+ val pendingAlarm = state.pendingAlarmToggle ?: return@intent
val (alarmId, previousState) = pendingAlarm
- viewModelScope.launch {
- val currentIndex = currentState.alarms.indexOfFirst { it.id == alarmId }
- if (currentIndex == -1) return@launch
-
- val currentAlarm = currentState.alarms[currentIndex]
- val restoredAlarm = currentAlarm.copy(isAlarmActive = previousState)
-
- alarmUseCase.updateAlarm(restoredAlarm).onSuccess { updatedAlarm ->
- val updatedAlarms = currentState.alarms.toMutableList()
- updatedAlarms[currentIndex] = updatedAlarm
- updateState {
- copy(
- alarms = updatedAlarms,
- pendingAlarmToggle = null,
- isNoActivatedAlarmDialogVisible = false,
- )
- }
-
- if (updatedAlarm.isAlarmActive) {
- alarmHelper.scheduleAlarm(updatedAlarm)
- } else {
- alarmHelper.unScheduleAlarm(updatedAlarm)
- }
- }.onFailure { error ->
- Log.e("HomeViewModel", "Failed to rollback alarm state", error)
+ val currentIndex = state.alarms.indexOfFirst { it.id == alarmId }
+ if (currentIndex == -1) return@intent
+
+ val currentAlarm = state.alarms[currentIndex]
+ val restoredAlarm = currentAlarm.copy(isAlarmActive = previousState)
+
+ alarmUseCase.updateAlarm(restoredAlarm).onSuccess { updatedAlarm ->
+ val updatedAlarms = state.alarms.toMutableList()
+ updatedAlarms[currentIndex] = updatedAlarm
+ reduce {
+ state.copy(
+ alarms = updatedAlarms,
+ pendingAlarmToggle = null,
+ isNoActivatedAlarmDialogVisible = false,
+ )
}
+
+ if (updatedAlarm.isAlarmActive) {
+ alarmUseCase.scheduleAlarm(updatedAlarm)
+ } else {
+ alarmUseCase.unScheduleAlarm(updatedAlarm)
+ }
+ }.onFailure { error ->
+ Log.e("HomeViewModel", "Failed to rollback alarm state", error)
}
}
@@ -253,24 +267,22 @@ class HomeViewModel @Inject constructor(
deleteAlarms(setOf(alarmId))
}
- private fun deleteAlarms(alarmIds: Set) {
- if (alarmIds.isEmpty()) return
+ private fun deleteAlarms(alarmIds: Set) = intent {
+ if (alarmIds.isEmpty()) return@intent
- val alarmsToDelete = currentState.alarms
+ val alarmsToDelete = state.alarms
.filter { it.id in alarmIds }
- viewModelScope.launch {
- alarmsToDelete.forEach { alarm ->
- alarmUseCase.deleteAlarm(alarm.id)
- alarmHelper.unScheduleAlarm(alarm)
- }
+ alarmsToDelete.forEach { alarm ->
+ alarmUseCase.deleteAlarm(alarm.id)
+ alarmUseCase.unScheduleAlarm(alarm)
}
- if (currentState.activeItemMenu != null) {
+ if (state.activeItemMenu != null) {
hideItemMenu()
}
- emitSideEffect(
+ postSideEffect(
HomeContract.SideEffect.ShowSnackBar(
message = resourceProvider.getString(R.string.alarm_deleted),
label = resourceProvider.getString(R.string.alarm_delete_dialog_btn_cancel),
@@ -283,198 +295,171 @@ class HomeViewModel @Inject constructor(
)
}
- private fun restoreDeletedAlarms(alarmsWithIndex: List) {
- viewModelScope.launch {
- alarmsWithIndex.forEach { alarm ->
- alarmUseCase.insertAlarm(alarm)
- alarmHelper.scheduleAlarm(alarm)
- }
+ private fun restoreDeletedAlarms(alarmsWithIndex: List) = intent {
+ alarmsWithIndex.forEach { alarm ->
+ alarmUseCase.insertAlarm(alarm)
+ alarmUseCase.scheduleAlarm(alarm)
}
}
- private fun restLastAddedAlarmIndex() {
- updateState { copy(lastAddedAlarmIndex = null) }
+ private fun restLastAddedAlarmIndex() = intent {
+ reduce { state.copy(lastAddedAlarmIndex = null) }
}
- private fun loadAllAlarms() {
- updateState { copy(initialLoading = true) }
+ private fun loadAllAlarms() = intent {
+ reduce { state.copy(initialLoading = true) }
- viewModelScope.launch {
- alarmUseCase.getAllAlarms().collect {
- updateState {
- copy(
- alarms = it,
- initialLoading = false,
- )
- }
- updateDeliveryTime(it)
+ alarmUseCase.getAllAlarms().collect { alarms ->
+ reduce {
+ state.copy(
+ alarms = alarms,
+ initialLoading = false,
+ )
}
+ updateDeliveryTime(alarms)
}
}
- private fun editAlarm(alarmId: Long) {
- emitSideEffect(HomeContract.SideEffect.NavigateToEditAlarm(alarmId))
+ private fun editAlarm(alarmId: Long) = intent {
+ postSideEffect(HomeContract.SideEffect.NavigateToEditAlarm(alarmId))
}
- private fun updateDeliveryTime(alarms: List) {
- val earliestAlarm = alarms
- .filter { it.isAlarmActive }
- .minByOrNull { alarm ->
- getNextAlarmDateWithTime(alarm.isAm, alarm.hour, alarm.minute, alarm.repeatDays)
- }
-
- val deliveryTime = earliestAlarm?.let { alarm ->
- val alarmDateTime = getNextAlarmDateWithTime(alarm.isAm, alarm.hour, alarm.minute, alarm.repeatDays)
- alarmDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm"))
- } ?: "NONE"
+ private fun updateDeliveryTime(alarms: List) = intent {
+ val deliveryTimeFormats = AlarmDateTimeFormatter.DeliveryTimeFormats(
+ noAlarm = resourceProvider.getString(R.string.home_fortune_no_alarm),
+ today = resourceProvider.getString(R.string.home_fortune_delivery_today, "%s"),
+ tomorrow = resourceProvider.getString(R.string.home_fortune_delivery_tomorrow, "%s"),
+ thisYear = resourceProvider.getString(R.string.home_fortune_delivery_this_year, "%s"),
+ otherYear = resourceProvider.getString(R.string.home_fortune_delivery_other_year, "%s"),
+ )
- updateState { copy(deliveryTime = formatDeliveryTime(deliveryTime)) }
+ val formattedTime = alarmDateTimeFormatter.getFormattedEarliestUpcomingAlarmDeliveryTime(
+ alarms = alarms,
+ formats = deliveryTimeFormats,
+ )
+ reduce { state.copy(deliveryTime = formattedTime) }
}
- private fun getNextAlarmDateWithTime(isAm: Boolean, hour: Int, minute: Int, repeatDays: Int): LocalDateTime {
- val now = LocalDateTime.now()
+ private fun loadDailyFortune() = intent {
+ val fortuneDate = fortuneRepository.fortuneDateEpochFlow.firstOrNull()
+ val todayDate = LocalDate.now().toEpochDay()
- val alarmHour = when {
- isAm && hour == 12 -> 0
- !isAm && hour != 12 -> hour + 12
- else -> hour
- }
- val alarmTime = LocalTime.of(alarmHour, minute)
- val todayAlarm = LocalDateTime.of(now.toLocalDate(), alarmTime)
-
- // ๋ฐ๋ณต ์์ผ์ด ์ค์ ๋์ง ์์ ๊ฒฝ์ฐ โ ๋จ์ผ ์๋
- if (repeatDays == 0) {
- return if (todayAlarm.isAfter(now)) todayAlarm else todayAlarm.plusDays(1)
+ if (fortuneDate != todayDate) {
+ processAction(HomeContract.Action.ShowNoDailyFortuneDialog)
+ } else {
+ fortuneRepository.markFortuneTooltipShown()
+ postSideEffect(HomeContract.SideEffect.NavigateToFortune)
}
+ }
- // ๋นํธ๋ง์คํฌ ๊ธฐ๋ฐ ๋ฐ๋ณต ์์ผ ์ถ์ถ
- val selectedDays = repeatDays.toAlarmDays().map { it.toDayOfWeek() }.sortedBy { it.value }
- val currentDayOfWeek = now.dayOfWeek
-
- // ๊ฐ์ฅ ๋น ๋ฅธ ๋ค์ ์๋ ๋ ์ง ๊ณ์ฐ
- val nextDayOffset = selectedDays
- .map { (it.value + 7 - currentDayOfWeek.value) % 7 }
- .filter { it > 0 || todayAlarm.isAfter(now) }
- .minOrNull() ?: (selectedDays.first().value + 7 - currentDayOfWeek.value)
-
- return todayAlarm.plusDays(nextDayOffset.toLong())
- }
-
- private fun formatDeliveryTime(deliveryTime: String): String {
- return try {
- if (deliveryTime == "NONE") return resourceProvider.getString(R.string.home_fortune_no_alarm)
-
- val inputDateTime = LocalDateTime.parse(deliveryTime, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm"))
- val now = LocalDateTime.now()
- val today = now.toLocalDate()
- val tomorrow = today.plusDays(1)
-
- return when {
- inputDateTime.toLocalDate() == today ->
- resourceProvider.getString(R.string.home_fortune_delivery_today, inputDateTime.format(DateTimeFormatter.ofPattern("a h:mm")))
- inputDateTime.toLocalDate() == tomorrow ->
- resourceProvider.getString(R.string.home_fortune_delivery_tomorrow, inputDateTime.format(DateTimeFormatter.ofPattern("a h:mm")))
- inputDateTime.year == now.year ->
- resourceProvider.getString(
- R.string.home_fortune_delivery_this_year,
- inputDateTime.format(DateTimeFormatter.ofPattern("M์ d์ผ a h:mm")),
- )
- else ->
- resourceProvider.getString(
- R.string.home_fortune_delivery_other_year,
- inputDateTime.format(DateTimeFormatter.ofPattern("yy๋
M์ d์ผ a h:mm")),
- )
+ private fun loadDailyFortuneState() = intent {
+ val todayDate = LocalDate.now().toEpochDay()
+
+ combine(
+ fortuneRepository.fortuneDateEpochFlow,
+ fortuneRepository.fortuneScoreFlow,
+ fortuneRepository.shouldShowFortuneToolTipFlow,
+ ) { fortuneDate, fortuneScore, shouldShowTooltip ->
+ val isTodayFortuneAvailable = fortuneDate == todayDate
+ val finalFortuneScore = if (isTodayFortuneAvailable) fortuneScore ?: -1 else -1
+
+ Pair(finalFortuneScore, shouldShowTooltip)
+ }.collect { (finalFortuneScore, hasNewFortune) ->
+ reduce {
+ state.copy(
+ lastFortuneScore = finalFortuneScore,
+ hasNewFortune = hasNewFortune,
+ isToolTipVisible = hasNewFortune,
+ )
}
- } catch (e: Exception) {
- resourceProvider.getString(R.string.home_fortune_no_alarm)
}
}
- private fun loadDailyFortune() {
- viewModelScope.launch {
- val fortuneDate = userPreferences.fortuneDateFlow.firstOrNull()
- val todayDate = LocalDate.now().format(DateTimeFormatter.ISO_DATE)
+ private fun loadUpdateNoticeVisibility() = intent {
+ if (!isOnlineNow()) {
+ reduce { state.copy(isUpdateNoticeVisible = false) }
+ return@intent
+ }
- Log.d("HomeViewModel", "fortuneDate: $fortuneDate, todayDate: $todayDate")
+ val dontShowVersion =
+ userInfoRepository.updateNoticeDontShowVersionFlow.firstOrNull()
+ val lastShownDate =
+ userInfoRepository.updateNoticeLastShownDateEpochFlow.firstOrNull()
- if (fortuneDate != todayDate) {
- processAction(HomeContract.Action.ShowNoDailyFortuneDialog)
- } else {
- userPreferences.markFortuneAsChecked()
- emitSideEffect(HomeContract.SideEffect.NavigateToFortune)
- }
+ val today = LocalDate.now().toEpochDay()
+
+ val shouldShow = when {
+ dontShowVersion != null && dontShowVersion == appVersion -> false
+ lastShownDate != null && lastShownDate == today -> false
+ else -> true
}
+
+ if (shouldShow) userInfoRepository.markUpdateNoticeShownToday()
+
+ reduce { state.copy(isUpdateNoticeVisible = shouldShow) }
}
- private fun loadDailyFortuneState() {
- viewModelScope.launch {
- val todayDate = LocalDate.now().format(DateTimeFormatter.ISO_DATE)
-
- combine(
- userPreferences.fortuneDateFlow,
- userPreferences.fortuneScoreFlow,
- userPreferences.hasNewFortuneFlow,
- ) { fortuneDate, fortuneScore, hasNewFortune ->
- val isTodayFortuneAvailable = fortuneDate == todayDate
- val finalFortuneScore = if (isTodayFortuneAvailable) fortuneScore ?: -1 else -1
-
- Pair(finalFortuneScore, hasNewFortune)
- }.collect { (finalFortuneScore, hasNewFortune) ->
- updateState {
- copy(
- lastFortuneScore = finalFortuneScore,
- hasNewFortune = hasNewFortune,
- isToolTipVisible = hasNewFortune,
- )
- }
- }
- }
+ private fun setUpdateNoticeDontShowVersion() = intent {
+ userInfoRepository.markUpdateNoticeDontShow(appVersion)
+ reduce { state.copy(isUpdateNoticeVisible = false) }
}
- private fun loadUserName() {
- viewModelScope.launch {
- userPreferences.userNameFlow.collect { userName ->
- updateState { copy(name = userName ?: "") }
- }
+ private fun hideUpdateNotice() = intent {
+ reduce { state.copy(isUpdateNoticeVisible = false) }
+ }
+
+ private fun loadUserName() = intent {
+ userInfoRepository.userNameFlow.first { userName ->
+ reduce { state.copy(name = userName ?: "") }
+ true
}
}
- private fun showNoDailyFortuneDialog() {
- updateState { copy(isNoDailyFortuneDialogVisible = true) }
+ private fun showNoDailyFortuneDialog() = intent {
+ reduce { state.copy(isNoDailyFortuneDialogVisible = true) }
}
- private fun hideNoDailyFortuneDialog() {
- updateState { copy(isNoDailyFortuneDialogVisible = false) }
+ private fun hideNoDailyFortuneDialog() = intent {
+ reduce { state.copy(isNoDailyFortuneDialogVisible = false) }
}
- private fun hideToolTip() {
- updateState { copy(isToolTipVisible = false) }
+ private fun hideToolTip() = intent {
+ reduce { state.copy(isToolTipVisible = false) }
}
- private fun navigateToSetting() {
- emitSideEffect(HomeContract.SideEffect.NavigateToSetting)
+ private fun navigateToSetting() = intent {
+ postSideEffect(HomeContract.SideEffect.NavigateToSetting)
}
- private fun showItemMenu(alarmId: Long, x: Float, y: Float) {
- updateState {
- copy(
+ private fun showItemMenu(alarmId: Long, x: Float, y: Float) = intent {
+ reduce {
+ state.copy(
activeItemMenu = alarmId,
activeItemMenuPosition = x to y,
)
}
}
- private fun hideItemMenu() {
- updateState {
- copy(
+ private fun hideItemMenu() = intent {
+ reduce {
+ state.copy(
activeItemMenu = null,
activeItemMenuPosition = null,
)
}
}
- private fun setSortOrder(sortOrder: HomeContract.AlarmSortOrder) {
- updateState { copy(sortOrder = sortOrder) }
+ private fun setSortOrder(sortOrder: HomeContract.AlarmSortOrder) = intent {
+ reduce { state.copy(sortOrder = sortOrder) }
hideDropDownMenu()
}
+
+ private fun isOnlineNow(): Boolean {
+ val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+ val network = cm.activeNetwork ?: return false
+ val caps = cm.getNetworkCapabilities(network) ?: return false
+
+ return caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
+ caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+ }
}
diff --git a/feature/home/src/main/java/com/yapp/alarm/AlarmDayLabel.kt b/feature/home/src/main/java/com/yapp/home/alarm/AlarmDayLabel.kt
similarity index 94%
rename from feature/home/src/main/java/com/yapp/alarm/AlarmDayLabel.kt
rename to feature/home/src/main/java/com/yapp/home/alarm/AlarmDayLabel.kt
index ee2e1b86..69acb835 100644
--- a/feature/home/src/main/java/com/yapp/alarm/AlarmDayLabel.kt
+++ b/feature/home/src/main/java/com/yapp/home/alarm/AlarmDayLabel.kt
@@ -1,4 +1,4 @@
-package com.yapp.alarm
+package com.yapp.home.alarm
import com.yapp.domain.model.AlarmDay
import feature.home.R
diff --git a/feature/home/src/main/java/com/yapp/alarm/addedit/AlarmAddEditContract.kt b/feature/home/src/main/java/com/yapp/home/alarm/addedit/AlarmAddEditContract.kt
similarity index 63%
rename from feature/home/src/main/java/com/yapp/alarm/addedit/AlarmAddEditContract.kt
rename to feature/home/src/main/java/com/yapp/home/alarm/addedit/AlarmAddEditContract.kt
index de6da472..0f128d45 100644
--- a/feature/home/src/main/java/com/yapp/alarm/addedit/AlarmAddEditContract.kt
+++ b/feature/home/src/main/java/com/yapp/home/alarm/addedit/AlarmAddEditContract.kt
@@ -1,12 +1,14 @@
-package com.yapp.alarm.addedit
+package com.yapp.home.alarm.addedit
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.yapp.domain.model.Alarm
import com.yapp.domain.model.AlarmDay
import com.yapp.domain.model.AlarmSound
+import com.yapp.domain.model.MissionType
import com.yapp.domain.model.toRepeatDays
import com.yapp.ui.base.UiState
+import java.time.LocalTime
sealed class AlarmAddEditContract {
@@ -16,6 +18,7 @@ sealed class AlarmAddEditContract {
val timeState: AlarmTimeState = AlarmTimeState(),
val daySelectionState: AlarmDaySelectionState = AlarmDaySelectionState(),
val holidayState: AlarmHolidayState = AlarmHolidayState(),
+ val missionState: AlarmMissionState = AlarmMissionState(),
val snoozeState: AlarmSnoozeState = AlarmSnoozeState(),
val soundState: AlarmSoundState = AlarmSoundState(),
val bottomSheetState: BottomSheetType? = null,
@@ -24,12 +27,8 @@ sealed class AlarmAddEditContract {
) : UiState
data class AlarmTimeState(
- val initialAmPm: String = "์ค์ ",
- val initialHour: String = "1",
- val initialMinute: String = "00",
- val currentAmPm: String = "์ค์ ",
- val currentHour: Int = 1,
- val currentMinute: Int = 0,
+ val initialTime: LocalTime = LocalTime.of(1, 0),
+ val currentTime: LocalTime = LocalTime.of(1, 0),
val alarmMessage: String = "",
)
@@ -45,12 +44,15 @@ sealed class AlarmAddEditContract {
val isDisableHolidayChecked: Boolean = false,
)
+ data class AlarmMissionState(
+ val missionType: MissionType = MissionType.TAP,
+ val missionCount: Int = 10,
+ )
+
data class AlarmSnoozeState(
val isSnoozeEnabled: Boolean = true,
- val snoozeIntervalIndex: Int = 2,
- val snoozeCountIndex: Int = 2,
- val snoozeIntervals: List = listOf("1๋ถ", "3๋ถ", "5๋ถ", "10๋ถ", "15๋ถ"),
- val snoozeCounts: List = listOf("1ํ", "3ํ", "5ํ", "10ํ", "๋ฌดํ"),
+ val snoozeInterval: Int = 5,
+ val snoozeCount: Int = 5,
)
data class AlarmSoundState(
@@ -74,22 +76,34 @@ sealed class AlarmAddEditContract {
data object ShowUnsavedChangesDialog : Action()
data object HideUnsavedChangesDialog : Action()
data object DeleteAlarm : Action()
- data class SetAlarmTime(val amPm: String, val hour: Int, val minute: Int) : Action()
+ data class SetAlarmTime(val newTime: LocalTime) : Action()
data object ToggleWeekdaysSelection : Action()
data object ToggleWeekendsSelection : Action()
data class ToggleSpecificDaySelection(val day: AlarmDay) : Action()
data object ToggleHolidaySkipOption : Action()
- data object ToggleSnoozeOption : Action()
- data class SetSnoozeInterval(val index: Int) : Action()
- data class SetSnoozeRepeatCount(val index: Int) : Action()
- data object ToggleVibrationOption : Action()
- data object ToggleSoundOption : Action()
- data class AdjustSoundVolume(val volume: Int) : Action()
- data class SelectAlarmSound(val index: Int) : Action()
- data class ToggleBottomSheet(val sheetType: BottomSheetType) : Action()
+ data class SaveMissionSetting(val type: MissionType, val count: Int) : Action()
+ data class SaveSnoozeSetting(
+ val enabled: Boolean,
+ val interval: Int,
+ val count: Int,
+ ) : Action()
+ data class SaveSoundSetting(
+ val vibrationEnabled: Boolean,
+ val soundEnabled: Boolean,
+ val soundVolume: Int,
+ val soundIndex: Int,
+ ) : Action()
+ data class ToggleVibrationEnabled(val enabled: Boolean) : Action()
+ data class ToggleSoundEnabled(val enabled: Boolean) : Action()
+ data class SetSoundVolume(val volume: Int) : Action()
+ data class SetSoundIndex(val index: Int) : Action()
+ data class ShowBottomSheet(val sheetType: BottomSheetType) : Action()
+ data class NavigateToMissionPreview(val missionType: MissionType, val missionCount: Int) : Action()
+ data object HideBottomSheet : Action()
}
sealed class BottomSheetType {
+ data object MissionSetting : BottomSheetType()
data object SnoozeSetting : BottomSheetType()
data object SoundSetting : BottomSheetType()
}
@@ -97,6 +111,17 @@ sealed class AlarmAddEditContract {
sealed class SideEffect : com.yapp.ui.base.SideEffect {
data object NavigateBack : SideEffect()
+ data class NavigateToMissionPreview(
+ val missionType: MissionType,
+ val missionCount: Int,
+ ) : SideEffect()
+
+ data class ShowBottomSheet(
+ val sheetType: BottomSheetType,
+ ) : SideEffect()
+
+ data object HideBottomSheet : SideEffect()
+
data class SaveAlarm(val id: Long) : SideEffect()
data class UpdateAlarm(val id: Long) : SideEffect()
@@ -118,19 +143,15 @@ sealed class AlarmAddEditContract {
internal fun AlarmAddEditContract.State.toAlarm(id: Long = 0): Alarm {
return Alarm(
id = id,
- isAm = timeState.currentAmPm == "์ค์ ",
- hour = timeState.currentHour,
- minute = timeState.currentMinute,
+ hour = timeState.currentTime.hour,
+ minute = timeState.currentTime.minute,
repeatDays = daySelectionState.selectedDays.toRepeatDays(),
isHolidayAlarmOff = holidayState.isDisableHolidayChecked,
+ missionType = missionState.missionType,
+ missionCount = missionState.missionCount,
isSnoozeEnabled = snoozeState.isSnoozeEnabled,
- snoozeInterval = snoozeState.snoozeIntervals.getOrNull(snoozeState.snoozeIntervalIndex)
- ?.filter { it.isDigit() }
- ?.toIntOrNull()
- ?: 5,
- snoozeCount = snoozeState.snoozeCounts.getOrNull(snoozeState.snoozeCountIndex)
- ?.let { if (it == "๋ฌดํ") -1 else it.filter { char -> char.isDigit() }.toIntOrNull() ?: 1 }
- ?: 1,
+ snoozeInterval = snoozeState.snoozeInterval,
+ snoozeCount = snoozeState.snoozeCount,
isVibrationEnabled = soundState.isVibrationEnabled,
isSoundEnabled = soundState.isSoundEnabled,
soundUri = soundState.sounds.getOrNull(soundState.soundIndex)?.uri.toString(),
diff --git a/feature/home/src/main/java/com/yapp/home/alarm/addedit/AlarmAddEditScreen.kt b/feature/home/src/main/java/com/yapp/home/alarm/addedit/AlarmAddEditScreen.kt
new file mode 100644
index 00000000..9d726ae3
--- /dev/null
+++ b/feature/home/src/main/java/com/yapp/home/alarm/addedit/AlarmAddEditScreen.kt
@@ -0,0 +1,815 @@
+package com.yapp.home.alarm.addedit
+
+import android.net.Uri
+import androidx.activity.compose.BackHandler
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.collectIsPressedAsState
+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.foundation.layout.statusBarsPadding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.ripple.RippleAlpha
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.LocalRippleConfiguration
+import androidx.compose.material3.RippleConfiguration
+import androidx.compose.material3.SnackbarHostState
+import androidx.compose.material3.SnackbarResult
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.material3.ripple
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+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 com.yapp.common.navigation.OrbitNavigator
+import com.yapp.designsystem.theme.OrbitTheme
+import com.yapp.domain.model.AlarmDay
+import com.yapp.domain.model.AlarmSound
+import com.yapp.domain.model.MissionType
+import com.yapp.home.ADD_ALARM_RESULT_KEY
+import com.yapp.home.DELETE_ALARM_RESULT_KEY
+import com.yapp.home.UPDATE_ALARM_RESULT_KEY
+import com.yapp.home.alarm.component.AlarmCheckItem
+import com.yapp.home.alarm.component.AlarmDayButton
+import com.yapp.home.alarm.component.bottomsheet.AlarmMissionBottomSheet
+import com.yapp.home.alarm.component.bottomsheet.AlarmSnoozeBottomSheet
+import com.yapp.home.alarm.component.bottomsheet.AlarmSoundBottomSheet
+import com.yapp.home.alarm.getLabelStringRes
+import com.yapp.ui.component.bottomsheet.OrbitBottomSheetLayout
+import com.yapp.ui.component.bottomsheet.OrbitBottomSheetState
+import com.yapp.ui.component.bottomsheet.rememberOrbitBottomSheetState
+import com.yapp.ui.component.button.OrbitButton
+import com.yapp.ui.component.dialog.OrbitDialog
+import com.yapp.ui.component.lottie.LottieAnimation
+import com.yapp.ui.component.snackbar.showCustomSnackBar
+import com.yapp.ui.component.switch.OrbitSwitch
+import com.yapp.ui.component.timepicker.OrbitPicker
+import feature.home.R
+import kotlinx.coroutines.CoroutineScope
+import org.orbitmvi.orbit.compose.collectSideEffect
+import java.time.LocalTime
+
+@Composable
+fun AlarmAddEditRoute(
+ viewModel: AlarmAddEditViewModel = hiltViewModel(),
+ navigator: OrbitNavigator,
+ bottomSheetState: OrbitBottomSheetState,
+ snackBarHostState: SnackbarHostState,
+) {
+ val state by viewModel.container.stateFlow.collectAsStateWithLifecycle()
+
+ val coroutineScope = rememberCoroutineScope()
+
+ viewModel.collectSideEffect { sideEffect ->
+ handleSideEffect(
+ sideEffect = sideEffect,
+ navigator = navigator,
+ bottomSheetState = bottomSheetState,
+ snackBarHostState = snackBarHostState,
+ coroutineScope = coroutineScope,
+ state = state,
+ processAction = viewModel::processAction,
+ )
+ }
+
+ AlarmAddEditScreen(
+ state = state,
+ bottomSheetState = bottomSheetState,
+ processAction = viewModel::processAction,
+ )
+}
+
+private suspend fun handleSideEffect(
+ sideEffect: AlarmAddEditContract.SideEffect,
+ navigator: OrbitNavigator,
+ bottomSheetState: OrbitBottomSheetState,
+ snackBarHostState: SnackbarHostState,
+ coroutineScope: CoroutineScope,
+ state: AlarmAddEditContract.State,
+ processAction: (AlarmAddEditContract.Action) -> Unit,
+) {
+ when (sideEffect) {
+ is AlarmAddEditContract.SideEffect.NavigateBack -> {
+ navigator.navigateBack()
+ }
+
+ is AlarmAddEditContract.SideEffect.NavigateToMissionPreview -> {
+ navigator.navigateToMissionPreview(
+ missionType = sideEffect.missionType.value,
+ missionCount = sideEffect.missionCount,
+ )
+ }
+
+ is AlarmAddEditContract.SideEffect.ShowBottomSheet -> {
+ bottomSheetState.show {
+ when (sideEffect.sheetType) {
+ AlarmAddEditContract.BottomSheetType.MissionSetting -> {
+ AlarmMissionBottomSheet(
+ missionState = state.missionState,
+ onDismiss = {
+ processAction(AlarmAddEditContract.Action.HideBottomSheet)
+ },
+ onSaveMission = { missionType, missionCount ->
+ processAction(
+ AlarmAddEditContract.Action.SaveMissionSetting(
+ type = missionType,
+ count = missionCount,
+ ),
+ )
+ },
+ onPreviewMission = { missionType, missionCount ->
+ processAction(
+ AlarmAddEditContract.Action.NavigateToMissionPreview(
+ missionType = missionType,
+ missionCount = missionCount,
+ ),
+ )
+ },
+ )
+ }
+
+ AlarmAddEditContract.BottomSheetType.SnoozeSetting -> {
+ AlarmSnoozeBottomSheet(
+ snoozeState = state.snoozeState,
+ onDismiss = {
+ processAction(AlarmAddEditContract.Action.HideBottomSheet)
+ },
+ onComplete = { enabled, interval, count ->
+ processAction(
+ AlarmAddEditContract.Action.SaveSnoozeSetting(
+ enabled = enabled,
+ interval = interval,
+ count = count,
+ ),
+ )
+ processAction(AlarmAddEditContract.Action.HideBottomSheet)
+ },
+ )
+ }
+
+ AlarmAddEditContract.BottomSheetType.SoundSetting -> {
+ AlarmSoundBottomSheet(
+ soundState = state.soundState,
+ onVibrationToggle = { enabled ->
+ processAction(AlarmAddEditContract.Action.ToggleVibrationEnabled(enabled))
+ },
+ onSoundToggle = { enabled ->
+ processAction(AlarmAddEditContract.Action.ToggleSoundEnabled(enabled))
+ },
+ onVolumeChanged = { volume ->
+ processAction(AlarmAddEditContract.Action.SetSoundVolume(volume))
+ },
+ onSoundSelected = { index ->
+ processAction(AlarmAddEditContract.Action.SetSoundIndex(index))
+ },
+ onDismiss = {
+ processAction(AlarmAddEditContract.Action.HideBottomSheet)
+ },
+ onComplete = { vibrationEnabled, soundEnabled, soundVolume, soundIndex ->
+ processAction(
+ AlarmAddEditContract.Action.SaveSoundSetting(
+ vibrationEnabled = vibrationEnabled,
+ soundEnabled = soundEnabled,
+ soundVolume = soundVolume,
+ soundIndex = soundIndex,
+ ),
+ )
+ },
+ )
+ }
+ }
+ }
+ }
+
+ is AlarmAddEditContract.SideEffect.HideBottomSheet -> {
+ bottomSheetState.hide()
+ }
+
+ is AlarmAddEditContract.SideEffect.SaveAlarm -> {
+ navigator.navController.previousBackStackEntry
+ ?.savedStateHandle
+ ?.set(ADD_ALARM_RESULT_KEY, sideEffect.id)
+ navigator.navController.popBackStack()
+ }
+
+ is AlarmAddEditContract.SideEffect.UpdateAlarm -> {
+ navigator.navController.previousBackStackEntry
+ ?.savedStateHandle
+ ?.set(UPDATE_ALARM_RESULT_KEY, sideEffect.id)
+ navigator.navigateBack()
+ }
+
+ is AlarmAddEditContract.SideEffect.DeleteAlarm -> {
+ navigator.navController.previousBackStackEntry
+ ?.savedStateHandle
+ ?.set(DELETE_ALARM_RESULT_KEY, sideEffect.id)
+ navigator.navigateBack()
+ }
+
+ is AlarmAddEditContract.SideEffect.ShowSnackBar -> {
+ val result = showCustomSnackBar(
+ scope = coroutineScope,
+ snackBarHostState = snackBarHostState,
+ message = sideEffect.message,
+ actionLabel = sideEffect.label,
+ iconRes = sideEffect.iconRes,
+ bottomPadding = sideEffect.bottomPadding,
+ durationMillis = sideEffect.durationMillis,
+ )
+
+ when (result) {
+ SnackbarResult.ActionPerformed -> sideEffect.onAction()
+ SnackbarResult.Dismissed -> sideEffect.onDismiss()
+ }
+ }
+ }
+}
+
+@Composable
+fun AlarmAddEditScreen(
+ state: AlarmAddEditContract.State,
+ bottomSheetState: OrbitBottomSheetState,
+ processAction: (AlarmAddEditContract.Action) -> Unit,
+) {
+ if (state.initialLoading) {
+ AlarmAddEditLoadingScreen()
+ } else {
+ AlarmAddEditContent(
+ state = state,
+ bottomSheetState = bottomSheetState,
+ processAction = processAction,
+ )
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun AlarmAddEditContent(
+ state: AlarmAddEditContract.State,
+ bottomSheetState: OrbitBottomSheetState,
+ processAction: (AlarmAddEditContract.Action) -> Unit,
+) {
+ BackHandler {
+ if (bottomSheetState.state.isVisible) {
+ processAction(AlarmAddEditContract.Action.HideBottomSheet)
+ } else {
+ processAction(AlarmAddEditContract.Action.CheckUnsavedChangesBeforeExit)
+ }
+ }
+
+ OrbitBottomSheetLayout(sheetState = bottomSheetState) {
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ AlarmAddEditTopBar(
+ mode = state.mode,
+ title = state.timeState.alarmMessage,
+ onBack = { processAction(AlarmAddEditContract.Action.CheckUnsavedChangesBeforeExit) },
+ onDelete = { processAction(AlarmAddEditContract.Action.ShowDeleteDialog) },
+ )
+ Box(
+ modifier = Modifier.weight(1f),
+ contentAlignment = Alignment.Center,
+ ) {
+ OrbitPicker(
+ initialTime = state.timeState.initialTime,
+ ) { newTime ->
+ processAction(AlarmAddEditContract.Action.SetAlarmTime(newTime))
+ }
+ }
+ AlarmAddEditSelectDaysSection(
+ modifier = Modifier.padding(horizontal = 20.dp),
+ daysSelectionState = state.daySelectionState,
+ holidayState = state.holidayState,
+ processAction = processAction,
+ )
+ Spacer(modifier = Modifier.height(12.dp))
+ AlarmAddEditSettingsSection(
+ modifier = Modifier.padding(horizontal = 20.dp),
+ state = state,
+ processAction = processAction,
+ )
+ Spacer(modifier = Modifier.height(24.dp))
+ OrbitButton(
+ label = stringResource(R.string.alarm_add_edit_save),
+ onClick = { processAction(AlarmAddEditContract.Action.SaveAlarm) },
+ enabled = true,
+ modifier = Modifier
+ .padding(
+ start = 20.dp,
+ end = 20.dp,
+ bottom = 12.dp,
+ ),
+ )
+ }
+ }
+
+ if (state.isDeleteDialogVisible) {
+ OrbitDialog(
+ title = stringResource(id = R.string.alarm_delete_dialog_title),
+ message = stringResource(id = R.string.alarm_delete_dialog_message),
+ confirmText = stringResource(id = R.string.alarm_delete_dialog_btn_delete),
+ cancelText = stringResource(id = R.string.alarm_delete_dialog_btn_cancel),
+ onConfirm = {
+ processAction(AlarmAddEditContract.Action.DeleteAlarm)
+ },
+ onCancel = {
+ processAction(AlarmAddEditContract.Action.HideDeleteDialog)
+ },
+ )
+ }
+
+ if (state.isUnsavedChangesDialogVisible) {
+ OrbitDialog(
+ title = stringResource(id = R.string.alarm_unsaved_changes_dialog_title),
+ message = stringResource(id = R.string.alarm_unsaved_changes_dialog_message),
+ confirmText = stringResource(id = R.string.alarm_unsaved_changes_dialog_btn_discard),
+ cancelText = stringResource(id = R.string.alarm_unsaved_changes_dialog_btn_cancel),
+ onConfirm = {
+ processAction(AlarmAddEditContract.Action.NavigateBack)
+ },
+ onCancel = {
+ processAction(AlarmAddEditContract.Action.HideUnsavedChangesDialog)
+ },
+ )
+ }
+}
+
+@Composable
+private fun AlarmAddEditLoadingScreen() {
+ Box(
+ modifier = Modifier.fillMaxSize(),
+ contentAlignment = Alignment.Center,
+ ) {
+ LottieAnimation(
+ modifier = Modifier
+ .size(70.dp)
+ .align(Alignment.Center),
+ resId = core.designsystem.R.raw.star_loading,
+ )
+ }
+}
+
+@Composable
+private fun AlarmAddEditTopBar(
+ mode: AlarmAddEditContract.EditMode = AlarmAddEditContract.EditMode.ADD,
+ title: String,
+ onBack: () -> Unit,
+ onDelete: () -> Unit,
+) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .statusBarsPadding()
+ .height(56.dp),
+ contentAlignment = Alignment.Center,
+ ) {
+ Icon(
+ painter = painterResource(id = core.designsystem.R.drawable.ic_back),
+ contentDescription = "Back",
+ tint = OrbitTheme.colors.white,
+ modifier = Modifier
+ .clickable(onClick = onBack)
+ .padding(start = 20.dp)
+ .align(Alignment.CenterStart),
+ )
+
+ Text(
+ title,
+ style = OrbitTheme.typography.body1SemiBold,
+ color = OrbitTheme.colors.white,
+ )
+
+ if (mode == AlarmAddEditContract.EditMode.EDIT) {
+ DeleteAlarmButton(
+ modifier = Modifier
+ .align(Alignment.CenterEnd)
+ .padding(end = 20.dp),
+ ) {
+ onDelete()
+ }
+ }
+ }
+}
+
+@Composable
+private fun DeleteAlarmButton(
+ modifier: Modifier = Modifier,
+ onDelete: () -> Unit,
+) {
+ val interactionSource = remember { MutableInteractionSource() }
+ val isPressed = interactionSource.collectIsPressedAsState().value
+
+ Surface(
+ onClick = onDelete,
+ modifier = modifier,
+ shape = RoundedCornerShape(8.dp),
+ interactionSource = interactionSource,
+ color = if (isPressed) OrbitTheme.colors.gray_800 else Color.Transparent,
+ ) {
+ Text(
+ text = stringResource(id = R.string.alarm_add_edit_delete),
+ style = OrbitTheme.typography.body1Medium,
+ color = OrbitTheme.colors.alert,
+ modifier = Modifier
+ .padding(
+ horizontal = 8.dp,
+ vertical = 4.dp,
+ ),
+ )
+ }
+}
+
+@Composable
+private fun AlarmAddEditSettingsSection(
+ modifier: Modifier = Modifier,
+ state: AlarmAddEditContract.State,
+ processAction: (AlarmAddEditContract.Action) -> Unit,
+) {
+ Column(
+ modifier = modifier
+ .fillMaxWidth()
+ .background(
+ color = OrbitTheme.colors.gray_800,
+ shape = RoundedCornerShape(12.dp),
+ )
+ .clip(
+ shape = RoundedCornerShape(12.dp),
+ ),
+ ) {
+ AlarmAddEditSettingItem(
+ label = stringResource(id = R.string.alarm_add_edit_mission),
+ description = when (state.missionState.missionType) {
+ MissionType.TAP -> {
+ val missionType = stringResource(id = R.string.alarm_add_edit_selected_mission_tap)
+ val missionCount = state.missionState.missionCount
+ stringResource(
+ id = R.string.alarm_add_edit_selected_mission_with_count,
+ missionType,
+ missionCount,
+ )
+ }
+ MissionType.SHAKE -> {
+ val missionType = stringResource(id = R.string.alarm_add_edit_selected_mission_shake)
+ val missionCount = state.missionState.missionCount
+ stringResource(
+ id = R.string.alarm_add_edit_selected_mission_with_count,
+ missionType,
+ missionCount,
+ )
+ }
+ else -> stringResource(id = R.string.alarm_add_edit_selected_mission_none)
+ },
+ onClick = {
+ processAction(
+ AlarmAddEditContract.Action.ShowBottomSheet(
+ AlarmAddEditContract.BottomSheetType.MissionSetting,
+ ),
+ )
+ },
+ )
+ Spacer(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(1.dp)
+ .padding(horizontal = 20.dp)
+ .background(OrbitTheme.colors.gray_700),
+ )
+ AlarmAddEditSettingItem(
+ label = stringResource(id = R.string.alarm_add_edit_alarm_snooze),
+ description = if (state.snoozeState.isSnoozeEnabled) {
+ val interval = stringResource(
+ id = R.string.alarm_add_edit_interval_minute,
+ state.snoozeState.snoozeInterval,
+ )
+ val count = if (state.snoozeState.snoozeCount == -1) {
+ stringResource(id = R.string.alarm_add_edit_repeat_count_infinite)
+ } else {
+ stringResource(
+ id = R.string.alarm_add_edit_repeat_count_times,
+ state.snoozeState.snoozeCount,
+ )
+ }
+ stringResource(
+ id = R.string.alarm_add_edit_alarm_selected_option,
+ interval,
+ count,
+ )
+ } else {
+ stringResource(id = R.string.alarm_add_edit_alarm_selected_option_none)
+ },
+ onClick = {
+ processAction(
+ AlarmAddEditContract.Action.ShowBottomSheet(
+ AlarmAddEditContract.BottomSheetType.SnoozeSetting,
+ ),
+ )
+ },
+ )
+ Spacer(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(1.dp)
+ .padding(horizontal = 20.dp)
+ .background(OrbitTheme.colors.gray_700),
+ )
+ AlarmAddEditSettingItem(
+ label = stringResource(id = R.string.alarm_add_edit_sound),
+ description = when {
+ state.soundState.isSoundEnabled && state.soundState.isVibrationEnabled -> {
+ "${stringResource(id = R.string.alarm_add_edit_vibration)}, ${
+ state.soundState.sounds.getOrElse(state.soundState.soundIndex) {
+ AlarmSound("", Uri.EMPTY)
+ }.title
+ }"
+ }
+
+ state.soundState.isSoundEnabled -> state.soundState.sounds.getOrElse(state.soundState.soundIndex) {
+ AlarmSound(
+ "",
+ Uri.EMPTY,
+ )
+ }.title
+
+ state.soundState.isVibrationEnabled -> stringResource(id = R.string.alarm_add_edit_vibration)
+ else -> stringResource(id = R.string.alarm_add_edit_alarm_selected_option_none)
+ },
+ onClick = {
+ processAction(
+ AlarmAddEditContract.Action.ShowBottomSheet(
+ AlarmAddEditContract.BottomSheetType.SoundSetting,
+ ),
+ )
+ },
+ )
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+private fun AlarmAddEditSettingItem(
+ label: String,
+ description: String,
+ onClick: () -> Unit,
+) {
+ val interactionSource = remember { MutableInteractionSource() }
+
+ CompositionLocalProvider(
+ LocalRippleConfiguration provides RippleConfiguration(
+ rippleAlpha = RippleAlpha(
+ pressedAlpha = 1f,
+ focusedAlpha = 1f,
+ hoveredAlpha = 1f,
+ draggedAlpha = 1f,
+ ),
+ ),
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable(
+ interactionSource = interactionSource,
+ indication = ripple(
+ color = OrbitTheme.colors.gray_700,
+ ),
+ ) {
+ onClick()
+ }
+ .padding(
+ horizontal = 20.dp,
+ vertical = 14.dp,
+ ),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Text(
+ label,
+ modifier = Modifier.width(80.dp),
+ style = OrbitTheme.typography.body1SemiBold,
+ color = OrbitTheme.colors.white,
+ )
+ Text(
+ description,
+ modifier = Modifier.weight(1f),
+ style = OrbitTheme.typography.body2Regular,
+ color = OrbitTheme.colors.gray_50,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ textAlign = TextAlign.End,
+ )
+ Icon(
+ painter = painterResource(id = core.designsystem.R.drawable.ic_arrow_right),
+ contentDescription = "Arrow",
+ tint = OrbitTheme.colors.gray_300,
+ )
+ }
+ }
+}
+
+@Composable
+private fun AlarmAddEditSelectDaysSection(
+ modifier: Modifier = Modifier,
+ daysSelectionState: AlarmAddEditContract.AlarmDaySelectionState,
+ holidayState: AlarmAddEditContract.AlarmHolidayState,
+ processAction: (AlarmAddEditContract.Action) -> Unit,
+) {
+ val configuration = LocalConfiguration.current
+ val screenWidthDp = configuration.screenWidthDp.dp
+
+ Column(
+ modifier = modifier
+ .fillMaxWidth()
+ .background(
+ color = OrbitTheme.colors.gray_800,
+ shape = RoundedCornerShape(12.dp),
+ )
+ .clip(
+ shape = RoundedCornerShape(12.dp),
+ ),
+ verticalArrangement = Arrangement.spacedBy(20.dp),
+ ) {
+ Column(
+ modifier = Modifier.padding(horizontal = 20.dp, vertical = 16.dp),
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Text(
+ text = stringResource(id = R.string.alarm_add_edit_repeat),
+ style = OrbitTheme.typography.body1SemiBold,
+ color = OrbitTheme.colors.white,
+ )
+
+ Spacer(modifier = Modifier.weight(1f))
+
+ AlarmCheckItem(
+ label = stringResource(id = R.string.alarm_add_edit_weekdays),
+ isPressed = daysSelectionState.isWeekdaysChecked,
+ onClick = {
+ processAction(AlarmAddEditContract.Action.ToggleWeekdaysSelection)
+ },
+ )
+ Spacer(modifier = Modifier.width(2.dp))
+ AlarmCheckItem(
+ label = stringResource(id = R.string.alarm_add_edit_weekends),
+ isPressed = daysSelectionState.isWeekendsChecked,
+ onClick = {
+ processAction(AlarmAddEditContract.Action.ToggleWeekendsSelection)
+ },
+ )
+ }
+
+ Spacer(modifier = Modifier.height(12.dp))
+
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ ) {
+ daysSelectionState.days.forEach { day ->
+ AlarmDayButton(
+ modifier = Modifier.size(
+ if (screenWidthDp > 360.dp) 36.dp else 34.dp,
+ ),
+ label = stringResource(id = day.getLabelStringRes()),
+ isPressed = daysSelectionState.selectedDays.contains(day),
+ onClick = {
+ processAction(AlarmAddEditContract.Action.ToggleSpecificDaySelection(day))
+ },
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.height(20.dp))
+
+ AlarmAddEditDisableHolidaySwitch(
+ state = holidayState,
+ processAction = processAction,
+ )
+ }
+ }
+}
+
+@Composable
+private fun AlarmAddEditDisableHolidaySwitch(
+ modifier: Modifier = Modifier,
+ state: AlarmAddEditContract.AlarmHolidayState,
+ processAction: (AlarmAddEditContract.Action) -> Unit,
+) {
+ Row(
+ modifier = modifier,
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Icon(
+ painter = painterResource(id = core.designsystem.R.drawable.ic_holiday),
+ contentDescription = "Holiday",
+ tint = OrbitTheme.colors.gray_400,
+ modifier = Modifier.padding(end = 4.dp),
+ )
+ Text(
+ text = stringResource(id = R.string.alarm_add_edit_disable_holiday),
+ style = OrbitTheme.typography.label1Medium,
+ color = OrbitTheme.colors.gray_400,
+ )
+
+ Spacer(modifier = Modifier.weight(1f))
+
+ OrbitSwitch(
+ isChecked = state.isDisableHolidayChecked,
+ isEnabled = state.isDisableHolidayEnabled,
+ onClick = {
+ processAction(AlarmAddEditContract.Action.ToggleHolidaySkipOption)
+ },
+ )
+ }
+}
+
+@Preview
+@Composable
+fun AlarmAddEditSettingsSectionPreview() {
+ AlarmAddEditSettingsSection(
+ state = AlarmAddEditContract.State(
+ timeState = AlarmAddEditContract.AlarmTimeState(
+ currentTime = LocalTime.of(19, 30),
+ ),
+ daySelectionState = AlarmAddEditContract.AlarmDaySelectionState(
+ isWeekdaysChecked = true,
+ isWeekendsChecked = false,
+ selectedDays = setOf(AlarmDay.MON, AlarmDay.TUE),
+ days = AlarmDay.entries.toSet(),
+ ),
+ holidayState = AlarmAddEditContract.AlarmHolidayState(
+ isDisableHolidayChecked = false,
+ ),
+ ),
+ processAction = { },
+ )
+}
+
+@Preview
+@Composable
+fun AlarmAddEditSettingItemPreview() {
+ AlarmAddEditSettingItem(
+ label = "์๋ ๋ฏธ๋ฃจ๊ธฐ",
+ description = "5๋ถ, ๋ฌดํ",
+ onClick = { },
+ )
+}
+
+@Preview
+@Composable
+fun AlarmAddEditScreenPreview() {
+ OrbitTheme {
+ Box(
+ modifier = Modifier.background(
+ color = OrbitTheme.colors.gray_900,
+ ),
+ ) {
+ AlarmAddEditScreen(
+ state = AlarmAddEditContract.State(
+ initialLoading = false,
+ timeState = AlarmAddEditContract.AlarmTimeState(
+ currentTime = LocalTime.of(19, 30),
+ ),
+ daySelectionState = AlarmAddEditContract.AlarmDaySelectionState(
+ isWeekdaysChecked = true,
+ isWeekendsChecked = false,
+ selectedDays = setOf(AlarmDay.MON, AlarmDay.TUE),
+ days = AlarmDay.entries.toSet(),
+ ),
+ holidayState = AlarmAddEditContract.AlarmHolidayState(
+ isDisableHolidayChecked = false,
+ ),
+ ),
+ bottomSheetState = rememberOrbitBottomSheetState(),
+ processAction = { },
+ )
+ }
+ }
+}
diff --git a/feature/home/src/main/java/com/yapp/home/alarm/addedit/AlarmAddEditViewModel.kt b/feature/home/src/main/java/com/yapp/home/alarm/addedit/AlarmAddEditViewModel.kt
new file mode 100644
index 00000000..b0f8dfb5
--- /dev/null
+++ b/feature/home/src/main/java/com/yapp/home/alarm/addedit/AlarmAddEditViewModel.kt
@@ -0,0 +1,553 @@
+package com.yapp.home.alarm.addedit
+
+import android.util.Log
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModel
+import com.yapp.analytics.AnalyticsEvent
+import com.yapp.analytics.AnalyticsHelper
+import com.yapp.common.util.ResourceProvider
+import com.yapp.domain.model.Alarm
+import com.yapp.domain.model.AlarmDay
+import com.yapp.domain.model.AlarmSound
+import com.yapp.domain.model.MissionType
+import com.yapp.domain.model.copyFrom
+import com.yapp.domain.model.toAlarmDayNames
+import com.yapp.domain.model.toAlarmDays
+import com.yapp.domain.model.toRepeatDays
+import com.yapp.domain.usecase.AlarmUseCase
+import com.yapp.home.util.AlarmDateTimeFormatter
+import com.yapp.media.haptic.HapticFeedbackManager
+import com.yapp.media.haptic.HapticType
+import dagger.hilt.android.lifecycle.HiltViewModel
+import feature.home.R
+import kotlinx.coroutines.flow.first
+import org.orbitmvi.orbit.Container
+import org.orbitmvi.orbit.ContainerHost
+import org.orbitmvi.orbit.syntax.simple.intent
+import org.orbitmvi.orbit.syntax.simple.postSideEffect
+import org.orbitmvi.orbit.syntax.simple.reduce
+import org.orbitmvi.orbit.viewmodel.container
+import java.time.LocalDateTime
+import java.time.LocalTime
+import javax.inject.Inject
+
+@HiltViewModel
+class AlarmAddEditViewModel @Inject constructor(
+ private val analyticsHelper: AnalyticsHelper,
+ private val alarmUseCase: AlarmUseCase,
+ private val resourceProvider: ResourceProvider,
+ private val alarmDateTimeFormatter: AlarmDateTimeFormatter,
+ private val hapticFeedbackManager: HapticFeedbackManager,
+ savedStateHandle: SavedStateHandle,
+) : ViewModel(), ContainerHost {
+
+ override val container: Container = container(initialState = AlarmAddEditContract.State()) {
+ intent {
+ reduce { state.copy(mode = if (alarmId == -1L) AlarmAddEditContract.EditMode.ADD else AlarmAddEditContract.EditMode.EDIT) }
+ initializeAlarmScreen()
+ }
+ }
+
+ private val alarmId: Long = savedStateHandle.get("alarmId") ?: -1
+
+ private fun initializeAlarmScreen() = intent {
+ alarmUseCase.getAlarmSounds().onSuccess { sounds ->
+ if (alarmId == -1L) {
+ setupNewAlarmScreen(sounds)
+ } else {
+ loadExistingAlarm(sounds)
+ }
+ }.onFailure {
+ Log.e("AlarmAddEditViewModel", "Failed to load alarm sounds", it)
+ }
+ }
+
+ private fun setupNewAlarmScreen(sounds: List) = intent {
+ val defaultSoundIndex = sounds.indexOfFirst { it.title == "Homecoming" }.takeIf { it >= 0 } ?: 0
+ val defaultSound = sounds[defaultSoundIndex]
+
+ alarmUseCase.initializeSoundPlayer(defaultSound.uri)
+
+ val now = LocalTime.now()
+
+ reduce {
+ state.copy(
+ initialLoading = false,
+ timeState = state.timeState.copy(
+ initialTime = now,
+ currentTime = now,
+ alarmMessage = getAlarmMessage(now, emptySet()),
+ ),
+ soundState = state.soundState.copy(sounds = sounds, soundIndex = defaultSoundIndex),
+ )
+ }
+ }
+
+ private fun loadExistingAlarm(sounds: List) = intent {
+ alarmUseCase.getAlarm(alarmId).onSuccess { alarm ->
+ val repeatDays = alarm.repeatDays.toAlarmDays()
+ val selectedSoundIndex = sounds.indexOfFirst { it.uri.toString() == alarm.soundUri }
+ val selectedSound = sounds.getOrNull(selectedSoundIndex) ?: sounds.first()
+
+ alarmUseCase.initializeSoundPlayer(selectedSound.uri)
+
+ reduce {
+ state.copy(
+ initialLoading = false,
+ timeState = state.timeState.copy(
+ initialTime = LocalTime.of(alarm.hour, alarm.minute),
+ currentTime = LocalTime.of(alarm.hour, alarm.minute),
+ alarmMessage = getAlarmMessage(
+ LocalTime.of(alarm.hour, alarm.minute),
+ repeatDays,
+ ),
+ ),
+ daySelectionState = setupDaySelectionState(repeatDays, state),
+ holidayState = state.holidayState.copy(
+ isDisableHolidayEnabled = repeatDays.isNotEmpty(),
+ isDisableHolidayChecked = alarm.isHolidayAlarmOff,
+ ),
+ missionState = setUpMissionState(alarm, state),
+ snoozeState = setupSnoozeState(alarm, state),
+ soundState = state.soundState.copy(
+ isVibrationEnabled = alarm.isVibrationEnabled,
+ isSoundEnabled = alarm.isSoundEnabled,
+ soundVolume = alarm.soundVolume,
+ sounds = sounds,
+ soundIndex = selectedSoundIndex,
+ ),
+ )
+ }
+ }
+ }
+
+ private fun setupDaySelectionState(
+ repeatDays: Set,
+ currentState: AlarmAddEditContract.State,
+ ): AlarmAddEditContract.AlarmDaySelectionState {
+ return currentState.daySelectionState.copy(
+ selectedDays = repeatDays,
+ isWeekdaysChecked = repeatDays.containsAll(setOf(AlarmDay.MON, AlarmDay.TUE, AlarmDay.WED, AlarmDay.THU, AlarmDay.FRI)),
+ isWeekendsChecked = repeatDays.containsAll(setOf(AlarmDay.SAT, AlarmDay.SUN)),
+ )
+ }
+
+ private fun setUpMissionState(
+ alarm: Alarm,
+ currentState: AlarmAddEditContract.State,
+ ): AlarmAddEditContract.AlarmMissionState {
+ return currentState.missionState.copy(
+ missionType = alarm.missionType,
+ missionCount = alarm.missionCount,
+ )
+ }
+
+ private fun setupSnoozeState(
+ alarm: Alarm,
+ currentState: AlarmAddEditContract.State,
+ ): AlarmAddEditContract.AlarmSnoozeState {
+ return currentState.snoozeState.copy(
+ isSnoozeEnabled = alarm.isSnoozeEnabled,
+ snoozeInterval = alarm.snoozeInterval,
+ snoozeCount = alarm.snoozeCount,
+ )
+ }
+
+ override fun onCleared() {
+ super.onCleared()
+ alarmUseCase.releaseSoundPlayer()
+ }
+
+ fun processAction(action: AlarmAddEditContract.Action) {
+ when (action) {
+ is AlarmAddEditContract.Action.CheckUnsavedChangesBeforeExit -> checkUnsavedChangesBeforeExit()
+ is AlarmAddEditContract.Action.NavigateBack -> navigateBack()
+ is AlarmAddEditContract.Action.SaveAlarm -> saveAlarm()
+ is AlarmAddEditContract.Action.ShowDeleteDialog -> showDeleteDialog()
+ is AlarmAddEditContract.Action.HideDeleteDialog -> hideDeleteDialog()
+ is AlarmAddEditContract.Action.ShowUnsavedChangesDialog -> showUnsavedChangesDialog()
+ is AlarmAddEditContract.Action.HideUnsavedChangesDialog -> hideUnsavedChangesDialog()
+ is AlarmAddEditContract.Action.DeleteAlarm -> deleteAlarm()
+ is AlarmAddEditContract.Action.SetAlarmTime -> setAlarmTime(action.newTime)
+ is AlarmAddEditContract.Action.ToggleWeekdaysSelection -> toggleWeekdaysSelection()
+ is AlarmAddEditContract.Action.ToggleWeekendsSelection -> toggleWeekendsSelection()
+ is AlarmAddEditContract.Action.ToggleSpecificDaySelection -> toggleSpecificDaySelection(action.day)
+ is AlarmAddEditContract.Action.ToggleHolidaySkipOption -> toggleHolidaySkipOption()
+ is AlarmAddEditContract.Action.SaveMissionSetting -> saveMissionSetting(action.type, action.count)
+ is AlarmAddEditContract.Action.SaveSnoozeSetting -> saveSnoozeSetting(
+ action.enabled,
+ action.interval,
+ action.count,
+ )
+ is AlarmAddEditContract.Action.SaveSoundSetting -> saveSoundSetting(
+ action.vibrationEnabled,
+ action.soundEnabled,
+ action.soundVolume,
+ action.soundIndex,
+ )
+ is AlarmAddEditContract.Action.ToggleVibrationEnabled -> toggleVibrationEnabled(action.enabled)
+ is AlarmAddEditContract.Action.ToggleSoundEnabled -> toggleSoundEnabled(action.enabled)
+ is AlarmAddEditContract.Action.SetSoundVolume -> setSoundVolume(action.volume)
+ is AlarmAddEditContract.Action.SetSoundIndex -> setSoundIndex(action.index)
+ is AlarmAddEditContract.Action.NavigateToMissionPreview -> navigateToMissionPreview(action.missionType, action.missionCount)
+ is AlarmAddEditContract.Action.ShowBottomSheet -> showBottomSheet(action.sheetType)
+ is AlarmAddEditContract.Action.HideBottomSheet -> hideBottomSheet()
+ }
+ }
+
+ private fun checkUnsavedChangesBeforeExit() = intent {
+ if (state.mode == AlarmAddEditContract.EditMode.ADD) {
+ navigateBack()
+ } else {
+ val updatedAlarm = state.toAlarm()
+ alarmUseCase.getAlarm(alarmId).onSuccess { existingAlarm ->
+ if (updatedAlarm.copy(id = alarmId) != existingAlarm) {
+ showUnsavedChangesDialog()
+ } else {
+ postSideEffect(AlarmAddEditContract.SideEffect.NavigateBack)
+ }
+ }
+ }
+ }
+
+ private fun navigateBack() = intent {
+ postSideEffect(AlarmAddEditContract.SideEffect.NavigateBack)
+ }
+
+ private fun navigateToMissionPreview(
+ missionType: MissionType,
+ missionCount: Int,
+ ) = intent {
+ val newTimeState = state.timeState.copy(
+ initialTime = state.timeState.currentTime,
+ )
+ reduce {
+ state.copy(
+ timeState = newTimeState,
+ )
+ }
+ postSideEffect(AlarmAddEditContract.SideEffect.NavigateToMissionPreview(missionType, missionCount))
+ }
+
+ private fun saveAlarm() = intent {
+ val newAlarm = state.toAlarm()
+
+ when (state.mode) {
+ AlarmAddEditContract.EditMode.EDIT -> updateExistingAlarm(newAlarm)
+ AlarmAddEditContract.EditMode.ADD -> checkAndCreateAlarm(newAlarm)
+ }
+ }
+
+ private fun updateExistingAlarm(alarm: Alarm) = intent {
+ val updatedAlarm = alarm.copy(id = alarmId)
+
+ alarmUseCase.getAlarm(alarmId).onSuccess { oldAlarm ->
+ alarmUseCase.unScheduleAlarm(oldAlarm)
+ }
+
+ alarmUseCase.updateAlarm(updatedAlarm)
+ .onSuccess {
+ alarmUseCase.scheduleAlarm(updatedAlarm)
+ postSideEffect(AlarmAddEditContract.SideEffect.UpdateAlarm(it.id))
+ }
+ .onFailure {
+ Log.e("AlarmAddEditViewModel", "Failed to update alarm", it)
+ }
+ }
+
+ private suspend fun checkAndCreateAlarm(newAlarm: Alarm) {
+ val timeMatchedAlarms = alarmUseCase.getAlarmsByTime(newAlarm.hour, newAlarm.minute)
+ .first()
+
+ when {
+ timeMatchedAlarms.any { it.copy(id = 0) == newAlarm.copy(id = 0) } -> {
+ showAlarmAlreadySetWarning()
+ }
+
+ timeMatchedAlarms.isNotEmpty() -> {
+ val existingAlarm = timeMatchedAlarms.first()
+ val updatedAlarm = existingAlarm.copyFrom(newAlarm).copy(id = existingAlarm.id)
+ updateExistingAlarm(updatedAlarm)
+ }
+
+ else -> {
+ createNewAlarm(newAlarm)
+ }
+ }
+ }
+
+ private fun showAlarmAlreadySetWarning() = intent {
+ postSideEffect(
+ AlarmAddEditContract.SideEffect.ShowSnackBar(
+ message = resourceProvider.getString(R.string.alarm_already_set),
+ iconRes = resourceProvider.getDrawable(core.designsystem.R.drawable.ic_alert),
+ bottomPadding = 78.dp,
+ onDismiss = { },
+ onAction = { },
+ ),
+ )
+ }
+
+ private fun createNewAlarm(alarm: Alarm) = intent {
+ alarmUseCase.insertAlarm(alarm)
+ .onSuccess {
+ analyticsHelper.logEvent(
+ AnalyticsEvent(
+ type = "alarm_create",
+ properties = mapOf(
+ AnalyticsEvent.AlarmPropertiesKeys.ALARM_ID to "${it.id}",
+ AnalyticsEvent.AlarmPropertiesKeys.REPEAT_DAYS to it.repeatDays.toAlarmDayNames(),
+ AnalyticsEvent.AlarmPropertiesKeys.SNOOZE_OPTION to listOf(it.snoozeInterval, it.snoozeCount),
+ ),
+ ),
+ )
+ alarmUseCase.scheduleAlarm(it)
+ postSideEffect(AlarmAddEditContract.SideEffect.SaveAlarm(it.id))
+ }
+ .onFailure {
+ Log.e("AlarmAddEditViewModel", "Failed to insert alarm", it)
+ }
+ }
+
+ private fun setAlarmTime(newTime: LocalTime) = intent {
+ val newTimeState = state.timeState.copy(
+ currentTime = newTime,
+ alarmMessage = getAlarmMessage(newTime, state.daySelectionState.selectedDays),
+ )
+
+ hapticFeedbackManager.performHapticFeedback(HapticType.LIGHT_TICK)
+
+ reduce {
+ state.copy(timeState = newTimeState)
+ }
+ }
+
+ private fun showDeleteDialog() = intent {
+ reduce { state.copy(isDeleteDialogVisible = true) }
+ }
+
+ private fun hideDeleteDialog() = intent {
+ reduce { state.copy(isDeleteDialogVisible = false) }
+ }
+
+ private fun showUnsavedChangesDialog() = intent {
+ reduce { state.copy(isUnsavedChangesDialogVisible = true) }
+ }
+
+ private fun hideUnsavedChangesDialog() = intent {
+ reduce { state.copy(isUnsavedChangesDialogVisible = false) }
+ }
+
+ private fun deleteAlarm() = intent {
+ postSideEffect(AlarmAddEditContract.SideEffect.DeleteAlarm(alarmId))
+ }
+
+ private fun toggleWeekdaysSelection() = intent {
+ val weekdays = setOf(AlarmDay.MON, AlarmDay.TUE, AlarmDay.WED, AlarmDay.THU, AlarmDay.FRI)
+ val isChecked = !state.daySelectionState.isWeekdaysChecked
+ val updatedDays = if (isChecked) {
+ state.daySelectionState.selectedDays + weekdays
+ } else {
+ state.daySelectionState.selectedDays - weekdays
+ }
+ val newDayState = state.daySelectionState.copy(
+ isWeekdaysChecked = isChecked,
+ selectedDays = updatedDays,
+ )
+ reduce {
+ state.copy(
+ timeState = state.timeState.copy(
+ alarmMessage = getAlarmMessage(state.timeState.currentTime, newDayState.selectedDays),
+ ),
+ daySelectionState = newDayState,
+ holidayState = state.holidayState.copy(
+ isDisableHolidayEnabled = newDayState.selectedDays.isNotEmpty(),
+ isDisableHolidayChecked = if (newDayState.selectedDays.isEmpty()) false else state.holidayState.isDisableHolidayChecked,
+ ),
+ )
+ }
+ }
+
+ private fun toggleWeekendsSelection() = intent {
+ val weekends = setOf(AlarmDay.SAT, AlarmDay.SUN)
+ val isChecked = !state.daySelectionState.isWeekendsChecked
+ val updatedDays = if (isChecked) {
+ state.daySelectionState.selectedDays + weekends
+ } else {
+ state.daySelectionState.selectedDays - weekends
+ }
+ val newDayState = state.daySelectionState.copy(
+ isWeekendsChecked = isChecked,
+ selectedDays = updatedDays,
+ )
+ reduce {
+ state.copy(
+ timeState = state.timeState.copy(
+ alarmMessage = getAlarmMessage(state.timeState.currentTime, newDayState.selectedDays),
+ ),
+ daySelectionState = newDayState,
+ holidayState = state.holidayState.copy(
+ isDisableHolidayEnabled = newDayState.selectedDays.isNotEmpty(),
+ isDisableHolidayChecked = if (newDayState.selectedDays.isEmpty()) false else state.holidayState.isDisableHolidayChecked,
+ ),
+ )
+ }
+ }
+
+ private fun toggleSpecificDaySelection(day: AlarmDay) = intent {
+ val updatedDays = if (day in state.daySelectionState.selectedDays) {
+ state.daySelectionState.selectedDays - day
+ } else {
+ state.daySelectionState.selectedDays + day
+ }
+ val weekdays = setOf(AlarmDay.MON, AlarmDay.TUE, AlarmDay.WED, AlarmDay.THU, AlarmDay.FRI)
+ val weekends = setOf(AlarmDay.SAT, AlarmDay.SUN)
+
+ val newDayState = state.daySelectionState.copy(
+ selectedDays = updatedDays,
+ isWeekdaysChecked = updatedDays.containsAll(weekdays),
+ isWeekendsChecked = updatedDays.containsAll(weekends),
+ )
+ reduce {
+ state.copy(
+ timeState = state.timeState.copy(
+ alarmMessage = getAlarmMessage(state.timeState.currentTime, newDayState.selectedDays),
+ ),
+ daySelectionState = newDayState,
+ holidayState = state.holidayState.copy(
+ isDisableHolidayEnabled = newDayState.selectedDays.isNotEmpty(),
+ isDisableHolidayChecked = if (newDayState.selectedDays.isEmpty()) false else state.holidayState.isDisableHolidayChecked,
+ ),
+ )
+ }
+ }
+
+ private fun toggleHolidaySkipOption() = intent {
+ val newHolidayState = state.holidayState.copy(
+ isDisableHolidayChecked = !state.holidayState.isDisableHolidayChecked,
+ )
+
+ reduce {
+ state.copy(holidayState = newHolidayState)
+ }
+
+ if (newHolidayState.isDisableHolidayChecked) {
+ postSideEffect(
+ AlarmAddEditContract.SideEffect.ShowSnackBar(
+ message = resourceProvider.getString(R.string.alarm_disabled_warning),
+ label = resourceProvider.getString(R.string.alarm_delete_dialog_btn_cancel),
+ iconRes = resourceProvider.getDrawable(core.designsystem.R.drawable.ic_check_green),
+ bottomPadding = 78.dp,
+ onDismiss = { },
+ onAction = {
+ intent {
+ reduce {
+ state.copy(
+ holidayState = state.holidayState.copy(
+ isDisableHolidayChecked = false,
+ ),
+ )
+ }
+ }
+ },
+ ),
+ )
+ }
+ }
+
+ private fun saveMissionSetting(type: MissionType, count: Int) = intent {
+ val newMissionState = state.missionState.copy(
+ missionType = type,
+ missionCount = count,
+ )
+ reduce {
+ state.copy(missionState = newMissionState)
+ }
+ }
+
+ private fun saveSnoozeSetting(
+ isSnoozeEnabled: Boolean,
+ snoozeInterval: Int,
+ snoozeCount: Int,
+ ) = intent {
+ val newSnoozeState = state.snoozeState.copy(
+ isSnoozeEnabled = isSnoozeEnabled,
+ snoozeInterval = snoozeInterval,
+ snoozeCount = snoozeCount,
+ )
+
+ reduce {
+ state.copy(snoozeState = newSnoozeState)
+ }
+ }
+
+ private fun saveSoundSetting(
+ vibrationEnabled: Boolean,
+ soundEnabled: Boolean,
+ soundVolume: Int,
+ soundIndex: Int,
+ ) = intent {
+ val newSoundState = state.soundState.copy(
+ isVibrationEnabled = vibrationEnabled,
+ isSoundEnabled = soundEnabled,
+ soundVolume = soundVolume,
+ soundIndex = soundIndex,
+ )
+
+ reduce {
+ state.copy(soundState = newSoundState)
+ }
+ }
+
+ private fun toggleVibrationEnabled(enabled: Boolean) = intent {
+ if (enabled) {
+ hapticFeedbackManager.performHapticFeedback(HapticType.SUCCESS)
+ }
+ }
+
+ private fun toggleSoundEnabled(enabled: Boolean) = intent {
+ if (!enabled) {
+ alarmUseCase.stopAlarmSound()
+ }
+ }
+
+ private fun setSoundVolume(volume: Int) = intent {
+ alarmUseCase.updateAlarmVolume(volume)
+ }
+
+ private fun setSoundIndex(index: Int) = intent {
+ val selectedSound = state.soundState.sounds[index]
+ alarmUseCase.initializeSoundPlayer(selectedSound.uri)
+ alarmUseCase.playAlarmSound(state.soundState.soundVolume)
+ }
+
+ private fun showBottomSheet(sheetType: AlarmAddEditContract.BottomSheetType) = intent {
+ postSideEffect(AlarmAddEditContract.SideEffect.ShowBottomSheet(sheetType))
+ }
+
+ private fun hideBottomSheet() = intent {
+ postSideEffect(AlarmAddEditContract.SideEffect.HideBottomSheet)
+ }
+
+ private fun getAlarmMessage(currentTime: LocalTime, selectedDays: Set): String {
+ val repeatDays = selectedDays.toRepeatDays()
+ val nextOccurrence = alarmDateTimeFormatter.calculateNextOccurrence(
+ hour = currentTime.hour,
+ minute = currentTime.minute,
+ repeatDays = repeatDays,
+ now = LocalDateTime.now(),
+ )
+
+ return alarmDateTimeFormatter.formatTimeDifference(
+ baseTime = LocalDateTime.now(),
+ futureTime = nextOccurrence,
+ formats = AlarmDateTimeFormatter.TimeDifferenceFormats(
+ daysHoursMinutesFormat = resourceProvider.getString(R.string.alarm_remaining_time_days_hours),
+ hoursMinutesFormat = resourceProvider.getString(R.string.alarm_remaining_time_hours_minutes),
+ minutesFormat = resourceProvider.getString(R.string.alarm_remaining_time_minutes_only),
+ soonFormat = resourceProvider.getString(R.string.alarm_remaining_time_soon),
+ ),
+ )
+ }
+}
diff --git a/feature/home/src/main/java/com/yapp/alarm/component/AlarmCheckItem.kt b/feature/home/src/main/java/com/yapp/home/alarm/component/AlarmCheckItem.kt
similarity index 91%
rename from feature/home/src/main/java/com/yapp/alarm/component/AlarmCheckItem.kt
rename to feature/home/src/main/java/com/yapp/home/alarm/component/AlarmCheckItem.kt
index 900d7e96..905f9605 100644
--- a/feature/home/src/main/java/com/yapp/alarm/component/AlarmCheckItem.kt
+++ b/feature/home/src/main/java/com/yapp/home/alarm/component/AlarmCheckItem.kt
@@ -1,4 +1,4 @@
-package com.yapp.alarm.component
+package com.yapp.home.alarm.component
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
@@ -13,6 +13,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.yapp.designsystem.theme.OrbitTheme
+import core.designsystem.R
@Composable
internal fun AlarmCheckItem(
@@ -30,7 +31,7 @@ internal fun AlarmCheckItem(
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
- painter = painterResource(id = core.designsystem.R.drawable.ic_check),
+ painter = painterResource(id = R.drawable.ic_check),
contentDescription = "Check",
tint = if (isPressed) OrbitTheme.colors.main else OrbitTheme.colors.gray_400,
)
diff --git a/feature/home/src/main/java/com/yapp/alarm/component/AlarmDayButton.kt b/feature/home/src/main/java/com/yapp/home/alarm/component/AlarmDayButton.kt
similarity index 98%
rename from feature/home/src/main/java/com/yapp/alarm/component/AlarmDayButton.kt
rename to feature/home/src/main/java/com/yapp/home/alarm/component/AlarmDayButton.kt
index 4172b075..9e76a68c 100644
--- a/feature/home/src/main/java/com/yapp/alarm/component/AlarmDayButton.kt
+++ b/feature/home/src/main/java/com/yapp/home/alarm/component/AlarmDayButton.kt
@@ -1,4 +1,4 @@
-package com.yapp.alarm.component
+package com.yapp.home.alarm.component
import androidx.compose.foundation.background
import androidx.compose.foundation.border
diff --git a/feature/home/src/main/java/com/yapp/alarm/component/AlarmListItem.kt b/feature/home/src/main/java/com/yapp/home/alarm/component/AlarmListItem.kt
similarity index 97%
rename from feature/home/src/main/java/com/yapp/alarm/component/AlarmListItem.kt
rename to feature/home/src/main/java/com/yapp/home/alarm/component/AlarmListItem.kt
index a590bd8e..6e978386 100644
--- a/feature/home/src/main/java/com/yapp/alarm/component/AlarmListItem.kt
+++ b/feature/home/src/main/java/com/yapp/home/alarm/component/AlarmListItem.kt
@@ -1,4 +1,4 @@
-package com.yapp.alarm.component
+package com.yapp.home.alarm.component
import android.os.Handler
import android.os.Looper
@@ -76,7 +76,6 @@ internal fun AlarmListItem(
onLongPress: (Long, Float, Float) -> Unit,
onToggleSelect: (Long) -> Unit,
onSwipe: (Long) -> Unit,
- isAm: Boolean,
hour: Int,
minute: Int,
isActive: Boolean,
@@ -197,7 +196,6 @@ internal fun AlarmListItem(
repeatDays = repeatDays,
isActive = isActive,
isHolidayAlarmOff = isHolidayAlarmOff,
- isAm = isAm,
hour = hour,
minute = minute,
)
@@ -220,7 +218,6 @@ private fun AlarmListItemContent(
repeatDays: Int,
isActive: Boolean,
isHolidayAlarmOff: Boolean,
- isAm: Boolean,
hour: Int,
minute: Int,
) {
@@ -230,6 +227,13 @@ private fun AlarmListItemContent(
OrbitTheme.colors.gray_500 to OrbitTheme.colors.gray_500
}
+ val isAm = hour < 12
+ val displayHour = when {
+ hour == 0 -> 12
+ hour > 12 -> hour - 12
+ else -> hour
+ }
+
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
@@ -260,7 +264,7 @@ private fun AlarmListItemContent(
Spacer(modifier = Modifier.width(6.dp))
Text(
- text = "$hour",
+ text = "$displayHour",
style = OrbitTheme.typography.title2Medium,
color = if (isActive) OrbitTheme.colors.white else OrbitTheme.colors.gray_500,
)
@@ -292,7 +296,6 @@ private fun Int.toRepeatDaysString(isAm: Boolean, hour: Int, minute: Int): Strin
days.size == 7 -> "๋งค์ผ"
days.isNotEmpty() -> "๋งค์ฃผ " + days.joinToString(", ") { it.toKoreanString() }
else -> getNextAlarmDateWithTime(
- isAm = isAm,
hour = hour,
minute = minute,
)
@@ -311,16 +314,10 @@ private fun AlarmDay.toKoreanString(): String {
}
}
-private fun getNextAlarmDateWithTime(isAm: Boolean, hour: Int, minute: Int): String {
+private fun getNextAlarmDateWithTime(hour: Int, minute: Int): String {
val now = LocalDateTime.now()
- val alarmHour = if (isAm) {
- if (hour == 12) 0 else hour
- } else {
- if (hour == 12) 12 else hour + 12
- }
-
- val alarmTime = LocalTime.of(alarmHour, minute)
+ val alarmTime = LocalTime.of(hour, minute)
val todayAlarm = LocalDateTime.of(now.toLocalDate(), alarmTime)
// ์ค๋ ์๊ฐ ์ด๋ฏธ ์ง๋ฌ์ผ๋ฉด ๋ด์ผ๋ก ์ค์
@@ -408,7 +405,6 @@ private fun AlarmListItemPreview() {
selectable = true,
swipeable = false,
selected = selected,
- isAm = true,
hour = 6,
minute = 0,
isActive = isActive,
@@ -436,7 +432,6 @@ private fun AlarmListItemPreview() {
selectable = false,
selected = false,
swipeable = true,
- isAm = true,
hour = 6,
minute = 0,
isActive = isActive,
@@ -467,7 +462,6 @@ private fun AlarmListItemMenuPreview() {
selectable = false,
swipeable = false,
selected = false,
- isAm = true,
hour = 6,
minute = 0,
isActive = true,
diff --git a/feature/home/src/main/java/com/yapp/home/alarm/component/SelectorItems.kt b/feature/home/src/main/java/com/yapp/home/alarm/component/SelectorItems.kt
new file mode 100644
index 00000000..1b4630f9
--- /dev/null
+++ b/feature/home/src/main/java/com/yapp/home/alarm/component/SelectorItems.kt
@@ -0,0 +1,74 @@
+package com.yapp.home.alarm.component
+
+import androidx.compose.foundation.background
+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.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.yapp.designsystem.theme.OrbitTheme
+import com.yapp.ui.component.radiobutton.OrbitRadioButton
+
+@Composable
+internal fun SelectorItems(
+ items: List,
+ selectedIndex: Int,
+ enabled: Boolean,
+ onItemSelected: (Int) -> Unit,
+) {
+ Box {
+ Column {
+ Spacer(modifier = Modifier.height(7.dp))
+ Spacer(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(6.dp)
+ .padding(horizontal = 6.dp)
+ .background(
+ if (enabled) {
+ OrbitTheme.colors.gray_600
+ } else {
+ OrbitTheme.colors.gray_700
+ },
+ ),
+ )
+ }
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 6.dp),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ ) {
+ items.forEachIndexed { index, item ->
+ Column(horizontalAlignment = getAlignment(index, items.size)) {
+ OrbitRadioButton(
+ selected = index == selectedIndex,
+ enabled = enabled,
+ onClick = { if (enabled) onItemSelected(index) },
+ )
+ Spacer(modifier = Modifier.height(12.dp))
+ Text(
+ text = item,
+ style = OrbitTheme.typography.body1Medium,
+ color = OrbitTheme.colors.gray_50,
+ )
+ }
+ }
+ }
+ }
+}
+
+private fun getAlignment(index: Int, size: Int): Alignment.Horizontal =
+ when (index) {
+ 0 -> Alignment.Start
+ size - 1 -> Alignment.End
+ else -> Alignment.CenterHorizontally
+ }
diff --git a/feature/home/src/main/java/com/yapp/home/alarm/component/bottomsheet/AlarmMissionBottomSheet.kt b/feature/home/src/main/java/com/yapp/home/alarm/component/bottomsheet/AlarmMissionBottomSheet.kt
new file mode 100644
index 00000000..99656263
--- /dev/null
+++ b/feature/home/src/main/java/com/yapp/home/alarm/component/bottomsheet/AlarmMissionBottomSheet.kt
@@ -0,0 +1,680 @@
+package com.yapp.home.alarm.component.bottomsheet
+
+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.PaddingValues
+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.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.listSaver
+import androidx.compose.runtime.saveable.rememberSaveable
+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.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.yapp.designsystem.theme.OrbitTheme
+import com.yapp.domain.model.MissionType
+import com.yapp.home.alarm.addedit.AlarmAddEditContract
+import com.yapp.home.alarm.component.SelectorItems
+import com.yapp.ui.component.button.OrbitButton
+import com.yapp.ui.component.lottie.LottieAnimation
+import com.yapp.ui.extensions.customClickable
+import core.designsystem.R
+
+enum class AlarmMissionSelectBottomSheetType {
+ MISSION_SETTING,
+ MISSION_SELECT,
+ MISSION_DETAIL,
+}
+
+private val countOptions = listOf(5, 10, 15, 20, 30)
+
+private fun MissionType.displayData(): Pair = when (this) {
+ MissionType.SHAKE -> Pair(R.drawable.ic_mission_shake, feature.home.R.string.alarm_add_edit_selected_mission_shake)
+ MissionType.TAP -> Pair(R.drawable.ic_mission_tap, feature.home.R.string.alarm_add_edit_selected_mission_tap)
+ else -> throw IllegalStateException("Invalid mission type")
+}
+
+val StepStackSaver: Saver>, out Any> =
+ listSaver(
+ save = { state -> state.value.map { it.name } },
+ restore = { restored -> mutableStateOf(restored.map { AlarmMissionSelectBottomSheetType.valueOf(it) }) },
+ )
+
+@Composable
+internal fun AlarmMissionBottomSheet(
+ missionState: AlarmAddEditContract.AlarmMissionState,
+ onDismiss: () -> Unit,
+ onSaveMission: (MissionType, Int) -> Unit,
+ onPreviewMission: (MissionType, Int) -> Unit,
+) {
+ val initialMissionType = missionState.missionType
+ val initialMissionCount = missionState.missionCount
+
+ var stepStack by rememberSaveable(saver = StepStackSaver) {
+ mutableStateOf(listOf(AlarmMissionSelectBottomSheetType.MISSION_SETTING))
+ }
+
+ var currentSelectedMissionType by rememberSaveable { mutableStateOf(initialMissionType) }
+ var currentSelectedMissionCount by rememberSaveable { mutableIntStateOf(initialMissionCount) }
+
+ fun push(step: AlarmMissionSelectBottomSheetType) {
+ stepStack = stepStack + step
+ }
+
+ fun pop() {
+ if (stepStack.size > 1) {
+ stepStack = stepStack.dropLast(1)
+ }
+ }
+
+ val currentStep = stepStack.last()
+
+ when (currentStep) {
+ AlarmMissionSelectBottomSheetType.MISSION_SETTING -> {
+ if (currentSelectedMissionType == MissionType.NONE) {
+ MissionAddContent {
+ push(AlarmMissionSelectBottomSheetType.MISSION_SELECT)
+ }
+ } else {
+ MissionSettingContent(
+ missionType = currentSelectedMissionType,
+ missionCount = currentSelectedMissionCount,
+ onDetail = { push(AlarmMissionSelectBottomSheetType.MISSION_DETAIL) },
+ onDelete = {
+ currentSelectedMissionType = MissionType.NONE
+ onSaveMission(currentSelectedMissionType, currentSelectedMissionCount)
+ },
+ onChange = { push(AlarmMissionSelectBottomSheetType.MISSION_SELECT) },
+ onDone = {
+ onSaveMission(currentSelectedMissionType, currentSelectedMissionCount)
+ onDismiss()
+ },
+ )
+ }
+ }
+
+ AlarmMissionSelectBottomSheetType.MISSION_SELECT -> {
+ MissionSelectContent(
+ onBack = { pop() },
+ onClose = { onDismiss() },
+ initialMission = currentSelectedMissionType,
+ onSelect = { selected ->
+ currentSelectedMissionType = selected
+ push(AlarmMissionSelectBottomSheetType.MISSION_DETAIL)
+ },
+ )
+ }
+
+ AlarmMissionSelectBottomSheetType.MISSION_DETAIL -> {
+ MissionDetailContent(
+ missionType = currentSelectedMissionType,
+ selectedMissionCount = currentSelectedMissionCount,
+ onCountChange = { currentSelectedMissionCount = it },
+ onBack = { pop() },
+ onClose = { onDismiss() },
+ onSave = {
+ onSaveMission(currentSelectedMissionType, currentSelectedMissionCount)
+ onDismiss()
+ },
+ onPreview = {
+ onPreviewMission(currentSelectedMissionType, currentSelectedMissionCount)
+ },
+ )
+ }
+ }
+}
+
+@Composable
+private fun MissionAddContent(
+ onNext: () -> Unit,
+) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(600.dp)
+ .padding(horizontal = 24.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Spacer(modifier = Modifier.height(26.dp))
+
+ Text(
+ modifier = Modifier.align(Alignment.Start),
+ text = stringResource(id = feature.home.R.string.mission_bottom_sheet_title),
+ style = OrbitTheme.typography.heading2SemiBold,
+ color = OrbitTheme.colors.white,
+ )
+
+ Box(
+ modifier = Modifier.weight(1f),
+ contentAlignment = Alignment.Center,
+ ) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Text(
+ text = stringResource(id = feature.home.R.string.mission_add_content_empty_title),
+ style = OrbitTheme.typography.body1Bold,
+ color = OrbitTheme.colors.white,
+ )
+
+ Spacer(modifier = Modifier.height(6.dp))
+
+ Text(
+ text = stringResource(id = feature.home.R.string.mission_add_content_empty_description),
+ style = OrbitTheme.typography.label2Regular,
+ color = OrbitTheme.colors.white.copy(alpha = 0.8f),
+ )
+
+ Spacer(modifier = Modifier.height(32.dp))
+
+ AddMissionButton { onNext() }
+ }
+ }
+ }
+}
+
+@Composable
+private fun AddMissionButton(
+ modifier: Modifier = Modifier,
+ onClick: () -> Unit,
+) {
+ Button(
+ onClick = onClick,
+ modifier = modifier,
+ shape = CircleShape,
+ colors = ButtonDefaults.buttonColors(
+ containerColor = OrbitTheme.colors.white,
+ contentColor = OrbitTheme.colors.gray_900,
+ ),
+ contentPadding = PaddingValues(
+ horizontal = 24.dp,
+ vertical = 12.dp,
+ ),
+ ) {
+ Icon(
+ painter = painterResource(R.drawable.ic_plus),
+ tint = Color.Unspecified,
+ contentDescription = "Add Mission",
+ )
+
+ Spacer(modifier = Modifier.width(4.dp))
+
+ Text(
+ text = stringResource(id = feature.home.R.string.mission_add_content_btn_add),
+ style = OrbitTheme.typography.body1SemiBold,
+ )
+ }
+}
+
+@Composable
+private fun MissionSettingContent(
+ missionType: MissionType,
+ missionCount: Int,
+ onDetail: () -> Unit,
+ onDelete: () -> Unit,
+ onChange: () -> Unit,
+ onDone: () -> Unit,
+) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(start = 12.dp, end = 12.dp, bottom = 12.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Spacer(modifier = Modifier.height(26.dp))
+
+ Text(
+ modifier = Modifier
+ .padding(start = 12.dp)
+ .align(Alignment.Start),
+ text = stringResource(id = feature.home.R.string.mission_bottom_sheet_title),
+ style = OrbitTheme.typography.heading2SemiBold,
+ color = OrbitTheme.colors.white,
+ )
+
+ Spacer(modifier = Modifier.height(14.dp))
+
+ SelectedMissionTypeItem(
+ missionType = missionType,
+ missionCount = missionCount,
+ onDetail = onDetail,
+ onDelete = onDelete,
+ )
+
+ Spacer(modifier = Modifier.height(32.dp))
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 8.dp),
+ horizontalArrangement = Arrangement.spacedBy(10.dp),
+ ) {
+ OrbitButton(
+ label = stringResource(id = feature.home.R.string.mission_setting_content_btn_change),
+ onClick = onChange,
+ enabled = true,
+ containerColor = OrbitTheme.colors.gray_600,
+ contentColor = OrbitTheme.colors.white,
+ pressedContainerColor = OrbitTheme.colors.gray_500,
+ pressedContentColor = OrbitTheme.colors.white.copy(alpha = 0.7f),
+ modifier = Modifier.weight(1f),
+ )
+
+ OrbitButton(
+ label = stringResource(id = feature.home.R.string.mission_setting_content_btn_done),
+ onClick = onDone,
+ enabled = true,
+ modifier = Modifier.weight(1f),
+ )
+ }
+ }
+}
+
+@Composable
+private fun SelectedMissionTypeItem(
+ missionType: MissionType,
+ missionCount: Int,
+ onDetail: () -> Unit,
+ onDelete: () -> Unit,
+) {
+ val (iconRes, titleRes) = missionType.displayData()
+ val title = stringResource(id = titleRes)
+
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Row(
+ modifier = Modifier
+ .weight(1f)
+ .clip(RoundedCornerShape(12.dp))
+ .clickable(
+ onClick = onDetail,
+ )
+ .padding(12.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Icon(
+ painter = painterResource(id = iconRes),
+ contentDescription = title,
+ modifier = Modifier.size(28.dp),
+ tint = Color.Unspecified,
+ )
+
+ Spacer(modifier = Modifier.width(12.dp))
+
+ Text(
+ text = title,
+ style = OrbitTheme.typography.headline2SemiBold,
+ color = OrbitTheme.colors.white,
+ )
+
+ Spacer(modifier = Modifier.width(8.dp))
+
+ MissionCountChip(count = missionCount)
+ }
+
+ Box(
+ modifier = Modifier
+ .clip(RoundedCornerShape(12.dp))
+ .clickable(
+ onClick = onDelete,
+ )
+ .padding(12.dp),
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_delete),
+ contentDescription = "Delete",
+ modifier = Modifier.size(20.dp),
+ tint = OrbitTheme.colors.gray_400,
+ )
+ }
+ }
+}
+
+@Composable
+private fun MissionCountChip(
+ count: Int,
+) {
+ Row(
+ modifier = Modifier
+ .background(
+ color = OrbitTheme.colors.main.copy(alpha = 0.1f),
+ shape = CircleShape,
+ )
+ .padding(start = 5.dp, end = 3.dp, top = 2.dp, bottom = 2.dp),
+ horizontalArrangement = Arrangement.spacedBy(2.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Text(
+ text = stringResource(id = feature.home.R.string.mission_count_chip_format, count),
+ style = OrbitTheme.typography.label2Regular,
+ color = OrbitTheme.colors.main.copy(alpha = 0.9f),
+ )
+
+ Icon(
+ painter = painterResource(id = R.drawable.ic_arrow_right),
+ contentDescription = "Close",
+ modifier = Modifier.size(12.dp),
+ tint = OrbitTheme.colors.main.copy(alpha = 0.9f),
+ )
+ }
+}
+
+@Composable
+private fun MissionSelectContent(
+ onBack: () -> Unit,
+ onClose: () -> Unit,
+ initialMission: MissionType,
+ onSelect: (MissionType) -> Unit,
+) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(600.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Spacer(modifier = Modifier.height(14.dp))
+
+ MissionSelectTopAppBar(
+ title = stringResource(id = feature.home.R.string.mission_select_content_title),
+ onBack = onBack,
+ onClose = onClose,
+ )
+
+ Column(
+ modifier = Modifier.padding(horizontal = 12.dp),
+ ) {
+ MissionTypeItem(
+ missionType = MissionType.SHAKE,
+ selected = initialMission == MissionType.SHAKE,
+ onClick = {
+ onSelect(MissionType.SHAKE)
+ },
+ )
+ MissionTypeItem(
+ missionType = MissionType.TAP,
+ selected = initialMission == MissionType.TAP,
+ onClick = {
+ onSelect(MissionType.TAP)
+ },
+ )
+ }
+ }
+}
+
+@Composable
+private fun MissionTypeItem(
+ missionType: MissionType,
+ selected: Boolean,
+ onClick: () -> Unit,
+) {
+ val (iconRes, titleRes) = missionType.displayData()
+ val title = stringResource(id = titleRes)
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clip(RoundedCornerShape(12.dp))
+ .clickable(
+ onClick = onClick,
+ )
+ .padding(
+ horizontal = 12.dp,
+ vertical = 16.dp,
+ ),
+ horizontalArrangement = Arrangement.spacedBy(12.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Icon(
+ painter = painterResource(id = iconRes),
+ contentDescription = title,
+ modifier = Modifier.size(28.dp),
+ tint = Color.Unspecified,
+ )
+
+ Text(
+ text = title,
+ style = OrbitTheme.typography.headline2SemiBold,
+ color = OrbitTheme.colors.white,
+ )
+
+ if (selected) {
+ Spacer(modifier = Modifier.weight(1f))
+
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(2.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Icon(
+ modifier = Modifier.size(16.dp),
+ painter = painterResource(id = R.drawable.ic_check),
+ tint = OrbitTheme.colors.white.copy(alpha = 0.5f),
+ contentDescription = null,
+ )
+
+ Text(
+ text = stringResource(id = feature.home.R.string.mission_select_content_selected),
+ style = OrbitTheme.typography.body2Medium,
+ color = OrbitTheme.colors.white.copy(alpha = 0.4f),
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun MissionDetailContent(
+ missionType: MissionType,
+ selectedMissionCount: Int,
+ onCountChange: (Int) -> Unit,
+ onBack: () -> Unit,
+ onClose: () -> Unit,
+ onSave: () -> Unit,
+ onPreview: () -> Unit,
+) {
+ val (title, lottieRes) = when (missionType) {
+ MissionType.SHAKE ->
+ Pair(stringResource(id = feature.home.R.string.alarm_add_edit_selected_mission_shake), R.raw.mission_shake)
+ MissionType.TAP ->
+ Pair(stringResource(id = feature.home.R.string.alarm_add_edit_selected_mission_tap), R.raw.mission_tap)
+ else -> return
+ }
+ val selectedMissionCountIndex = countOptions.indexOf(selectedMissionCount).coerceAtLeast(0)
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(600.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Spacer(modifier = Modifier.height(14.dp))
+
+ MissionSelectTopAppBar(
+ title = title,
+ onBack = onBack,
+ onClose = onClose,
+ )
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(
+ horizontal = 20.dp,
+ vertical = 12.dp,
+ ),
+ ) {
+ Spacer(modifier = Modifier.height(12.dp))
+
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(200.dp)
+ .background(
+ color = OrbitTheme.colors.gray_700,
+ shape = RoundedCornerShape(16.dp),
+ ),
+ contentAlignment = Alignment.Center,
+ ) {
+ LottieAnimation(
+ resId = lottieRes,
+ scaleXAdjustment = 0.85f,
+ scaleYAdjustment = 0.85f,
+ )
+ }
+
+ Spacer(modifier = Modifier.height(28.dp))
+
+ Text(
+ text = stringResource(id = feature.home.R.string.mission_detail_content_count_title),
+ style = OrbitTheme.typography.headline2SemiBold,
+ color = OrbitTheme.colors.gray_50,
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ ) {
+ Text(
+ text = stringResource(id = feature.home.R.string.mission_detail_content_count_level_easy),
+ style = OrbitTheme.typography.label2SemiBold,
+ color = OrbitTheme.colors.gray_300,
+ )
+
+ Text(
+ text = stringResource(id = feature.home.R.string.mission_detail_content_count_level_hard),
+ style = OrbitTheme.typography.label2SemiBold,
+ color = OrbitTheme.colors.gray_300,
+ )
+ }
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ SelectorItems(
+ items = countOptions.map { stringResource(id = feature.home.R.string.mission_count_chip_format, it) },
+ selectedIndex = selectedMissionCountIndex,
+ enabled = true,
+ onItemSelected = { index -> onCountChange(countOptions[index]) },
+ )
+
+ Spacer(modifier = Modifier.weight(1f))
+
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(10.dp),
+ ) {
+ OrbitButton(
+ label = stringResource(id = feature.home.R.string.mission_detail_content_btn_preview),
+ onClick = onPreview,
+ useFillMaxWidth = false,
+ enabled = true,
+ containerColor = OrbitTheme.colors.gray_600,
+ contentColor = OrbitTheme.colors.white,
+ pressedContainerColor = OrbitTheme.colors.gray_500,
+ pressedContentColor = OrbitTheme.colors.white.copy(alpha = 0.7f),
+ )
+
+ OrbitButton(
+ label = stringResource(id = feature.home.R.string.mission_detail_content_btn_save),
+ onClick = onSave,
+ enabled = true,
+ modifier = Modifier.weight(1f),
+ )
+ }
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+private fun MissionSelectTopAppBar(
+ title: String,
+ onBack: () -> Unit,
+ onClose: () -> Unit,
+) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 20.dp)
+ .height(48.dp),
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_back),
+ contentDescription = "Back",
+ tint = OrbitTheme.colors.white,
+ modifier = Modifier
+ .customClickable(
+ rippleEnabled = false,
+ fadeOnPress = true,
+ pressedAlpha = 0.5f,
+ onClick = onBack,
+ )
+ .align(Alignment.CenterStart),
+ )
+
+ Text(
+ text = title,
+ modifier = Modifier.align(Alignment.Center),
+ style = OrbitTheme.typography.body1SemiBold,
+ color = OrbitTheme.colors.white,
+ )
+
+ Box(
+ modifier = Modifier
+ .size(32.dp)
+ .clip(CircleShape)
+ .clickable { onClose() }
+ .align(Alignment.CenterEnd),
+ contentAlignment = Alignment.Center,
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_close),
+ contentDescription = "Close",
+ modifier = Modifier.size(24.dp),
+ tint = OrbitTheme.colors.white,
+ )
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Preview
+@Composable
+private fun AlarmMissionSelectBottomSheetPreview() {
+ OrbitTheme {
+ AlarmMissionBottomSheet(
+ missionState = AlarmAddEditContract.AlarmMissionState(),
+ onDismiss = { },
+ onSaveMission = { _, _ -> },
+ onPreviewMission = { _, _ -> },
+ )
+ }
+}
diff --git a/feature/home/src/main/java/com/yapp/home/alarm/component/bottomsheet/AlarmSnoozeBottomSheet.kt b/feature/home/src/main/java/com/yapp/home/alarm/component/bottomsheet/AlarmSnoozeBottomSheet.kt
new file mode 100644
index 00000000..ef6643c0
--- /dev/null
+++ b/feature/home/src/main/java/com/yapp/home/alarm/component/bottomsheet/AlarmSnoozeBottomSheet.kt
@@ -0,0 +1,206 @@
+package com.yapp.home.alarm.component.bottomsheet
+
+import androidx.compose.foundation.background
+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.ExperimentalMaterial3Api
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+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.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.yapp.designsystem.theme.OrbitTheme
+import com.yapp.home.alarm.addedit.AlarmAddEditContract
+import com.yapp.home.alarm.component.SelectorItems
+import com.yapp.ui.component.button.OrbitButton
+import com.yapp.ui.component.switch.OrbitSwitch
+import feature.home.R
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+internal fun AlarmSnoozeBottomSheet(
+ snoozeState: AlarmAddEditContract.AlarmSnoozeState,
+ onDismiss: () -> Unit = {},
+ onComplete: (enabled: Boolean, interval: Int, count: Int) -> Unit,
+) {
+ val snoozeIntervalOptions = listOf(1, 3, 5, 10, 15)
+ val snoozeCountOptions = listOf(1, 3, 5, 10, -1)
+
+ val snoozeIntervals = snoozeIntervalOptions.map {
+ stringResource(id = R.string.alarm_add_edit_interval_minute, it)
+ }
+ val snoozeCounts = listOf(
+ stringResource(id = R.string.alarm_add_edit_repeat_count_times, 1),
+ stringResource(id = R.string.alarm_add_edit_repeat_count_times, 3),
+ stringResource(id = R.string.alarm_add_edit_repeat_count_times, 5),
+ stringResource(id = R.string.alarm_add_edit_repeat_count_times, 10),
+ stringResource(id = R.string.alarm_add_edit_repeat_count_infinite),
+ )
+
+ var selectedSnoozeEnabled by remember { mutableStateOf(snoozeState.isSnoozeEnabled) }
+ var selectedSnoozeIntervalIndex by remember { mutableIntStateOf(snoozeIntervalOptions.indexOf(snoozeState.snoozeInterval)) }
+ var selectedSnoozeCountIndex by remember {
+ mutableIntStateOf(
+ if (snoozeState.snoozeCount == -1) {
+ snoozeCountOptions.lastIndex
+ } else {
+ snoozeCountOptions.indexOf(snoozeState.snoozeCount)
+ },
+ )
+ }
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 24.dp, vertical = 12.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Spacer(modifier = Modifier.height(6.dp))
+ VibrationSection(selectedSnoozeEnabled) {
+ selectedSnoozeEnabled = !selectedSnoozeEnabled
+ }
+ Spacer(modifier = Modifier.height(20.dp))
+ SelectorSection(
+ title = stringResource(id = R.string.alarm_add_edit_interval),
+ selectedIndex = selectedSnoozeIntervalIndex,
+ items = snoozeIntervals,
+ enabled = selectedSnoozeEnabled,
+ onItemSelected = {
+ selectedSnoozeIntervalIndex = it
+ },
+ )
+ Spacer(modifier = Modifier.height(32.dp))
+ SelectorSection(
+ title = stringResource(id = R.string.alarm_add_edit_repeat_count),
+ selectedIndex = selectedSnoozeCountIndex,
+ items = snoozeCounts,
+ enabled = selectedSnoozeEnabled,
+ onItemSelected = {
+ selectedSnoozeCountIndex = it
+ },
+ )
+ Spacer(modifier = Modifier.height(20.dp))
+ if (selectedSnoozeEnabled) {
+ AlarmSnoozeMessage(
+ interval = snoozeIntervals[selectedSnoozeIntervalIndex],
+ count = snoozeCounts[selectedSnoozeCountIndex],
+ )
+ }
+ Spacer(modifier = Modifier.height(40.dp))
+ OrbitButton(
+ label = stringResource(id = R.string.alarm_add_edit_complete),
+ enabled = true,
+ containerColor = OrbitTheme.colors.gray_600,
+ contentColor = OrbitTheme.colors.white,
+ pressedContainerColor = OrbitTheme.colors.gray_500,
+ pressedContentColor = OrbitTheme.colors.white.copy(alpha = 0.7f),
+ onClick = {
+ onDismiss()
+ onComplete(
+ selectedSnoozeEnabled,
+ snoozeIntervalOptions[selectedSnoozeIntervalIndex],
+ snoozeCountOptions[selectedSnoozeCountIndex],
+ )
+ },
+ )
+ }
+}
+
+@Composable
+private fun VibrationSection(snoozeEnabled: Boolean, onSnoozeToggle: () -> Unit) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 8.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Text(
+ text = stringResource(id = R.string.alarm_add_edit_alarm_snooze),
+ style = OrbitTheme.typography.heading2SemiBold,
+ color = OrbitTheme.colors.white,
+ )
+ Spacer(modifier = Modifier.weight(1f))
+ OrbitSwitch(
+ isChecked = snoozeEnabled,
+ isEnabled = true,
+ onClick = onSnoozeToggle,
+ )
+ }
+}
+
+@Composable
+private fun SelectorSection(
+ title: String,
+ selectedIndex: Int,
+ items: List,
+ enabled: Boolean,
+ onItemSelected: (Int) -> Unit,
+) {
+ Column {
+ Text(
+ text = title,
+ style = OrbitTheme.typography.headline2Medium,
+ color = OrbitTheme.colors.gray_50,
+ )
+ Spacer(modifier = Modifier.height(16.dp))
+ SelectorItems(
+ items = items,
+ selectedIndex = selectedIndex,
+ enabled = enabled,
+ onItemSelected = onItemSelected,
+ )
+ }
+}
+
+@Composable
+private fun AlarmSnoozeMessage(interval: String, count: String) {
+ val formattedCount = if (count == stringResource(id = R.string.alarm_add_edit_repeat_count_infinite)) "${count}๋ฒ" else count
+
+ Box(
+ modifier = Modifier
+ .background(
+ color = OrbitTheme.colors.gray_700,
+ shape = RoundedCornerShape(8.dp),
+ )
+ .padding(horizontal = 12.dp, vertical = 6.dp),
+ contentAlignment = Alignment.Center,
+ ) {
+ Text(
+ text = stringResource(id = R.string.alarm_add_edit_alarm_snooze_description, interval, formattedCount),
+ style = OrbitTheme.typography.label1Medium,
+ color = OrbitTheme.colors.main,
+ )
+ }
+}
+
+@Preview
+@Composable
+private fun AlarmSnoozeBottomSheetPreview() {
+ var isSnoozeEnabled by remember { mutableStateOf(true) }
+ var snoozeInterval by remember { mutableIntStateOf(5) }
+ var snoozeCount by remember { mutableIntStateOf(5) }
+
+ OrbitTheme {
+ AlarmSnoozeBottomSheet(
+ snoozeState = AlarmAddEditContract.AlarmSnoozeState(
+ isSnoozeEnabled = isSnoozeEnabled,
+ snoozeInterval = snoozeInterval,
+ snoozeCount = snoozeCount,
+ ),
+ onComplete = { _, _, _ -> },
+ )
+ }
+}
diff --git a/feature/home/src/main/java/com/yapp/alarm/component/bottomsheet/AlarmSoundBottomSheet.kt b/feature/home/src/main/java/com/yapp/home/alarm/component/bottomsheet/AlarmSoundBottomSheet.kt
similarity index 66%
rename from feature/home/src/main/java/com/yapp/alarm/component/bottomsheet/AlarmSoundBottomSheet.kt
rename to feature/home/src/main/java/com/yapp/home/alarm/component/bottomsheet/AlarmSoundBottomSheet.kt
index 04049c3f..7b706435 100644
--- a/feature/home/src/main/java/com/yapp/alarm/component/bottomsheet/AlarmSoundBottomSheet.kt
+++ b/feature/home/src/main/java/com/yapp/home/alarm/component/bottomsheet/AlarmSoundBottomSheet.kt
@@ -1,4 +1,4 @@
-package com.yapp.alarm.component.bottomsheet
+package com.yapp.home.alarm.component.bottomsheet
import android.net.Uri
import androidx.compose.foundation.background
@@ -10,21 +10,17 @@ 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.statusBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
-import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
-import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -34,88 +30,41 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.yapp.designsystem.theme.OrbitTheme
import com.yapp.domain.model.AlarmSound
-import com.yapp.ui.component.OrbitBottomSheet
+import com.yapp.home.alarm.addedit.AlarmAddEditContract
import com.yapp.ui.component.button.OrbitButton
import com.yapp.ui.component.radiobutton.OrbitRadioButton
import com.yapp.ui.component.slider.OrbitSlider
import com.yapp.ui.component.switch.OrbitSwitch
import feature.home.R
-import kotlinx.coroutines.launch
-@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun AlarmSoundBottomSheet(
- vibrationEnabled: Boolean,
- soundEnabled: Boolean,
- soundVolume: Int,
- soundIndex: Int,
- sounds: List,
- onVibrationToggle: () -> Unit,
- onSoundToggle: () -> Unit,
+ soundState: AlarmAddEditContract.AlarmSoundState,
+ onVibrationToggle: (Boolean) -> Unit,
+ onSoundToggle: (Boolean) -> Unit,
onVolumeChanged: (Int) -> Unit,
onSoundSelected: (Int) -> Unit,
- onComplete: () -> Unit,
- isSheetOpen: Boolean,
- onDismiss: () -> Unit,
+ onDismiss: () -> Unit = {},
+ onComplete: (vibrationEnabled: Boolean, soundEnabled: Boolean, soundVolume: Int, soundIndex: Int) -> Unit,
) {
- val scope = rememberCoroutineScope()
- val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
-
- OrbitBottomSheet(
- modifier = Modifier.statusBarsPadding(),
- isSheetOpen = isSheetOpen,
- sheetState = sheetState,
- onDismissRequest = {
- scope.launch {
- sheetState.hide()
- }.invokeOnCompletion { onDismiss() }
- },
- ) {
- BottomSheetContent(
- vibrationEnabled = vibrationEnabled,
- soundEnabled = soundEnabled,
- soundVolume = soundVolume,
- soundIndex = soundIndex,
- sounds = sounds,
- onVibrationToggle = onVibrationToggle,
- onSoundToggle = onSoundToggle,
- onVolumeChanged = onVolumeChanged,
- onSoundSelected = onSoundSelected,
- onComplete = {
- scope.launch {
- sheetState.hide()
- }.invokeOnCompletion { onComplete() }
- },
- )
- }
-}
+ var selectedVibrationEnabled by remember { mutableStateOf(soundState.isVibrationEnabled) }
+ var selectedSoundEnabled by remember { mutableStateOf(soundState.isSoundEnabled) }
+ var selectedSoundVolume by remember { mutableIntStateOf(soundState.soundVolume) }
+ var selectedSoundIndex by remember { mutableIntStateOf(soundState.soundIndex) }
-@Composable
-private fun BottomSheetContent(
- vibrationEnabled: Boolean,
- soundEnabled: Boolean,
- soundVolume: Int,
- soundIndex: Int,
- sounds: List,
- onVibrationToggle: () -> Unit,
- onSoundToggle: () -> Unit,
- onVolumeChanged: (Int) -> Unit,
- onSoundSelected: (Int) -> Unit,
- onComplete: () -> Unit,
-) {
Column(
modifier = Modifier
.fillMaxWidth()
- .padding(
- horizontal = 24.dp,
- vertical = 12.dp,
- ),
+ .padding(horizontal = 24.dp, vertical = 12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Spacer(modifier = Modifier.height(6.dp))
VibrationSection(
- isVibrationEnabled = vibrationEnabled,
- onVibrationToggle = onVibrationToggle,
+ isVibrationEnabled = selectedVibrationEnabled,
+ onVibrationToggle = {
+ selectedVibrationEnabled = !selectedVibrationEnabled
+ onVibrationToggle(selectedVibrationEnabled)
+ },
)
Spacer(
modifier = Modifier
@@ -125,13 +74,22 @@ private fun BottomSheetContent(
)
SoundSection(
modifier = Modifier.weight(1f),
- soundEnabled = soundEnabled,
- onSoundToggle = onSoundToggle,
- soundVolume = soundVolume,
- onVolumeChanged = onVolumeChanged,
- soundIndex = soundIndex,
- sounds = sounds,
- onSoundSelected = { onSoundSelected(it) },
+ soundEnabled = selectedSoundEnabled,
+ onSoundToggle = {
+ selectedSoundEnabled = !selectedSoundEnabled
+ onSoundToggle(selectedSoundEnabled)
+ },
+ soundVolume = selectedSoundVolume,
+ onVolumeChanged = {
+ selectedSoundVolume = it
+ onVolumeChanged(it)
+ },
+ soundIndex = selectedSoundIndex,
+ sounds = soundState.sounds,
+ onSoundSelected = {
+ selectedSoundIndex = it
+ onSoundSelected(it)
+ },
)
OrbitButton(
@@ -141,7 +99,15 @@ private fun BottomSheetContent(
contentColor = OrbitTheme.colors.white,
pressedContainerColor = OrbitTheme.colors.gray_500,
pressedContentColor = OrbitTheme.colors.white.copy(alpha = 0.7f),
- onClick = onComplete,
+ onClick = {
+ onDismiss()
+ onComplete(
+ selectedVibrationEnabled,
+ selectedSoundEnabled,
+ selectedSoundVolume,
+ selectedSoundIndex,
+ )
+ },
)
}
}
@@ -300,27 +266,17 @@ private fun SoundSelectionItem(
@Preview
@Composable
private fun AlarmSoundBottomSheetPreview() {
- var isVibrationEnabled by remember { mutableStateOf(true) }
- var isSoundEnabled by remember { mutableStateOf(true) }
- var soundVolume by remember { mutableIntStateOf(0) }
- var soundIndex by remember { mutableIntStateOf(0) }
- val sounds by remember { mutableStateOf((1..20).map { AlarmSound("sound $it", Uri.EMPTY) }) }
- var isSheetOpen by remember { mutableStateOf(true) }
-
OrbitTheme {
AlarmSoundBottomSheet(
- vibrationEnabled = isVibrationEnabled,
- soundEnabled = isSoundEnabled,
- soundVolume = soundVolume,
- soundIndex = soundIndex,
- sounds = sounds,
- onVibrationToggle = { isVibrationEnabled = !isVibrationEnabled },
- onSoundToggle = { isSoundEnabled = !isSoundEnabled },
- onVolumeChanged = { soundVolume = it },
- onSoundSelected = { soundIndex = it },
- onComplete = { isSheetOpen = false },
- isSheetOpen = isSheetOpen,
- onDismiss = { isSheetOpen = false },
+ soundState = AlarmAddEditContract.AlarmSoundState(
+ sounds = (1..20).map { AlarmSound("sound $it", Uri.EMPTY) },
+ ),
+ onVibrationToggle = {},
+ onSoundToggle = {},
+ onVolumeChanged = {},
+ onSoundSelected = {},
+ onComplete = { _, _, _, _ ->
+ },
)
}
}
diff --git a/feature/home/src/main/java/com/yapp/home/component/bottomsheet/AlarmListBottomSheet.kt b/feature/home/src/main/java/com/yapp/home/component/bottomsheet/AlarmListBottomSheet.kt
index 20b00907..61e759c7 100644
--- a/feature/home/src/main/java/com/yapp/home/component/bottomsheet/AlarmListBottomSheet.kt
+++ b/feature/home/src/main/java/com/yapp/home/component/bottomsheet/AlarmListBottomSheet.kt
@@ -36,6 +36,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@@ -48,10 +49,10 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
-import com.yapp.alarm.component.AlarmListItem
import com.yapp.designsystem.theme.OrbitTheme
import com.yapp.domain.model.Alarm
import com.yapp.home.HomeContract
+import com.yapp.home.alarm.component.AlarmListItem
import com.yapp.home.component.AlarmListDropDownMenu
import com.yapp.home.component.AlarmSortDropDownMenu
import com.yapp.ui.component.checkbox.OrbitCheckBox
@@ -87,23 +88,28 @@ internal fun AlarmListBottomSheet(
onToggleSelect: (Long) -> Unit,
onToggleActive: (Long) -> Unit,
onSwipe: (Long) -> Unit,
+ onExpanded: () -> Unit,
content: @Composable () -> Unit,
) {
var expandedType by remember { mutableStateOf(BottomSheetExpandState.HALF_EXPANDED) }
- val sheetState = rememberStandardBottomSheetState(
- confirmValueChange = {
- expandedType = when (it) {
- SheetValue.Expanded -> BottomSheetExpandState.EXPANDED
- else -> BottomSheetExpandState.HALF_EXPANDED
- }
- true
- },
- )
-
+ val sheetState = rememberStandardBottomSheetState()
val scaffoldState = rememberBottomSheetScaffoldState(bottomSheetState = sheetState)
val coroutineScope = rememberCoroutineScope()
+ LaunchedEffect(Unit) {
+ snapshotFlow { sheetState.currentValue }
+ .collect { value ->
+ expandedType = when (value) {
+ SheetValue.Expanded -> {
+ onExpanded()
+ BottomSheetExpandState.EXPANDED
+ }
+ SheetValue.PartiallyExpanded, SheetValue.Hidden -> BottomSheetExpandState.HALF_EXPANDED
+ }
+ }
+ }
+
val nestedScrollConnection = remember {
object : androidx.compose.ui.input.nestedscroll.NestedScrollConnection {
override fun onPreScroll(
@@ -118,13 +124,6 @@ internal fun AlarmListBottomSheet(
}
}
- LaunchedEffect(sheetState.currentValue) {
- expandedType = when (sheetState.currentValue) {
- SheetValue.Expanded -> BottomSheetExpandState.EXPANDED
- else -> BottomSheetExpandState.HALF_EXPANDED
- }
- }
-
BottomSheetScaffold(
scaffoldState = scaffoldState,
sheetContent = {
@@ -253,7 +252,6 @@ internal fun AlarmBottomSheetContent(
onClick = onClickAlarm,
onLongPress = onLongPressAlarm,
onToggleSelect = onToggleSelect,
- isAm = alarm.isAm,
hour = alarm.hour,
minute = alarm.minute,
isActive = alarm.isAlarmActive,
@@ -325,23 +323,19 @@ private fun AlarmListTopBar(
onClick = onClickMore,
)
- if (menuExpanded) {
- AlarmListDropDownMenu(
- expanded = menuExpanded,
- onDismissRequest = onDismissRequest,
- onClickEdit = onClickEdit,
- onClickSort = onClickSort,
- )
- }
+ AlarmListDropDownMenu(
+ expanded = menuExpanded,
+ onDismissRequest = onDismissRequest,
+ onClickEdit = onClickEdit,
+ onClickSort = onClickSort,
+ )
- if (sortDropDownMenuExpanded) {
- AlarmSortDropDownMenu(
- expanded = sortDropDownMenuExpanded,
- sortOrder = sortOrder,
- onDismissRequest = onDismissRequest,
- onSetSortOrder = onSetSortOrder,
- )
- }
+ AlarmSortDropDownMenu(
+ expanded = sortDropDownMenuExpanded,
+ sortOrder = sortOrder,
+ onDismissRequest = onDismissRequest,
+ onSetSortOrder = onSetSortOrder,
+ )
}
}
}
diff --git a/feature/home/src/main/java/com/yapp/home/component/bottomsheet/UpdateNoticeBottomSheet.kt b/feature/home/src/main/java/com/yapp/home/component/bottomsheet/UpdateNoticeBottomSheet.kt
new file mode 100644
index 00000000..fd38d5c2
--- /dev/null
+++ b/feature/home/src/main/java/com/yapp/home/component/bottomsheet/UpdateNoticeBottomSheet.kt
@@ -0,0 +1,150 @@
+package com.yapp.home.component.bottomsheet
+
+import android.content.pm.PackageManager
+import android.os.Build
+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.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxSize
+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.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalInspectionMode
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import coil.compose.AsyncImage
+import com.yapp.designsystem.theme.OrbitTheme
+import feature.home.R
+
+private fun resolveVersionName(ctx: android.content.Context): String {
+ return runCatching {
+ val pm = ctx.packageManager
+ val packageName = ctx.packageName
+ val info = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ pm.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0))
+ } else {
+ @Suppress("DEPRECATION")
+ pm.getPackageInfo(packageName, 0)
+ }
+ info.versionName ?: ""
+ }.getOrDefault("")
+}
+
+private fun bannerUrl(versionName: String): String =
+ "https://www.orbitalarm.net/images/aos/$versionName/update-banner.png"
+
+@Composable
+internal fun UpdateNoticeBottomSheet(
+ onDontShowAgain: () -> Unit,
+ onClose: () -> Unit,
+) {
+ val context = LocalContext.current
+ val isPreview = LocalInspectionMode.current
+
+ val versionName = remember(isPreview) {
+ if (isPreview) "preview" else resolveVersionName(context)
+ }
+ val imageUrl = remember(versionName) { bannerUrl(versionName.ifEmpty { "unknown" }) }
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color(0xFF17191F).copy(alpha = 0.85f))
+ .clickable(onClick = onClose),
+ contentAlignment = Alignment.BottomCenter,
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(
+ color = OrbitTheme.colors.gray_900,
+ shape = RoundedCornerShape(topStart = 30.dp, topEnd = 30.dp),
+ )
+ .clip(RoundedCornerShape(topStart = 30.dp, topEnd = 30.dp))
+ .clickable(
+ indication = null,
+ interactionSource = remember { MutableInteractionSource() },
+ ) { },
+ ) {
+ if (isPreview) {
+ // ํ๋ฆฌ๋ทฐ์ฉ ๋ฐ์ค
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .aspectRatio(1f)
+ .background(color = OrbitTheme.colors.white),
+ )
+ } else {
+ AsyncImage(
+ model = imageUrl,
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ modifier = Modifier
+ .fillMaxWidth()
+ .aspectRatio(1f),
+ )
+ }
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 8.dp, bottom = 20.dp, start = 20.dp, end = 20.dp),
+ horizontalArrangement = Arrangement.spacedBy(10.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Box(
+ modifier = Modifier
+ .weight(1f)
+ .clickable(onClick = onDontShowAgain)
+ .padding(vertical = 14.dp),
+ contentAlignment = Alignment.Center,
+ ) {
+ Text(
+ text = stringResource(id = R.string.update_notice_bottom_sheet_dont_show_again),
+ style = OrbitTheme.typography.body1SemiBold,
+ color = OrbitTheme.colors.white,
+ )
+ }
+ Box(
+ modifier = Modifier
+ .weight(1f)
+ .clickable(onClick = onClose)
+ .padding(vertical = 14.dp),
+ contentAlignment = Alignment.Center,
+ ) {
+ Text(
+ text = stringResource(id = R.string.update_notice_bottom_sheet_close),
+ style = OrbitTheme.typography.body1SemiBold,
+ color = OrbitTheme.colors.white,
+ )
+ }
+ }
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun UpdateNoticeBottomSheetPreview() {
+ OrbitTheme {
+ UpdateNoticeBottomSheet(
+ onDontShowAgain = {},
+ onClose = {},
+ )
+ }
+}
diff --git a/feature/home/src/main/java/com/yapp/home/util/AlarmDateTimeFormatter.kt b/feature/home/src/main/java/com/yapp/home/util/AlarmDateTimeFormatter.kt
new file mode 100644
index 00000000..448cc811
--- /dev/null
+++ b/feature/home/src/main/java/com/yapp/home/util/AlarmDateTimeFormatter.kt
@@ -0,0 +1,220 @@
+package com.yapp.home.util
+
+import android.util.Log
+import com.yapp.domain.model.Alarm
+import com.yapp.domain.model.toAlarmDays
+import com.yapp.domain.model.toDayOfWeek
+import java.time.Clock
+import java.time.Duration
+import java.time.LocalDateTime
+import java.time.LocalTime
+import java.time.format.DateTimeFormatter
+import java.time.format.DateTimeParseException
+import java.util.Locale
+import javax.inject.Inject
+
+class AlarmDateTimeFormatter @Inject constructor(
+ private val clock: Clock,
+ private val displayLocale: Locale,
+) {
+
+ companion object {
+ private const val NO_ALARM_STRING = "NONE"
+ private const val DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm"
+ }
+
+ data class DeliveryTimeFormats(
+ val noAlarm: String,
+ val today: String, // ์: "์ค๋ %s"
+ val tomorrow: String, // ์: "๋ด์ผ %s"
+ val thisYear: String, // ์: "%s" (๋ ์ง์ ์๊ฐ๋ง)
+ val otherYear: String, // ์: "%s" (๋
๋, ๋ ์ง, ์๊ฐ)
+ val todayTimePattern: String = "a h:mm",
+ val thisYearDatePattern: String = "M์ d์ผ a h:mm",
+ val otherYearDatePattern: String = "yy๋
M์ d์ผ a h:mm",
+ )
+
+ data class TimeDifferenceFormats(
+ val daysHoursMinutesFormat: String, // ์: "%1$d์ผ %2$d์๊ฐ %3$d๋ถ ํ์ ์ธ๋ ค์"
+ val hoursMinutesFormat: String, // ์: "%1$d์๊ฐ %2$d๋ถ ํ์ ์ธ๋ ค์"
+ val minutesFormat: String, // ์: "%1$d๋ถ ํ์ ์ธ๋ ค์"
+ val soonFormat: String, // ์: "๊ณง ์ธ๋ ค์"
+ )
+
+ fun calculateNextOccurrence(
+ hour: Int,
+ minute: Int,
+ repeatDays: Int,
+ now: LocalDateTime = LocalDateTime.now(clock),
+ ): LocalDateTime {
+ val alarmTime = LocalTime.of(hour, minute)
+ val todayAlarmDateTime = LocalDateTime.of(now.toLocalDate(), alarmTime)
+
+ if (repeatDays == 0) { // ๋จ์ผ ์๋
+ return if (todayAlarmDateTime.isAfter(now)) {
+ todayAlarmDateTime
+ } else {
+ todayAlarmDateTime.plusDays(1)
+ }
+ }
+
+ val selectedDaysOfWeek = repeatDays.toAlarmDays()
+ .map { it.toDayOfWeek() }
+ .sortedBy { it.value }
+
+ require(selectedDaysOfWeek.isNotEmpty()) {
+ "๋ฐ๋ณต ์๋์ ์ต์ ํ๋ ์ด์์ ์์ผ์ ์ ํํด์ผ ํฉ๋๋ค. repeatDays: $repeatDays"
+ }
+
+ val currentDayOfWeek = now.dayOfWeek
+
+ // ์ค๋ ์๋์ด ๊ฐ๋ฅํ์ง ํ์ธ
+ if (selectedDaysOfWeek.contains(currentDayOfWeek) && todayAlarmDateTime.isAfter(now)) {
+ return todayAlarmDateTime
+ }
+
+ for (dayOffset in 1..7) {
+ val nextPotentialDate = now.toLocalDate().plusDays(dayOffset.toLong())
+ val dayOfWeekPotentialDate = nextPotentialDate.dayOfWeek
+ val potentialAlarmDateTime = nextPotentialDate.atTime(alarmTime)
+
+ if (selectedDaysOfWeek.contains(dayOfWeekPotentialDate)) {
+ return potentialAlarmDateTime
+ }
+ }
+
+ error("๋ฐ๋ณต ์๋์ ๋ค์ ๋ฐ์ ์๊ฐ์ ๊ณ์ฐํ ์ ์์ต๋๋ค. selectedDaysOfWeek: $selectedDaysOfWeek")
+ }
+
+ private fun formatDeliveryDateTimeString(
+ deliveryDateTimeString: String, // "yyyy-MM-dd'T'HH:mm" ํฌ๋งท ๋๋ "NONE"
+ formats: DeliveryTimeFormats,
+ now: LocalDateTime = LocalDateTime.now(clock),
+ ): String {
+ return try {
+ if (deliveryDateTimeString.equals(NO_ALARM_STRING, ignoreCase = true)) {
+ return formats.noAlarm
+ }
+
+ val inputFormatter =
+ DateTimeFormatter.ofPattern(DATE_TIME_FORMAT).withLocale(displayLocale)
+ val alarmOccurrenceDateTime = LocalDateTime.parse(
+ deliveryDateTimeString,
+ inputFormatter,
+ )
+ val today = now.toLocalDate()
+ val tomorrow = today.plusDays(1)
+
+ when {
+ // 1. ๋
๋๊ฐ ํ์ฌ ๋
๋์ ๋ค๋ฅด๋ฉด 'otherYear' ํฌ๋งท ์ ์ฉ
+ alarmOccurrenceDateTime.year != now.year -> {
+ val formattedDateTime = alarmOccurrenceDateTime.format(
+ DateTimeFormatter.ofPattern(formats.otherYearDatePattern)
+ .withLocale(displayLocale),
+ )
+ String.format(formats.otherYear, formattedDateTime)
+ }
+ // 2. (๋
๋๊ฐ ๊ฐ๊ณ ) ๋ ์ง๊ฐ ์ค๋์ด๋ฉด 'today' ํฌ๋งท ์ ์ฉ
+ alarmOccurrenceDateTime.toLocalDate() == today -> {
+ val formattedTime = alarmOccurrenceDateTime.format(
+ DateTimeFormatter.ofPattern(formats.todayTimePattern)
+ .withLocale(displayLocale),
+ )
+ String.format(formats.today, formattedTime)
+ }
+ // 3. (๋
๋๊ฐ ๊ฐ๊ณ ) ๋ ์ง๊ฐ ๋ด์ผ์ด๋ฉด 'tomorrow' ํฌ๋งท ์ ์ฉ
+ alarmOccurrenceDateTime.toLocalDate() == tomorrow -> {
+ val formattedTime = alarmOccurrenceDateTime.format( // ๋ด์ผ๋ ์๊ฐ ํฌ๋งท ์ฌ์ฉ
+ DateTimeFormatter.ofPattern(formats.todayTimePattern)
+ .withLocale(displayLocale),
+ )
+ String.format(formats.tomorrow, formattedTime)
+ }
+ // 4. ๊ทธ ์ธ์ ๊ฒฝ์ฐ (๋
๋๊ฐ ๊ฐ๊ณ , ์ค๋์ด๋ ๋ด์ผ์ด ์๋ ๋ค๋ฅธ ๋ ) 'thisYear' ํฌ๋งท ์ ์ฉ
+ else -> {
+ val formattedDateTime = alarmOccurrenceDateTime.format(
+ DateTimeFormatter.ofPattern(formats.thisYearDatePattern)
+ .withLocale(displayLocale),
+ )
+ String.format(formats.thisYear, formattedDateTime)
+ }
+ }
+ } catch (e: DateTimeParseException) {
+ Log.e("AlarmDateTimeFormatter", "Invalid date format: $deliveryDateTimeString", e)
+ formats.noAlarm
+ } catch (e: Exception) {
+ Log.e(
+ "AlarmDateTimeFormatter",
+ "Error formatting delivery date time: $deliveryDateTimeString",
+ e,
+ )
+ formats.noAlarm
+ }
+ }
+
+ fun getFormattedEarliestUpcomingAlarmDeliveryTime(
+ alarms: List,
+ formats: DeliveryTimeFormats,
+ now: LocalDateTime = LocalDateTime.now(clock),
+ ): String {
+ val earliestAlarmDateTime = alarms
+ .filter { it.isAlarmActive }
+ .mapNotNull { alarm ->
+ try {
+ calculateNextOccurrence(alarm.hour, alarm.minute, alarm.repeatDays, now)
+ } catch (e: Exception) {
+ Log.e(
+ "AlarmDateTimeFormatter",
+ "Error calculating next occurrence for alarm: $alarm",
+ e,
+ )
+ null // ์์ธ ๋ฐ์ ์ null๋ก ์ฒ๋ฆฌ
+ }
+ }
+ .minOrNull()
+
+ val deliveryDateTimeString = earliestAlarmDateTime?.format(
+ DateTimeFormatter.ofPattern(DATE_TIME_FORMAT).withLocale(displayLocale),
+ ) ?: NO_ALARM_STRING
+
+ return formatDeliveryDateTimeString(deliveryDateTimeString, formats, now)
+ }
+
+ fun formatTimeDifference(
+ baseTime: LocalDateTime,
+ futureTime: LocalDateTime,
+ formats: TimeDifferenceFormats,
+ ): String {
+ if (!futureTime.isAfter(baseTime)) {
+ return formats.soonFormat
+ }
+
+ val duration = Duration.between(baseTime, futureTime)
+ val totalMinutes = duration.toMinutes()
+
+ if (totalMinutes < 1) {
+ return formats.soonFormat
+ }
+
+ val days = duration.toDays()
+ val remainingHours = duration.toHours() % 24
+ val remainingMinutes = duration.toMinutes() % 60
+
+ return when {
+ days > 0 -> String.format(
+ formats.daysHoursMinutesFormat,
+ days,
+ remainingHours,
+ remainingMinutes,
+ )
+
+ remainingHours > 0 -> String.format(
+ formats.hoursMinutesFormat,
+ remainingHours,
+ remainingMinutes,
+ )
+
+ else -> String.format(formats.minutesFormat, remainingMinutes)
+ }
+ }
+}
diff --git a/feature/home/src/main/res/values/strings.xml b/feature/home/src/main/res/values/strings.xml
index cfe4c73c..b2266b16 100644
--- a/feature/home/src/main/res/values/strings.xml
+++ b/feature/home/src/main/res/values/strings.xml
@@ -32,6 +32,33 @@
ํ
๊ณตํด์ผ ์๋ ๋๊ธฐ
+ ๋ฏธ์
+ %1$s, %2$dํ
+ ํ๋ค๊ธฐ
+ ํฐ์นํ๊ธฐ
+ ์์
+
+
+ ๋ฏธ์
+
+ ๋ฑ๋ก๋ ๋ฏธ์
์ด ์์ด์
+ ์ ๋ฏธ์
์ ์ถ๊ฐํด๋ณด์ธ์
+ ๋ฏธ์
์ถ๊ฐ
+
+ ๋ฏธ์
๋ณ๊ฒฝ
+ ์๋ฃ
+
+ ๋ฏธ์
์ ํ
+ ์ ํ๋จ
+
+ %dํ
+
+ ํ์
+ ์ฌ์
+ ์ด๋ ค์
+ ๋ฏธ๋ฆฌ๋ณด๊ธฐ
+ ๋ฏธ์
์ ์ฅ
+
%s, %s
์ ํจ
@@ -92,4 +119,12 @@
์๋ ๋ฏธ๋ฃจ๊ธฐ
๋จ์ ์๊ฐ
+
+ %1$d์ผ %2$d์๊ฐ ํ์ ์ธ๋ ค์
+ %1$d์๊ฐ %2$d๋ถ ํ์ ์ธ๋ ค์
+ %d๋ถ ํ์ ์ธ๋ ค์
+ ๊ณง ์ธ๋ ค์
+
+ ๋ค์ ๋ณด์ง ์๊ธฐ
+ ๋ซ๊ธฐ
diff --git a/feature/home/src/test/kotlin/com/yapp/home/util/AlarmDateTimeFormatterTest.kt b/feature/home/src/test/kotlin/com/yapp/home/util/AlarmDateTimeFormatterTest.kt
new file mode 100644
index 00000000..c6ab9554
--- /dev/null
+++ b/feature/home/src/test/kotlin/com/yapp/home/util/AlarmDateTimeFormatterTest.kt
@@ -0,0 +1,205 @@
+package com.yapp.home.util
+
+import com.yapp.domain.model.Alarm
+import com.yapp.domain.model.AlarmDay
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import java.time.Clock
+import java.time.LocalDateTime
+import java.time.ZoneId
+import java.time.format.DateTimeFormatter
+import java.util.Locale
+
+class AlarmDateTimeFormatterTest {
+
+ private lateinit var formatter: AlarmDateTimeFormatter
+ private val fixedNow = LocalDateTime.of(2023, 10, 26, 10, 0, 0) // ๋ชฉ์์ผ
+ private val fixedClock = Clock.fixed(fixedNow.atZone(ZoneId.of("Asia/Seoul")).toInstant(), ZoneId.of("Asia/Seoul"))
+ private val testLocale: Locale = Locale.KOREA
+
+ @Before
+ fun `ํ
์คํธ_์ค๋น`() {
+ formatter = AlarmDateTimeFormatter(clock = fixedClock, displayLocale = testLocale)
+ }
+
+ private fun getLocalizedFormatter(pattern: String): DateTimeFormatter {
+ return DateTimeFormatter.ofPattern(pattern).withLocale(testLocale)
+ }
+
+ private val deliveryFormats = AlarmDateTimeFormatter.DeliveryTimeFormats(
+ noAlarm = "๋ฐ์ ์ ์๋ ์ด์ธ๊ฐ ์์ด์",
+ today = "%1\$s ๋์ฐฉ",
+ tomorrow = "๋ด์ผ %1\$s ๋์ฐฉ",
+ thisYear = "%1\$s ๋์ฐฉ",
+ otherYear = "%1\$s ๋์ฐฉ",
+ todayTimePattern = "a h:mm",
+ thisYearDatePattern = "M์ d์ผ a h:mm",
+ otherYearDatePattern = "yy๋
M์ d์ผ a h:mm"
+ )
+
+ @Test
+ fun `๊ฐ์ฅ๋น ๋ฅธ_์๋์๊ฐ_ํฌ๋งทํ
_ํ์ฑ์๋_์์ผ๋ฉด_์์ ๋_์๋์์_๋ฐํ`() {
+ // given
+ val alarms = listOf(Alarm(id = 1, hour = 14, minute = 0, repeatDays = 0, isAlarmActive = false))
+
+ // when
+ val result = formatter.getFormattedEarliestUpcomingAlarmDeliveryTime(alarms, deliveryFormats, fixedNow)
+
+ // then
+ assertEquals(deliveryFormats.noAlarm, result)
+ }
+
+ @Test
+ fun `๊ฐ์ฅ๋น ๋ฅธ_์๋์๊ฐ_ํฌ๋งทํ
_์ค๋_๋ฏธ๋_ํ์ฑ์๋_ํ๋๋ฉด_์์ ๋_์ค๋ํ์_๋ฐํ`() {
+ // given
+ val alarms = listOf(Alarm(id = 1, hour = 14, minute = 30, repeatDays = 0, isAlarmActive = true))
+ val expectedTime = LocalDateTime.of(2023, 10, 26, 14, 30)
+ val expected = String.format(deliveryFormats.today, expectedTime.format(getLocalizedFormatter(deliveryFormats.todayTimePattern)))
+
+ // when
+ val result = formatter.getFormattedEarliestUpcomingAlarmDeliveryTime(alarms, deliveryFormats, fixedNow)
+
+ // then
+ assertEquals(expected, result)
+ }
+
+ @Test
+ fun `๊ฐ์ฅ๋น ๋ฅธ_์๋์๊ฐ_ํฌ๋งทํ
_๋ด์ผ_ํ์ฑ์๋_ํ๋๋ฉด_์์ ๋_๋ด์ผํ์_๋ฐํ`() {
+ // given
+ val alarms = listOf(Alarm(id = 1, hour = 8, minute = 0, repeatDays = 0, isAlarmActive = true))
+ val expectedTime = fixedNow.toLocalDate().plusDays(1).atTime(8, 0)
+ val expected = String.format(deliveryFormats.tomorrow, expectedTime.format(getLocalizedFormatter(deliveryFormats.todayTimePattern)))
+
+ // when
+ val result = formatter.getFormattedEarliestUpcomingAlarmDeliveryTime(alarms, deliveryFormats, fixedNow)
+
+ // then
+ assertEquals(expected, result)
+ }
+
+ @Test
+ fun `๊ฐ์ฅ๋น ๋ฅธ_์๋์๊ฐ_ํฌ๋งทํ
_์ฌํด_๋ค๋ฅธ๋ ์ง๋ฉด_์์ ๋_์ฌํดํ์_๋ฐํ`() {
+ // given
+ val alarms = listOf(Alarm(id = 1, hour = 14, minute = 30, repeatDays = AlarmDay.SUN.bitValue, isAlarmActive = true))
+ val expectedTime = LocalDateTime.of(2023, 10, 29, 14, 30)
+ val expected = String.format(deliveryFormats.thisYear, expectedTime.format(getLocalizedFormatter(deliveryFormats.thisYearDatePattern)))
+
+ // when
+ val result = formatter.getFormattedEarliestUpcomingAlarmDeliveryTime(alarms, deliveryFormats, fixedNow)
+
+ // then
+ assertEquals(expected, result)
+ }
+
+ @Test
+ fun `๊ฐ์ฅ๋น ๋ฅธ_์๋์๊ฐ_ํฌ๋งทํ
_๋ค๋ฅธํด๋ฉด_์์ ๋_๋ค๋ฅธํดํ์_๋ฐํ`() {
+ // given
+ val now = LocalDateTime.of(2023, 12, 31, 10, 0, 0)
+ val alarms = listOf(Alarm(id = 1, hour = 9, minute = 0, repeatDays = 0, isAlarmActive = true))
+ val expectedTime = LocalDateTime.of(2024, 1, 1, 9, 0)
+ val expected = String.format(deliveryFormats.otherYear, expectedTime.format(getLocalizedFormatter(deliveryFormats.otherYearDatePattern)))
+
+ // when
+ val result = formatter.getFormattedEarliestUpcomingAlarmDeliveryTime(alarms, deliveryFormats, now)
+
+ // then
+ assertEquals(expected, result)
+ }
+
+ @Test
+ fun `๊ฐ์ฅ๋น ๋ฅธ_์๋์๊ฐ_ํฌ๋งทํ
_์ฌ๋ฌ_ํ์ฑ์๋์ค_๊ฐ์ฅ๋น ๋ฅธ๊ฒ_์ ํํ_ํฌ๋งทํ
_์์ ๋ํ์`() {
+ // given
+ val alarms = listOf(
+ Alarm(id = 1, hour = 15, minute = 0, repeatDays = 0, isAlarmActive = true), // ์ค๋ 15:00
+ Alarm(id = 2, hour = 12, minute = 0, repeatDays = 0, isAlarmActive = true), // ์ค๋ 12:00 (์ด๊ฒ ๋ ๋น ๋ฆ)
+ Alarm(id = 3, hour = 9, minute = 0, repeatDays = 0, isAlarmActive = false),
+ Alarm(id = 4, hour = 8, minute = 0, repeatDays = AlarmDay.FRI.bitValue, isAlarmActive = true) // ๋ด์ผ 08:00
+ )
+ val expectedTime = LocalDateTime.of(2023, 10, 26, 12, 0)
+ val expected = String.format(deliveryFormats.today, expectedTime.format(getLocalizedFormatter(deliveryFormats.todayTimePattern)))
+
+ // when
+ val result = formatter.getFormattedEarliestUpcomingAlarmDeliveryTime(alarms, deliveryFormats, fixedNow)
+
+ // then
+ assertEquals(expected, result)
+ }
+
+ @Test
+ fun `๋ ์ง์๊ฐ๋ฌธ์์ด_ํฌ๋งทํ
_์๋ชป๋_๋ ์งํ์์ด๋ฉด_์์ ๋_์๋์์_๋ฐํ`() {
+ // given
+ val alarms = emptyList()
+
+ // when
+ val result = formatter.getFormattedEarliestUpcomingAlarmDeliveryTime(alarms, deliveryFormats, fixedNow)
+
+ // then
+ assertEquals(deliveryFormats.noAlarm, result)
+ }
+
+ private val timeFormats = AlarmDateTimeFormatter.TimeDifferenceFormats(
+ daysHoursMinutesFormat = "%1\$d์ผ %2\$d์๊ฐ %3\$d๋ถ ํ์ ์ธ๋ ค์",
+ hoursMinutesFormat = "%1\$d์๊ฐ %2\$d๋ถ ํ์ ์ธ๋ ค์",
+ minutesFormat = "%1\$d๋ถ ํ์ ์ธ๋ ค์",
+ soonFormat = "๊ณง ์ธ๋ ค์"
+ )
+
+ @Test
+ fun `์๊ฐ์ฐจ์ด_ํฌ๋งทํ
_์ฐจ์ด์๊ฑฐ๋_๊ณผ๊ฑฐ๋ฉด_๊ณง์ธ๋ ค์_๋ฐํ`() {
+ // when & then
+ assertEquals(timeFormats.soonFormat, formatter.formatTimeDifference(fixedNow, fixedNow, timeFormats))
+ assertEquals(timeFormats.soonFormat, formatter.formatTimeDifference(fixedNow, fixedNow.minusMinutes(1), timeFormats))
+ }
+
+ @Test
+ fun `์๊ฐ์ฐจ์ด_ํฌ๋งทํ
_1๋ถ๋ฏธ๋ง_์ฐจ์ด๋ฉด_๊ณง์ธ๋ ค์_๋ฐํ`() {
+ // given
+ val future = fixedNow.plusSeconds(30)
+
+ // when
+ val result = formatter.formatTimeDifference(fixedNow, future, timeFormats)
+
+ // then
+ assertEquals(timeFormats.soonFormat, result)
+ }
+
+ @Test
+ fun `์๊ฐ์ฐจ์ด_ํฌ๋งทํ
_25๋ถ_์ฐจ์ด๋ฉด_์ ํํ_๋ฌธ์์ด_๋ฐํ`() {
+ // given
+ val futureTime = fixedNow.plusMinutes(25)
+ val expected = String.format(testLocale, timeFormats.minutesFormat, 25L)
+
+ // when
+ val result = formatter.formatTimeDifference(fixedNow, futureTime, timeFormats)
+
+ // then
+ assertEquals(expected, result)
+ }
+
+ @Test
+ fun `์๊ฐ์ฐจ์ด_ํฌ๋งทํ
_70๋ถ_์ฐจ์ด๋ฉด_์ ํํ_๋ฌธ์์ด_๋ฐํ`() {
+ // given
+ val futureTime = fixedNow.plusMinutes(70)
+ val expected = String.format(testLocale, timeFormats.hoursMinutesFormat, 1L, 10L)
+
+ // when
+ val result = formatter.formatTimeDifference(fixedNow, futureTime, timeFormats)
+
+ // then
+ assertEquals(expected, result)
+ }
+
+ @Test
+ fun `์๊ฐ์ฐจ์ด_ํฌ๋งทํ
_1์ผ_1์๊ฐ_5๋ถ_์ฐจ์ด๋ฉด_์ ํํ_๋ฌธ์์ด_๋ฐํ`() {
+ // given
+ val futureTime = fixedNow.plusDays(1).plusHours(1).plusMinutes(5)
+ val expected = String.format(testLocale, timeFormats.daysHoursMinutesFormat, 1L, 1L, 5L)
+
+ // when
+ val result = formatter.formatTimeDifference(fixedNow, futureTime, timeFormats)
+
+ // then
+ assertEquals(expected, result)
+ }
+}
diff --git a/feature/mission/build.gradle.kts b/feature/mission/build.gradle.kts
index 95ffd72c..eda23f95 100644
--- a/feature/mission/build.gradle.kts
+++ b/feature/mission/build.gradle.kts
@@ -15,7 +15,6 @@ dependencies {
implementation(projects.core.media)
implementation(projects.core.alarm)
implementation(projects.domain)
- implementation(projects.core.datastore)
implementation(libs.orbit.core)
implementation(libs.orbit.compose)
implementation(libs.orbit.viewmodel)
diff --git a/feature/mission/src/main/java/com/yapp/mission/MissionContract.kt b/feature/mission/src/main/java/com/yapp/mission/MissionContract.kt
index f1812c49..8e5c61d4 100644
--- a/feature/mission/src/main/java/com/yapp/mission/MissionContract.kt
+++ b/feature/mission/src/main/java/com/yapp/mission/MissionContract.kt
@@ -1,15 +1,17 @@
package com.yapp.mission
+import com.yapp.domain.MissionMode
import com.yapp.domain.model.MissionType
sealed class MissionContract {
data class State(
- val missionType: MissionType = MissionType.Click,
+ val missionMode: MissionMode = MissionMode.REAL,
+ val missionType: MissionType = MissionType.TAP,
val isMissionTypeLoading: Boolean = true,
+ val missionCount: Int = 10,
+ val currentCount: Int = 0,
val isMissionCompleted: Boolean = false,
- val shakeCount: Int = 0,
- val clickCount: Int = 0,
val playWhenClick: Boolean = false,
val showFinalAnimation: Boolean = false,
val isFlipped: Boolean = false,
@@ -20,16 +22,16 @@ sealed class MissionContract {
) : com.yapp.ui.base.UiState
sealed class Action {
+ data object NavigateBack : Action()
data object ShakeCard : Action()
data object ClickCard : Action()
data object ShowExitDialog : Action()
data object HideExitDialog : Action()
- data object RetryPostFortune : Action()
}
sealed class SideEffect : com.yapp.ui.base.SideEffect {
data object NavigateToFortune : SideEffect()
-
+ data object NavigateToHome : SideEffect()
data object NavigateBack : SideEffect()
}
}
diff --git a/feature/mission/src/main/java/com/yapp/mission/MissionNavGraph.kt b/feature/mission/src/main/java/com/yapp/mission/MissionNavGraph.kt
index a24f0545..5befebf4 100644
--- a/feature/mission/src/main/java/com/yapp/mission/MissionNavGraph.kt
+++ b/feature/mission/src/main/java/com/yapp/mission/MissionNavGraph.kt
@@ -1,6 +1,5 @@
package com.yapp.mission
-import androidx.compose.runtime.LaunchedEffect
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import androidx.navigation.navDeepLink
@@ -8,6 +7,7 @@ import androidx.navigation.navOptions
import com.yapp.common.navigation.OrbitNavigator
import com.yapp.common.navigation.extensions.sharedHiltViewModel
import com.yapp.common.navigation.route.MissionRoute
+import org.orbitmvi.orbit.compose.collectSideEffect
fun NavGraphBuilder.missionScreen(
navigator: OrbitNavigator,
@@ -15,30 +15,48 @@ fun NavGraphBuilder.missionScreen(
composable(
deepLinks = listOf(
navDeepLink {
- uriPattern = "orbitapp://mission?notificationId={notificationId}"
+ uriPattern = "orbitapp://mission?notificationId={notificationId}&missionType={missionType}&missionCount={missionCount}"
},
),
) {
val viewModel = it.sharedHiltViewModel(navigator.navController)
- LaunchedEffect(viewModel) {
- viewModel.container.sideEffectFlow.collect { sideEffect ->
- when (sideEffect) {
- MissionContract.SideEffect.NavigateToFortune -> {
- navigator.navigateToFortune(
- navOptions = navOptions {
- popUpTo(MissionRoute) {
- inclusive = true
- }
- },
- )
+ viewModel.collectSideEffect {
+ handleSideEffect(it, navigator)
+ }
+
+ MissionRoute(
+ navigator = navigator,
+ viewModel = viewModel,
+ )
+ }
+}
+
+private fun handleSideEffect(
+ sideEffect: MissionContract.SideEffect,
+ navigator: OrbitNavigator,
+) {
+ when (sideEffect) {
+ MissionContract.SideEffect.NavigateToFortune -> {
+ navigator.navigateToFortune(
+ navOptions = navOptions {
+ popUpTo {
+ inclusive = true
}
+ },
+ )
+ }
- MissionContract.SideEffect.NavigateBack -> navigator.navigateBack()
- }
- }
+ MissionContract.SideEffect.NavigateToHome -> {
+ navigator.navigateToHome(
+ navOptions = navOptions {
+ popUpTo {
+ inclusive = true
+ }
+ },
+ )
}
- MissionRoute(viewModel)
+ MissionContract.SideEffect.NavigateBack -> navigator.navigateBack()
}
}
diff --git a/feature/mission/src/main/java/com/yapp/mission/MissionScreen.kt b/feature/mission/src/main/java/com/yapp/mission/MissionScreen.kt
index cd42cdd1..65249b8c 100644
--- a/feature/mission/src/main/java/com/yapp/mission/MissionScreen.kt
+++ b/feature/mission/src/main/java/com/yapp/mission/MissionScreen.kt
@@ -1,5 +1,6 @@
package com.yapp.mission
+import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.BackHandler
import androidx.compose.animation.Crossfade
@@ -9,14 +10,21 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.asPaddingValues
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.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -31,6 +39,7 @@ import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
@@ -38,7 +47,9 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.yapp.analytics.AnalyticsEvent
import com.yapp.analytics.AnalyticsHelper
import com.yapp.analytics.LocalAnalyticsHelper
+import com.yapp.common.navigation.OrbitNavigator
import com.yapp.designsystem.theme.OrbitTheme
+import com.yapp.domain.MissionMode
import com.yapp.domain.model.MissionType
import com.yapp.mission.component.FlipCard
import com.yapp.mission.component.MissionProgressBar
@@ -47,9 +58,13 @@ import com.yapp.ui.component.lottie.LottieAnimation
import com.yapp.ui.extensions.customClickable
import com.yapp.ui.utils.heightForScreenPercentage
import com.yapp.ui.utils.paddingForScreenPercentage
+import feature.mission.R
@Composable
-fun MissionRoute(viewModel: MissionViewModel = hiltViewModel()) {
+fun MissionRoute(
+ viewModel: MissionViewModel = hiltViewModel(),
+ navigator: OrbitNavigator,
+) {
val state by viewModel.container.stateFlow.collectAsStateWithLifecycle()
val context = LocalContext.current
@@ -59,6 +74,21 @@ fun MissionRoute(viewModel: MissionViewModel = hiltViewModel()) {
}
}
+ BackHandler {
+ if (state.missionMode == MissionMode.PREVIEW) {
+ navigator.navigateBack()
+ return@BackHandler
+ }
+
+ viewModel.processAction(
+ if (state.showExitDialog) {
+ MissionContract.Action.HideExitDialog
+ } else {
+ MissionContract.Action.ShowExitDialog
+ },
+ )
+ }
+
LaunchedEffect(Unit) {
shakeDetector.start()
}
@@ -76,10 +106,6 @@ fun MissionRoute(viewModel: MissionViewModel = hiltViewModel()) {
)
}
-/**
- * Mission ์ํ์ ๋ฐ๋ผ ์ ์ ํ ํ๋ฉด์ ๊ตฌ์ฑํ๋ ๋ฉ์ธ ์ปจํ
์ด๋.
- * ๋ก๋ฉ, ์ฝํ
์ธ , ์ฑ๊ณต ์ค๋ฒ๋ ์ด, ๋ค์ด์ผ๋ก๊ทธ ๋ฑ ๋ถ๊ธฐ ์ฒ๋ฆฌ ํฌํจ.
- */
@Composable
fun MissionScreen(
stateProvider: () -> MissionContract.State,
@@ -89,18 +115,10 @@ fun MissionScreen(
val state = stateProvider()
val analytics = LocalAnalyticsHelper.current
- BackHandler {
- eventDispatcher(
- if (state.showExitDialog) {
- MissionContract.Action.HideExitDialog
- } else {
- MissionContract.Action.ShowExitDialog
- },
- )
- }
-
- Box(modifier = Modifier.fillMaxSize()) {
- if (state.isMissionTypeLoading) {
+ Box(
+ modifier = Modifier.fillMaxSize(),
+ ) {
+ if (state.isMissionTypeLoading || state.missionType == MissionType.NONE) {
MissionLoadingScreen()
return
}
@@ -112,7 +130,10 @@ fun MissionScreen(
modifier = Modifier.matchParentSize(),
)
- MissionContent(state, eventDispatcher)
+ MissionContent(
+ state = state,
+ eventDispatcher = eventDispatcher,
+ )
if (state.showExitDialog) {
ExitDialog(state, eventDispatcher, onFinish, analytics)
@@ -122,17 +143,35 @@ fun MissionScreen(
MissionSuccessOverlay()
}
- state.errorMessage?.let {
- ErrorDialog(message = it) {
- eventDispatcher(MissionContract.Action.RetryPostFortune)
+ if (state.missionMode == MissionMode.PREVIEW) {
+ val insets = WindowInsets.navigationBars.asPaddingValues()
+
+ Button(
+ onClick = {
+ eventDispatcher(MissionContract.Action.NavigateBack)
+ },
+ shape = CircleShape,
+ colors = ButtonDefaults.buttonColors(
+ containerColor = OrbitTheme.colors.white,
+ contentColor = OrbitTheme.colors.gray_900,
+ ),
+ contentPadding = PaddingValues(
+ horizontal = 24.dp,
+ vertical = 12.dp,
+ ),
+ modifier = Modifier
+ .align(Alignment.BottomCenter)
+ .padding(bottom = insets.calculateBottomPadding() + 28.dp),
+ ) {
+ Text(
+ text = stringResource(id = R.string.mission_preview_exit),
+ style = OrbitTheme.typography.body1SemiBold,
+ )
}
}
}
}
-/**
- * ๋ฏธ์
์ฝํ
์ธ ๋ณธ๋ฌธ. TopBar, ์งํ ๋ฐ, ์ํ๋ณ ๊ฒ์ ํฌํจ.
- */
@Composable
fun MissionContent(
state: MissionContract.State,
@@ -142,32 +181,39 @@ fun MissionContent(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
- MissionTopAppBar(onExit = { eventDispatcher(MissionContract.Action.ShowExitDialog) })
+ MissionTopAppBar(
+ mode = state.missionMode,
+ onExit = { eventDispatcher(MissionContract.Action.ShowExitDialog) },
+ )
MissionProgressBarSection(state)
MissionLabel(state)
Spacer(modifier = Modifier.heightForScreenPercentage(0.0665f))
when (state.missionType) {
- is MissionType.Shake -> {
- if (state.shakeCount == 0) {
+ MissionType.SHAKE -> {
+ if (state.currentCount == 0) {
MissionShakeInitialImage()
} else {
- FlipCard(state = state, eventDispatcher = eventDispatcher)
+ FlipCard(state)
}
}
- is MissionType.Click -> {
+ MissionType.TAP -> {
MissionClickCard(state, eventDispatcher)
}
+
+ MissionType.NONE -> {
+ Log.e("MissionContent", "Invalid or NONE MissionType: ${state.missionType}")
+ }
}
}
}
-/**
- * '๋๊ฐ๊ธฐ' ๋ฒํผ์ด ํฌํจ๋ ๋ฏธ์
์๋จ ์ฑ๋ฐ ์์ญ.
- */
@Composable
-fun MissionTopAppBar(onExit: () -> Unit) {
+fun MissionTopAppBar(
+ mode: MissionMode,
+ onExit: () -> Unit,
+) {
Spacer(modifier = Modifier.heightForScreenPercentage(0.066f))
Box(
modifier = Modifier
@@ -176,43 +222,41 @@ fun MissionTopAppBar(onExit: () -> Unit) {
contentAlignment = Alignment.TopEnd,
) {
Row(
- modifier = Modifier.customClickable(
- rippleEnabled = false,
- fadeOnPress = true,
- pressedAlpha = 0.5f,
- onClick = onExit,
- ),
+ modifier = Modifier
+ .height(26.dp)
+ .customClickable(
+ rippleEnabled = false,
+ fadeOnPress = true,
+ pressedAlpha = 0.5f,
+ onClick = onExit,
+ ),
) {
- Icon(
- painter = painterResource(id = core.designsystem.R.drawable.ic_cancel),
- contentDescription = null,
- tint = OrbitTheme.colors.white,
- modifier = Modifier.size(24.dp),
- )
- Text(
- text = "๋๊ฐ๊ธฐ",
- color = OrbitTheme.colors.white,
- style = OrbitTheme.typography.body1SemiBold,
- modifier = Modifier
- .padding(start = 4.dp)
- .align(Alignment.CenterVertically),
- )
+ if (mode == MissionMode.REAL) {
+ Icon(
+ painter = painterResource(id = core.designsystem.R.drawable.ic_cancel),
+ contentDescription = null,
+ tint = OrbitTheme.colors.white,
+ modifier = Modifier.size(24.dp),
+ )
+ Text(
+ text = stringResource(id = R.string.exit),
+ color = OrbitTheme.colors.white,
+ style = OrbitTheme.typography.body1SemiBold,
+ modifier = Modifier
+ .padding(start = 4.dp)
+ .align(Alignment.CenterVertically),
+ )
+ }
}
}
}
-/**
- * ๋ฏธ์
์งํ๋ ProgressBar ์น์
.
- */
@Composable
fun MissionProgressBarSection(state: MissionContract.State) {
Spacer(modifier = Modifier.heightForScreenPercentage(0.0246f))
MissionProgressBar(
- currentProgress = when (state.missionType) {
- is MissionType.Shake -> state.shakeCount
- is MissionType.Click -> state.clickCount
- },
- totalProgress = 10,
+ currentProgress = state.currentCount,
+ totalProgress = state.missionCount,
modifier = Modifier
.fillMaxWidth()
.height(5.dp)
@@ -221,14 +265,15 @@ fun MissionProgressBarSection(state: MissionContract.State) {
Spacer(modifier = Modifier.heightForScreenPercentage(0.06f))
}
-/**
- * ๋ฏธ์
์๋ด ๋ฌธ๊ตฌ ๋ฐ ํ์ฌ ์นด์ดํธ.
- */
@Composable
fun MissionLabel(state: MissionContract.State) {
- val instruction =
- if (state.missionType is MissionType.Shake) "10ํ๋ฅผ ํ๋ค์ด ๋ถ์ ์ ๋ค์ง์ด์ค" else "10ํ๋ฅผ ๋๋ฌ ํธ์ง๋ฅผ ์ด์ด์ค"
- val count = if (state.missionType is MissionType.Shake) state.shakeCount else state.clickCount
+ val instruction = stringResource(
+ id = when (state.missionType) {
+ MissionType.SHAKE -> R.string.mission_instruction_shake
+ else -> R.string.mission_instruction_tap
+ },
+ state.missionCount,
+ )
Text(
text = instruction,
@@ -237,15 +282,12 @@ fun MissionLabel(state: MissionContract.State) {
)
Spacer(modifier = Modifier.heightForScreenPercentage(0.005f))
Text(
- text = count.toString(),
+ text = state.currentCount.toString(),
color = OrbitTheme.colors.white,
style = OrbitTheme.typography.displaySemiBold,
)
}
-/**
- * ํ๋ค๊ธฐ ๋ฏธ์
์ด๊ธฐ ์ด๋ฏธ์ง.
- */
@Composable
fun MissionShakeInitialImage() {
Image(
@@ -257,15 +299,12 @@ fun MissionShakeInitialImage() {
)
}
-/**
- * ํด๋ฆญ ๋ฏธ์
์นด๋. ํด๋ฆญ ์ ์ ๋๋ฉ์ด์
๋ฐ ์ํ ๋ณํ.
- */
@Composable
fun MissionClickCard(
state: MissionContract.State,
eventDispatcher: (MissionContract.Action) -> Unit,
) {
- if (state.clickCount == 0) {
+ if (state.currentCount == 0) {
Image(
painter = painterResource(id = core.designsystem.R.drawable.ic_mission_main_letter),
contentDescription = null,
@@ -295,9 +334,6 @@ fun MissionClickCard(
}
}
-/**
- * ๋ฏธ์
์ข
๋ฃ ์ ๋๊ฐ๊ธฐ ๋ค์ด์ผ๋ก๊ทธ.
- */
@Composable
fun ExitDialog(
state: MissionContract.State,
@@ -306,18 +342,19 @@ fun ExitDialog(
analytics: AnalyticsHelper,
) {
OrbitDialog(
- title = "๋๊ฐ๋ฉด ์ด์ธ๋ฅผ ๋ฐ์ ์ ์์ด์",
- message = "๋ฏธ์
์ ์ํํ์ง ์๊ณ ๋๊ฐ์๊ฒ ์ด์?",
- confirmText = "๋๊ฐ๊ธฐ",
- cancelText = "์ทจ์",
+ title = stringResource(id = R.string.mission_exit_dialog_title),
+ message = stringResource(id = R.string.mission_exit_dialog_message),
+ confirmText = stringResource(id = R.string.exit),
+ cancelText = stringResource(id = R.string.cancel),
onConfirm = {
analytics.logEvent(
AnalyticsEvent(
type = "mission_fail",
properties = mapOf(
AnalyticsEvent.MissionPropertiesKeys.MISSION_TYPE to when (state.missionType) {
- is MissionType.Shake -> "shake"
- is MissionType.Click -> "click"
+ MissionType.SHAKE -> "shake"
+ MissionType.TAP -> "click"
+ else -> ""
},
),
),
@@ -328,9 +365,6 @@ fun ExitDialog(
)
}
-/**
- * ๋ฏธ์
์ฑ๊ณต ์ ์ค๋ฒ๋ ์ด ํ๋ฉด.
- */
@Composable
fun MissionSuccessOverlay() {
Box(
@@ -354,7 +388,7 @@ fun MissionSuccessOverlay() {
play = true,
)
Text(
- text = "๋ฏธ์
์ฑ๊ณต!",
+ text = stringResource(id = R.string.mission_success),
color = OrbitTheme.colors.white,
style = OrbitTheme.typography.title1Bold,
modifier = Modifier
@@ -365,22 +399,6 @@ fun MissionSuccessOverlay() {
}
}
-/**
- * ์ค๋ฅ ๋ฐ์ ์ ๋ค์ด์ผ๋ก๊ทธ.
- */
-@Composable
-fun ErrorDialog(message: String, onConfirm: () -> Unit) {
- OrbitDialog(
- title = "์ค๋ฅ",
- message = message,
- confirmText = "ํ์ธ",
- onConfirm = onConfirm,
- )
-}
-
-/**
- * ๋ก๋ฉ ํ๋ฉด. ๋ฏธ์
ํ์
๋ก๋ฉ ์ค์ ํ์.
- */
@Composable
fun MissionLoadingScreen() {
Box(
@@ -394,16 +412,36 @@ fun MissionLoadingScreen() {
}
}
+@Composable
+@Preview
+private fun MissionRouteReal() {
+ MissionScreen(
+ stateProvider = {
+ MissionContract.State(
+ isMissionTypeLoading = false,
+ missionType = MissionType.TAP,
+ currentCount = 0,
+ showFinalAnimation = false,
+ playWhenClick = false,
+ showExitDialog = false,
+ isMissionCompleted = false,
+ )
+ },
+ eventDispatcher = {},
+ onFinish = {},
+ )
+}
+
@Composable
@Preview
private fun MissionRoutePreview() {
MissionScreen(
stateProvider = {
MissionContract.State(
+ missionMode = MissionMode.PREVIEW,
isMissionTypeLoading = false,
- missionType = MissionType.Shake,
- shakeCount = 0,
- clickCount = 0,
+ missionType = MissionType.TAP,
+ currentCount = 0,
showFinalAnimation = false,
playWhenClick = false,
showExitDialog = false,
diff --git a/feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt b/feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt
index 38512436..b5e1c45d 100644
--- a/feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt
+++ b/feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt
@@ -1,25 +1,26 @@
package com.yapp.mission
import android.app.Application
-import android.util.Log
import androidx.lifecycle.SavedStateHandle
-import androidx.lifecycle.viewModelScope
+import androidx.lifecycle.ViewModel
import com.yapp.alarm.pendingIntent.interaction.createAlarmDismissIntent
import com.yapp.analytics.AnalyticsEvent
import com.yapp.analytics.AnalyticsHelper
-import com.yapp.datastore.UserPreferences
+import com.yapp.domain.MissionMode
+import com.yapp.domain.model.FortuneCreateStatus
import com.yapp.domain.model.MissionType
import com.yapp.domain.repository.FortuneRepository
-import com.yapp.domain.usecase.GetMissionTypeUseCase
import com.yapp.media.haptic.HapticFeedbackManager
import com.yapp.media.haptic.HapticType
-import com.yapp.ui.base.BaseViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.firstOrNull
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
+import kotlinx.coroutines.flow.first
+import org.orbitmvi.orbit.Container
+import org.orbitmvi.orbit.ContainerHost
+import org.orbitmvi.orbit.syntax.simple.intent
+import org.orbitmvi.orbit.syntax.simple.postSideEffect
+import org.orbitmvi.orbit.syntax.simple.reduce
+import org.orbitmvi.orbit.viewmodel.container
import javax.inject.Inject
@HiltViewModel
@@ -27,145 +28,132 @@ class MissionViewModel @Inject constructor(
private val analyticsHelper: AnalyticsHelper,
private val hapticFeedbackManager: HapticFeedbackManager,
private val fortuneRepository: FortuneRepository,
- private val userPreferences: UserPreferences,
- private val getMissionTypeUseCase: GetMissionTypeUseCase,
private val app: Application,
- savedStateHandle: SavedStateHandle,
-) : BaseViewModel(
- MissionContract.State(),
-) {
- init {
- savedStateHandle.get("notificationId")?.toLong()?.let {
- sendAlarmDismissIntent(it)
- }
- loadRemoteMissionType()
- }
-
- private fun loadRemoteMissionType() {
- viewModelScope.launch {
- val missionType = getMissionTypeUseCase.execute()
- updateState {
- copy(
- missionType = missionType,
- isMissionTypeLoading = false,
- )
- }
- }
+ private val savedStateHandle: SavedStateHandle,
+) : ViewModel(), ContainerHost {
+
+ override val container: Container = container(
+ initialState = MissionContract.State(),
+ ) {
+ sendAlarmDismissIntent()
+ loadMissionInfo(
+ missionTypeRaw = savedStateHandle.get("missionType"),
+ missionCountRaw = savedStateHandle.get("missionCount"),
+ missionModeRaw = savedStateHandle.get("missionMode"),
+ )
}
fun processAction(action: MissionContract.Action) {
when (action) {
- is MissionContract.Action.ShakeCard -> handleShake()
- is MissionContract.Action.ClickCard -> handleClick()
+ is MissionContract.Action.NavigateBack -> navigateBack()
+ is MissionContract.Action.ShakeCard -> handleMissionProgress(MissionType.SHAKE)
+ is MissionContract.Action.ClickCard -> handleMissionProgress(MissionType.TAP)
is MissionContract.Action.ShowExitDialog -> showExitDialog()
is MissionContract.Action.HideExitDialog -> hideExitDialog()
- is MissionContract.Action.RetryPostFortune -> retryPostFortune()
}
}
- private fun showExitDialog() {
- updateState { copy(showExitDialog = true) }
- }
+ private fun sendAlarmDismissIntent() {
+ val notificationId = savedStateHandle.get("notificationId")?.toLongOrNull() ?: return
+ val missionType = savedStateHandle.get("missionType")?.toIntOrNull() ?: -1
+ val missionCount = savedStateHandle.get("missionCount")?.toIntOrNull() ?: -1
- private fun hideExitDialog() {
- updateState { copy(showExitDialog = false) }
+ val alarmDismissIntent = createAlarmDismissIntent(
+ context = app,
+ notificationId = notificationId,
+ missionType = missionType,
+ missionCount = missionCount,
+ )
+ app.sendBroadcast(alarmDismissIntent)
}
- private fun handleShake() = viewModelScope.launch {
- if (currentState.missionType !is MissionType.Shake) return@launch
-
- val currentCount = currentState.shakeCount
- if (currentCount < 9) {
- performHapticSuccess()
- updateState { copy(shakeCount = currentCount + 1) }
- } else if (!currentState.isFlipped) {
- completeMission(type = "shake")
- updateState {
- copy(
- isMissionCompleted = true,
- shakeCount = 10,
- isFlipped = true,
- )
- }
- delay(500)
+ private fun loadMissionInfo(
+ missionTypeRaw: String?,
+ missionCountRaw: String?,
+ missionModeRaw: String?,
+ ) = intent {
+ val missionType = missionTypeRaw?.toIntOrNull() ?: MissionType.TAP.value
+ val missionCount = missionCountRaw?.toIntOrNull() ?: 10
+ val missionMode = MissionMode.fromRaw(missionModeRaw)
+
+ reduce {
+ state.copy(
+ missionMode = missionMode,
+ missionType = MissionType.fromInt(missionType),
+ missionCount = missionCount,
+ isMissionTypeLoading = false,
+ )
}
}
- private fun handleClick() = viewModelScope.launch {
- if (currentState.missionType !is MissionType.Click) return@launch
+ private fun navigateBack() = intent {
+ postSideEffect(MissionContract.SideEffect.NavigateBack)
+ }
- val currentCount = currentState.clickCount
- if (currentCount < 9) {
- performHapticSuccess()
- logMissionSuccess("click")
- updateState { copy(clickCount = currentCount + 1, playWhenClick = true) }
- delay(500)
- updateState { copy(playWhenClick = false) }
- } else {
- updateState {
- copy(
- clickCount = 10,
- showFinalAnimation = true,
- )
- }
- postFortune()
- delay(500)
- updateState { copy(isMissionCompleted = true) }
- }
+ private fun showExitDialog() = intent {
+ reduce { state.copy(showExitDialog = true) }
}
- private fun postFortune() {
- viewModelScope.launch {
- val userId = userPreferences.userIdFlow.firstOrNull() ?: return@launch
- val result = runCatching {
- withContext(Dispatchers.IO) {
- fortuneRepository.postFortune(userId)
- }
- }
+ private fun hideExitDialog() = intent {
+ reduce { state.copy(showExitDialog = false) }
+ }
- result.onSuccess {
- val data = it.getOrThrow()
- userPreferences.saveFortuneId(data.id)
- userPreferences.saveFortuneScore(data.avgFortuneScore)
+ private fun handleMissionProgress(missionType: MissionType) = intent {
+ val isLast = state.currentCount >= state.missionCount - 1
+ val nextCount = state.currentCount + 1
- emitSideEffect(MissionContract.SideEffect.NavigateToFortune)
- }.onFailure { error ->
- Log.e("MissionViewModel", "์ด์ธ ๋ฐ์ดํฐ ์์ฒญ ์คํจ: ${error.message}")
- updateState { copy(errorMessage = error.message) }
- }
- }
- }
+ performHapticSuccess()
- private fun retryPostFortune() {
- viewModelScope.launch {
- val userId = userPreferences.userIdFlow.firstOrNull() ?: return@launch
- val result = runCatching {
- withContext(Dispatchers.IO) {
- fortuneRepository.postFortune(userId)
- }
+ if (isLast) {
+ completeMission(type = missionType.name.lowercase())
+ reduce {
+ state.copy(
+ isMissionCompleted = true,
+ currentCount = state.missionCount,
+ showFinalAnimation = true,
+ )
+ }
+ delay(500)
+ } else {
+ val transientState = if (missionType == MissionType.TAP) {
+ state.copy(currentCount = nextCount, playWhenClick = true)
+ } else {
+ state.copy(currentCount = nextCount)
}
- result.onSuccess {
- val data = it.getOrThrow()
- userPreferences.saveFortuneId(data.id)
- userPreferences.saveFortuneScore(data.avgFortuneScore)
+ reduce { transientState }
- emitSideEffect(MissionContract.SideEffect.NavigateToFortune)
- }.onFailure {
- Log.e("MissionViewModel", "์ด์ธ ์ฌ์์ฒญ ์คํจ: ${it.message}")
- navigateToHome()
+ if (missionType == MissionType.TAP) {
+ delay(500)
+ reduce { state.copy(playWhenClick = false) }
}
}
}
- private fun completeMission(type: String) {
+ private fun completeMission(type: String) = intent {
performHapticSuccess()
logMissionSuccess(type)
- postFortune()
- }
- private fun performHapticSuccess() {
- hapticFeedbackManager.performHapticFeedback(HapticType.SUCCESS)
+ if (state.missionMode != MissionMode.REAL) {
+ postSideEffect(MissionContract.SideEffect.NavigateBack)
+ return@intent
+ }
+
+ val fortuneCreateStatus = fortuneRepository.fortuneCreateStatusFlow.first()
+ val hasUnseenFortune = fortuneRepository.hasUnseenFortuneFlow.first()
+
+ val shouldOpenFortune = (
+ fortuneCreateStatus is FortuneCreateStatus.Creating ||
+ fortuneCreateStatus is FortuneCreateStatus.Success && hasUnseenFortune
+ )
+
+ postSideEffect(
+ if (shouldOpenFortune) {
+ MissionContract.SideEffect.NavigateToFortune
+ } else {
+ MissionContract.SideEffect.NavigateBack
+ },
+ )
}
private fun logMissionSuccess(type: String) {
@@ -179,15 +167,7 @@ class MissionViewModel @Inject constructor(
)
}
- private fun navigateToHome() {
- emitSideEffect(MissionContract.SideEffect.NavigateToFortune)
- }
-
- private fun sendAlarmDismissIntent(id: Long) {
- val alarmDismissIntent = createAlarmDismissIntent(
- context = app,
- notificationId = id,
- )
- app.sendBroadcast(alarmDismissIntent)
+ private fun performHapticSuccess() {
+ hapticFeedbackManager.performHapticFeedback(HapticType.SUCCESS)
}
}
diff --git a/feature/mission/src/main/java/com/yapp/mission/component/FlipCard.kt b/feature/mission/src/main/java/com/yapp/mission/component/FlipCard.kt
index 630ba25a..14e41a39 100644
--- a/feature/mission/src/main/java/com/yapp/mission/component/FlipCard.kt
+++ b/feature/mission/src/main/java/com/yapp/mission/component/FlipCard.kt
@@ -25,7 +25,6 @@ import com.yapp.mission.MissionContract
@Composable
fun FlipCard(
state: MissionContract.State,
- eventDispatcher: (MissionContract.Action) -> Unit,
) {
val rotationZ = remember { Animatable(0f) }
val rotationY = remember { Animatable(state.rotationY) }
@@ -49,8 +48,8 @@ fun FlipCard(
}
}
- LaunchedEffect(state.shakeCount) {
- if (state.shakeCount in 1..9) {
+ LaunchedEffect(state.currentCount) {
+ if (state.currentCount in 1..state.missionCount - 1) {
rotationZ.animateTo(
targetValue = -20f,
animationSpec = tween(durationMillis = 66, easing = LinearEasing),
@@ -109,7 +108,6 @@ fun FlipCardPreview() {
) {
FlipCard(
state = state.copy(rotationY = rotationY, rotationZ = rotationZ),
- eventDispatcher = {},
)
}
}
diff --git a/feature/mission/src/main/res/values/strings.xml b/feature/mission/src/main/res/values/strings.xml
new file mode 100644
index 00000000..3fd93606
--- /dev/null
+++ b/feature/mission/src/main/res/values/strings.xml
@@ -0,0 +1,14 @@
+
+
+ ๋๊ฐ๊ธฐ
+ ์ทจ์
+ ํ์ธ
+ ์ค๋ฅ
+
+ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ข
๋ฃ
+ ๋๊ฐ๋ฉด ์ด์ธ๋ฅผ ๋ฐ์ ์ ์์ด์
+ ๋ฏธ์
์ ์ํํ์ง ์๊ณ ๋๊ฐ์๊ฒ ์ด์?
+ ๋ฏธ์
์ฑ๊ณต!
+ %1$dํ๋ฅผ ํ๋ค์ด ๋ถ์ ์ ๋ค์ง์ด์ค
+ %1$dํ๋ฅผ ๋๋ฌ ํธ์ง๋ฅผ ์ด์ด์ค
+
diff --git a/feature/navigator/.gitignore b/feature/navigator/.gitignore
deleted file mode 100644
index 42afabfd..00000000
--- a/feature/navigator/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
\ No newline at end of file
diff --git a/feature/navigator/build.gradle.kts b/feature/navigator/build.gradle.kts
deleted file mode 100644
index a8db6273..00000000
--- a/feature/navigator/build.gradle.kts
+++ /dev/null
@@ -1,26 +0,0 @@
-import com.yapp.convention.setNamespace
-
-plugins {
- id("orbit.android.feature")
-}
-
-android {
- setNamespace("feature.navigator")
-}
-
-dependencies {
- implementation(projects.core.common)
- implementation(projects.core.analytics)
- implementation(libs.orbit.core)
- implementation(libs.orbit.compose)
- implementation(libs.orbit.viewmodel)
- implementation(libs.kotlin.reflect)
- implementation(projects.feature.home)
- implementation(projects.feature.alarmInteraction)
- implementation(projects.feature.onboarding)
- implementation(projects.feature.mission)
- implementation(projects.feature.fortune)
- implementation(projects.feature.setting)
- implementation(projects.feature.splash)
- implementation(projects.feature.webview)
-}
diff --git a/feature/navigator/consumer-rules.pro b/feature/navigator/consumer-rules.pro
deleted file mode 100644
index e69de29b..00000000
diff --git a/feature/navigator/proguard-rules.pro b/feature/navigator/proguard-rules.pro
deleted file mode 100644
index 481bb434..00000000
--- a/feature/navigator/proguard-rules.pro
+++ /dev/null
@@ -1,21 +0,0 @@
-# Add project specific ProGuard rules here.
-# You can control the set of applied configuration files using the
-# proguardFiles setting in build.gradle.
-#
-# For more details, see
-# http://developer.android.com/guide/developing/tools/proguard.html
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-# public *;
-#}
-
-# Uncomment this to preserve the line number information for
-# debugging stack traces.
-#-keepattributes SourceFile,LineNumberTable
-
-# If you keep the line number information, uncomment this to
-# hide the original source file name.
-#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/feature/onboarding/build.gradle.kts b/feature/onboarding/build.gradle.kts
index 6c1d22e0..e573da87 100644
--- a/feature/onboarding/build.gradle.kts
+++ b/feature/onboarding/build.gradle.kts
@@ -14,10 +14,10 @@ dependencies {
implementation(projects.core.analytics)
implementation(projects.core.media)
implementation(projects.domain)
- implementation(projects.core.datastore)
implementation(libs.orbit.core)
implementation(libs.orbit.compose)
implementation(libs.orbit.viewmodel)
+ implementation(libs.compose.material)
implementation(libs.coil.compose)
implementation(libs.coil.gif)
implementation(libs.accompanist.permission)
diff --git a/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingAccessScreen.kt b/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingAccessScreen.kt
index abc83d5c..1485ed59 100644
--- a/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingAccessScreen.kt
+++ b/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingAccessScreen.kt
@@ -21,9 +21,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.imePadding
-import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
@@ -48,7 +46,6 @@ import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionStatus
-import com.google.accompanist.permissions.shouldShowRationale
import com.yapp.analytics.AnalyticsEvent
import com.yapp.analytics.LocalAnalyticsHelper
import com.yapp.designsystem.theme.OrbitTheme
@@ -238,8 +235,6 @@ fun OnboardingAccessScreen(
modifier = Modifier
.fillMaxSize()
.background(OrbitTheme.colors.gray_900)
- .statusBarsPadding()
- .navigationBarsPadding()
.imePadding(),
) {
if (!hasRequestedPermission) {
diff --git a/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingAlarmTimeSelectionScreen.kt b/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingAlarmTimeSelectionScreen.kt
index 963b03ba..8ccd007f 100644
--- a/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingAlarmTimeSelectionScreen.kt
+++ b/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingAlarmTimeSelectionScreen.kt
@@ -20,6 +20,7 @@ import com.yapp.designsystem.theme.OrbitTheme
import com.yapp.ui.component.timepicker.OrbitPicker
import com.yapp.ui.utils.heightForScreenPercentage
import feature.onboarding.R
+import java.time.LocalTime
@Composable
fun OnboardingAlarmTimeSelectionRoute(
@@ -57,8 +58,8 @@ fun OnboardingAlarmTimeSelectionRoute(
)
},
onBackClick = { viewModel.processAction(OnboardingContract.Action.PreviousStep) },
- setAlarmTime = { isAm, hour, minute ->
- viewModel.processAction(OnboardingContract.Action.SetAlarmTime(isAm, hour, minute))
+ setAlarmTime = { newTime ->
+ viewModel.processAction(OnboardingContract.Action.SetAlarmTime(newTime))
},
)
}
@@ -69,7 +70,7 @@ fun OnboardingAlarmTimeSelectionScreen(
totalSteps: Int,
onNextClick: () -> Unit,
onBackClick: () -> Unit,
- setAlarmTime: (String, Int, Int) -> Unit,
+ setAlarmTime: (LocalTime) -> Unit,
) {
OnboardingScreen(
currentStep = currentStep,
@@ -100,8 +101,8 @@ fun OnboardingAlarmTimeSelectionScreen(
OrbitPicker(
modifier = Modifier.padding(top = 90.dp),
- ) { amPm, hour, minute ->
- setAlarmTime(amPm, hour, minute)
+ ) { newTime ->
+ setAlarmTime(newTime)
}
}
}
@@ -116,7 +117,7 @@ fun OnboardingAlarmTimeSelectionScreenPreview() {
totalSteps = 0,
onNextClick = {},
onBackClick = {},
- setAlarmTime = { _, _, _ -> },
+ setAlarmTime = { _ -> },
)
}
}
diff --git a/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingBirthdayScreen.kt b/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingBirthdayScreen.kt
index 39868a89..ee9180dc 100644
--- a/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingBirthdayScreen.kt
+++ b/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingBirthdayScreen.kt
@@ -10,9 +10,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.imePadding
-import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -104,8 +102,6 @@ fun OnboardingBirthdayScreen(
modifier = Modifier
.fillMaxSize()
.background(OrbitTheme.colors.gray_900)
- .statusBarsPadding()
- .navigationBarsPadding()
.imePadding(),
) {
OnBoardingTopAppBar(
diff --git a/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingCompleteScreen2.kt b/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingCompleteScreen2.kt
index f99e8771..f0e752e1 100644
--- a/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingCompleteScreen2.kt
+++ b/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingCompleteScreen2.kt
@@ -9,10 +9,8 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.imePadding
-import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@@ -57,8 +55,6 @@ fun OnboardingCompleteScreen2(
modifier = Modifier
.fillMaxSize()
.background(OrbitTheme.colors.gray_900)
- .statusBarsPadding()
- .navigationBarsPadding()
.imePadding(),
) {
OnBoardingTopAppBar(
diff --git a/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingContract.kt b/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingContract.kt
index 5e8b83c2..95dc24db 100644
--- a/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingContract.kt
+++ b/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingContract.kt
@@ -1,12 +1,13 @@
package com.yapp.onboarding
import com.yapp.ui.base.UiState
+import java.time.LocalTime
sealed class OnboardingContract {
data class State(
val currentStep: Int = 1,
- val timeState: AlarmTimeState = AlarmTimeState(),
+ val selectedTime: LocalTime = LocalTime.of(1, 0),
val textFieldValue: String = "",
val showWarning: Boolean = false,
val isButtonEnabled: Boolean = false,
@@ -18,7 +19,6 @@ sealed class OnboardingContract {
val isBirthDateValid: Boolean = false,
val isBirthTimeValid: Boolean = false,
val isValid: Boolean = false,
- val isBottomSheetOpen: Boolean = false,
val isShowWarningDialog: Boolean = false,
) : UiState {
val birthDateFormatted: String
@@ -43,23 +43,18 @@ sealed class OnboardingContract {
}
}
- data class AlarmTimeState(
- val selectedAmPm: String = "์ค์ ",
- val selectedHour: Int = 1,
- val selectedMinute: Int = 0,
- )
-
sealed class Action {
data object NextStep : Action()
data object PreviousStep : Action()
- data class SetAlarmTime(val isAm: String, val hour: Int, val minute: Int) : Action()
+ data class SetAlarmTime(val newTime: LocalTime) : Action()
data object CreateAlarm : Action()
data class UpdateField(val value: String, val fieldType: FieldType) : Action()
data object Reset : Action()
data object Submit : Action()
data class UpdateGender(val gender: String) : Action()
data class UpdateBirthDate(val lunar: String, val year: Int, val month: Int, val day: Int) : Action()
- data object ToggleBottomSheet : Action()
+ data object ShowBottomSheet : Action()
+ data object HideBottomSheet : Action()
data object CompleteOnboarding : Action()
data class OpenWebView(val url: String) : Action()
data object ShowWarningDialog : Action()
@@ -76,6 +71,10 @@ sealed class OnboardingContract {
data object NavigateBack : SideEffect()
+ data object ShowBottomSheet : SideEffect()
+
+ data object HideBottomSheet : SideEffect()
+
data object OnboardingCompleted : SideEffect()
data class OpenWebView(val url: String) : SideEffect()
diff --git a/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingGenderScreen.kt b/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingGenderScreen.kt
index c9ba857d..01f0df00 100644
--- a/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingGenderScreen.kt
+++ b/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingGenderScreen.kt
@@ -1,5 +1,6 @@
package com.yapp.onboarding
+import android.net.Uri
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -21,18 +22,27 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import androidx.navigation.navOptions
import com.yapp.analytics.AnalyticsEvent
import com.yapp.analytics.LocalAnalyticsHelper
+import com.yapp.common.navigation.OrbitNavigator
+import com.yapp.common.navigation.route.OnboardingBaseRoute
import com.yapp.designsystem.theme.OrbitTheme
import com.yapp.onboarding.component.UserInfoBottomSheet
+import com.yapp.ui.component.bottomsheet.OrbitBottomSheetLayout
+import com.yapp.ui.component.bottomsheet.OrbitBottomSheetState
+import com.yapp.ui.component.bottomsheet.rememberOrbitBottomSheetState
import com.yapp.ui.component.dialog.OrbitDialog
import com.yapp.ui.toggle.OrbitGenderToggle
import com.yapp.ui.utils.heightForScreenPercentage
import com.yapp.ui.utils.paddingForScreenPercentage
import feature.onboarding.R
+import org.orbitmvi.orbit.compose.collectSideEffect
@Composable
fun OnboardingGenderRoute(
+ navigator: OrbitNavigator,
+ bottomSheetState: OrbitBottomSheetState,
viewModel: OnboardingViewModel,
) {
val state by viewModel.container.stateFlow.collectAsStateWithLifecycle()
@@ -50,90 +60,152 @@ fun OnboardingGenderRoute(
)
}
- BackHandler {
- viewModel.processAction(OnboardingContract.Action.PreviousStep)
+ viewModel.collectSideEffect { sideEffect ->
+ handleSideEffect(
+ sideEffect = sideEffect,
+ navigator = navigator,
+ bottomSheetState = bottomSheetState,
+ state = state,
+ processAction = viewModel::processAction,
+ )
}
OnboardingGenderScreen(
state = state,
+ bottomSheetState = bottomSheetState,
currentStep = 5,
totalSteps = 6,
- onNextClick = { viewModel.processAction(OnboardingContract.Action.ToggleBottomSheet) },
- onBackClick = { viewModel.processAction(OnboardingContract.Action.PreviousStep) },
- onGenderSelect = { gender ->
- analyticsHelper.logEvent(
- AnalyticsEvent(
- type = "onboarding_gender_select",
- properties = mapOf(
- AnalyticsEvent.OnboardingPropertiesKeys.GENDER to gender,
- ),
- ),
- )
- viewModel.processAction(OnboardingContract.Action.UpdateGender(gender))
- },
- onDismissRequest = {
- viewModel.processAction(OnboardingContract.Action.ToggleBottomSheet)
- },
- onConfirmRequest = {
- viewModel.processAction(OnboardingContract.Action.ToggleBottomSheet)
- viewModel.processAction(OnboardingContract.Action.Submit)
- },
+ processAction = viewModel::processAction,
)
}
+private suspend fun handleSideEffect(
+ sideEffect: OnboardingContract.SideEffect,
+ navigator: OrbitNavigator,
+ bottomSheetState: OrbitBottomSheetState,
+ state: OnboardingContract.State,
+ processAction: (OnboardingContract.Action) -> Unit,
+) {
+ when (sideEffect) {
+ is OnboardingContract.SideEffect.NavigateToNextStep -> {
+ navigator.navigateToOnboardingNextStep(sideEffect.currentStep)
+ }
+
+ OnboardingContract.SideEffect.NavigateBack -> {
+ processAction(OnboardingContract.Action.Reset)
+ navigator.navigateBack()
+ }
+
+ is OnboardingContract.SideEffect.ShowBottomSheet -> {
+ bottomSheetState.show {
+ UserInfoBottomSheet(
+ name = state.userName,
+ gender = state.selectedGender ?: "๋ฌด์ง๊ฐ",
+ birthDate = state.birthDateFormatted,
+ birthTime = state.birthTimeFormatted,
+ onDismiss = {
+ processAction(OnboardingContract.Action.HideBottomSheet)
+ },
+ onConfirm = {
+ processAction(OnboardingContract.Action.HideBottomSheet)
+ processAction(OnboardingContract.Action.Submit)
+ },
+ )
+ }
+ }
+
+ is OnboardingContract.SideEffect.HideBottomSheet -> {
+ bottomSheetState.hide()
+ }
+
+ OnboardingContract.SideEffect.OnboardingCompleted -> {
+ navigator.navigateToHome(
+ navOptions = navOptions {
+ popUpTo {
+ inclusive = true
+ }
+ },
+ )
+ }
+
+ is OnboardingContract.SideEffect.OpenWebView -> {
+ navigator.navigateToWebView(Uri.encode(sideEffect.url))
+ }
+ }
+}
+
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun OnboardingGenderScreen(
state: OnboardingContract.State,
+ bottomSheetState: OrbitBottomSheetState,
currentStep: Int,
totalSteps: Int,
- onNextClick: () -> Unit,
- onBackClick: () -> Unit,
- onGenderSelect: (String) -> Unit,
- onDismissRequest: () -> Unit,
- onConfirmRequest: () -> Unit,
+ processAction: (OnboardingContract.Action) -> Unit,
+ logEvent: (AnalyticsEvent) -> Unit = { },
) {
- OnboardingScreen(
- currentStep = currentStep,
- totalSteps = totalSteps,
- isButtonEnabled = state.selectedGender != null,
- onNextClick = onNextClick,
- onBackClick = onBackClick,
- buttonLabel = "๋ค์",
- ) {
- Column(
- modifier = Modifier.fillMaxSize(),
- horizontalAlignment = Alignment.CenterHorizontally,
- ) {
- Spacer(modifier = Modifier.heightForScreenPercentage(0.05f))
- Text(
- text = stringResource(id = R.string.onboarding_step6_text_title),
- style = OrbitTheme.typography.heading1SemiBold,
- color = OrbitTheme.colors.white,
- modifier = Modifier.fillMaxWidth(),
- textAlign = TextAlign.Center,
- )
+ BackHandler {
+ if (state.isShowWarningDialog) {
+ processAction(OnboardingContract.Action.HideWarningDialog)
+ } else if (bottomSheetState.state.isVisible) {
+ processAction(OnboardingContract.Action.HideBottomSheet)
+ } else {
+ processAction(OnboardingContract.Action.PreviousStep)
+ }
+ }
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .padding(horizontal = 38.dp)
- .paddingForScreenPercentage(topPercentage = 0.11f),
- horizontalArrangement = Arrangement.spacedBy(15.dp),
+ OrbitBottomSheetLayout(sheetState = bottomSheetState) {
+ OnboardingScreen(
+ currentStep = currentStep,
+ totalSteps = totalSteps,
+ isButtonEnabled = state.selectedGender != null,
+ onNextClick = {
+ processAction(OnboardingContract.Action.ShowBottomSheet)
+ },
+ onBackClick = {
+ processAction(OnboardingContract.Action.PreviousStep)
+ },
+ buttonLabel = "๋ค์",
+ ) {
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ horizontalAlignment = Alignment.CenterHorizontally,
) {
- Box(modifier = Modifier.weight(1f)) {
- OrbitGenderToggle(
- label = "๋จ์ฑ",
- isSelected = state.selectedGender == "๋จ์ฑ",
- onToggle = { onGenderSelect("๋จ์ฑ") },
- )
- }
- Box(modifier = Modifier.weight(1f)) {
- OrbitGenderToggle(
- label = "์ฌ์ฑ",
- isSelected = state.selectedGender == "์ฌ์ฑ",
- onToggle = { onGenderSelect("์ฌ์ฑ") },
- )
+ Spacer(modifier = Modifier.heightForScreenPercentage(0.05f))
+ Text(
+ text = stringResource(id = R.string.onboarding_step6_text_title),
+ style = OrbitTheme.typography.heading1SemiBold,
+ color = OrbitTheme.colors.white,
+ modifier = Modifier.fillMaxWidth(),
+ textAlign = TextAlign.Center,
+ )
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 38.dp)
+ .paddingForScreenPercentage(topPercentage = 0.11f),
+ horizontalArrangement = Arrangement.spacedBy(15.dp),
+ ) {
+ listOf("๋จ์ฑ", "์ฌ์ฑ").forEach { gender ->
+ Box(modifier = Modifier.weight(1f)) {
+ OrbitGenderToggle(
+ label = gender,
+ isSelected = state.selectedGender == gender,
+ onToggle = {
+ logEvent(
+ AnalyticsEvent(
+ type = "onboarding_gender_select",
+ properties = mapOf(
+ AnalyticsEvent.OnboardingPropertiesKeys.GENDER to gender,
+ ),
+ ),
+ )
+ processAction(OnboardingContract.Action.UpdateGender(gender))
+ },
+ )
+ }
+ }
}
}
}
@@ -144,21 +216,9 @@ fun OnboardingGenderScreen(
title = stringResource(id = R.string.onboarding_warning_dialog_title),
message = stringResource(id = R.string.onboarding_warning_dialog_message),
confirmText = stringResource(id = R.string.onboarding_warning_dialog_btn_confirm),
- onConfirm = {
- onConfirmRequest()
- },
+ onConfirm = { processAction(OnboardingContract.Action.HideWarningDialog) },
)
}
-
- UserInfoBottomSheet(
- isSheetOpen = state.isBottomSheetOpen,
- onDismissRequest = onDismissRequest,
- onConfirmRequest = onConfirmRequest,
- name = state.userName,
- gender = state.selectedGender ?: "๋ฌด์ง๊ฐ",
- birthDate = state.birthDateFormatted,
- birthTime = state.birthTimeFormatted,
- )
}
@Composable
@@ -168,12 +228,9 @@ fun OnboardingGenderScreenPreview() {
state = OnboardingContract.State(
isButtonEnabled = true,
),
+ bottomSheetState = rememberOrbitBottomSheetState(),
currentStep = 0,
totalSteps = 0,
- onNextClick = {},
- onBackClick = {},
- onGenderSelect = {},
- onDismissRequest = {},
- onConfirmRequest = {},
+ processAction = {},
)
}
diff --git a/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingNavGraph.kt b/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingNavGraph.kt
index ee57af2e..a642d861 100644
--- a/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingNavGraph.kt
+++ b/feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingNavGraph.kt
@@ -1,7 +1,6 @@
package com.yapp.onboarding
import android.net.Uri
-import androidx.compose.runtime.LaunchedEffect
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import androidx.navigation.navOptions
@@ -10,108 +9,106 @@ import com.yapp.common.navigation.OrbitNavigator
import com.yapp.common.navigation.extensions.sharedHiltViewModel
import com.yapp.common.navigation.route.OnboardingBaseRoute
import com.yapp.common.navigation.route.OnboardingDestination
-import kotlinx.coroutines.flow.collectLatest
+import com.yapp.ui.component.bottomsheet.OrbitBottomSheetState
+import org.orbitmvi.orbit.compose.collectSideEffect
fun NavGraphBuilder.onboardingNavGraph(
navigator: OrbitNavigator,
+ bottomSheetState: OrbitBottomSheetState,
) {
navigation(startDestination = OnboardingDestination.Explain) {
composable {
val viewModel = it.sharedHiltViewModel(navigator.navController)
- LaunchedEffect(viewModel) {
- viewModel.container.sideEffectFlow.collectLatest { sideEffect ->
- handleSideEffect(sideEffect, navigator, viewModel)
- }
+
+ viewModel.collectSideEffect { sideEffect ->
+ handleOnboardingCommonSideEffect(sideEffect, navigator, viewModel::processAction)
}
+
OnboardingExplainRoute(viewModel)
}
composable {
val viewModel = it.sharedHiltViewModel(navigator.navController)
- LaunchedEffect(viewModel) {
- viewModel.container.sideEffectFlow.collectLatest { sideEffect ->
- handleSideEffect(sideEffect, navigator, viewModel)
- }
+
+ viewModel.collectSideEffect { sideEffect ->
+ handleOnboardingCommonSideEffect(sideEffect, navigator, viewModel::processAction)
}
+
OnboardingAlarmTimeSelectionRoute(viewModel)
}
composable {
val viewModel = it.sharedHiltViewModel(navigator.navController)
- LaunchedEffect(viewModel) {
- viewModel.container.sideEffectFlow.collectLatest { sideEffect ->
- handleSideEffect(sideEffect, navigator, viewModel)
- }
+
+ viewModel.collectSideEffect { sideEffect ->
+ handleOnboardingCommonSideEffect(sideEffect, navigator, viewModel::processAction)
}
+
OnboardingBirthdayRoute(viewModel)
}
composable {
val viewModel = it.sharedHiltViewModel(navigator.navController)
- LaunchedEffect(viewModel) {
- viewModel.container.sideEffectFlow.collectLatest { sideEffect ->
- handleSideEffect(sideEffect, navigator, viewModel)
- }
+
+ viewModel.collectSideEffect { sideEffect ->
+ handleOnboardingCommonSideEffect(sideEffect, navigator, viewModel::processAction)
}
+
OnboardingTimeOfBirthRoute(viewModel)
}
composable {
val viewModel = it.sharedHiltViewModel(navigator.navController)
- LaunchedEffect(viewModel) {
- viewModel.container.sideEffectFlow.collectLatest { sideEffect ->
- handleSideEffect(sideEffect, navigator, viewModel)
- }
+
+ viewModel.collectSideEffect { sideEffect ->
+ handleOnboardingCommonSideEffect(sideEffect, navigator, viewModel::processAction)
}
+
OnboardingNameRoute(viewModel)
}
composable {
val viewModel = it.sharedHiltViewModel(navigator.navController)
- LaunchedEffect(viewModel) {
- viewModel.container.sideEffectFlow.collectLatest { sideEffect ->
- handleSideEffect(sideEffect, navigator, viewModel)
- }
- }
- OnboardingGenderRoute(viewModel)
+
+ OnboardingGenderRoute(navigator, bottomSheetState, viewModel)
}
composable