diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..4b1e2b8 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +root = true + +[*] +# 인코딩 방식 +charset = utf-8 +# 줄바꿈 타입 +end_of_line = lf +# 들여쓰기 타입 +indent_style = space +# true 경우, 문자 앞의 공백을 제거 +trim_trailing_whitespace = true +# ture 경우, 파일을 저장할 때 새 줄로 끝남 +insert_final_newline = true +# 최대 길이 +max_line_length = 120 +# indent_size +indent_size = 4 + +[*.{kt,kts}] +disabled_rules = import-ordering,comment-spacing \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index e69de29..b2a6328 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -0,0 +1,8 @@ +# What's Changed + + + + +### Memo + + 별도 메모 사항이 있을 경우 작성 diff --git a/README.md b/README.md index 33211e2..216b723 100644 --- a/README.md +++ b/README.md @@ -2,18 +2,30 @@ 안드로이드에서 공통적으로 사용될 모듈 저장소 +**Authors** : raine@lemoncloud.io ## Module -| module | description | -|----------------------------------|---------------------------| -| ui-architecture | mvi 기반 architecture 인터페이스 | -| android-component(Not implement) | android component 관련 유틸리티 | - +| module | description | +|------------------------------|---------------------------| +| lemon-core-ui:architecture | mvi 기반 architecture 인터페이스 | +| lemon-core-android:component | android component 유틸리티 | ### UI-Architecture -안드로이드 UI 구조를 효과적으로 빌딩하기 위한 아키텍처 라이브러리. MVI 기반의 아키텍처로 State Event Effect를 제어하여 사용자와 UI간의 상태 및 이벤트 흐름과 사이에 발생하는 이펙트를 효과적으로 처리할 수 있습니다. +안드로이드 UI 구조를 효과적으로 빌딩하기 위한 아키텍처 라이브러리. MVI 기반의 아키텍처로 State Event Effect를 제어하여 사용자와 UI간의 상태 및 이벤트 흐름과 사이에 발생하는 이펙트를 효과적으로 +처리할 수 있습니다. ### Android-Component + 안드로이드 컴포넌트 제어 라이브러리 안드로이드 컴포넌트 초기화, 설정 및 컴포넌트간의 통신과 같은 작업을 수행합니다. + +## 초기화 + +최초로 프로젝트를 다운받은 후 `./init_lint_settings.sh` 를 실행해주세요. commit changes 에 대한 lint를 자동적으로 수행합니다. + +## AAR 배포 + +Lemon Android Core Module에서 사용되는 라이브러리를 배포해야 할 상황이 존재할 경우 루트 디렉터리에 존재하는 `assemble_aar.sh` 스크립트 파일을 실행하면 됩니다. +이때 생성되는 AAR들은 난독화가 적용되어 있습니다. 배포되는 aar들의 난독화 여부와 flavor 구성들을 수정하고 싶을 경우, `build-system` 모듈의 `Config` 를 확인하세요. + diff --git a/assemble_aar.sh b/assemble_aar.sh new file mode 100755 index 0000000..fa24d8e --- /dev/null +++ b/assemble_aar.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +chmod +x "./gradlew" + +# ktlint 검사 +./gradlew ktlintCheck + +# AAR 배포 +./gradlew assembleRelease + +# 모든 모듈의 AAR 을 수집하여 ./build/outputs 에 배치 +./gradlew assembleAAR + diff --git a/buildSystem/.gitignore b/build-system/.gitignore similarity index 100% rename from buildSystem/.gitignore rename to build-system/.gitignore diff --git a/buildSystem/build.gradle.kts b/build-system/build.gradle.kts similarity index 100% rename from buildSystem/build.gradle.kts rename to build-system/build.gradle.kts diff --git a/buildSystem/settings.gradle.kts b/build-system/settings.gradle.kts similarity index 86% rename from buildSystem/settings.gradle.kts rename to build-system/settings.gradle.kts index 2994d40..1a7bc35 100644 --- a/buildSystem/settings.gradle.kts +++ b/build-system/settings.gradle.kts @@ -11,6 +11,6 @@ dependencyResolutionManagement { } -rootProject.name = "buildSystem" +rootProject.name = "build-system" diff --git a/buildSystem/src/main/kotlin/io/lemon/android/buildSystem/Config.kt b/build-system/src/main/kotlin/io/lemon/android/buildSystem/Config.kt similarity index 95% rename from buildSystem/src/main/kotlin/io/lemon/android/buildSystem/Config.kt rename to build-system/src/main/kotlin/io/lemon/android/buildSystem/Config.kt index 7040aeb..f0c83ba 100644 --- a/buildSystem/src/main/kotlin/io/lemon/android/buildSystem/Config.kt +++ b/build-system/src/main/kotlin/io/lemon/android/buildSystem/Config.kt @@ -1,7 +1,6 @@ package io.lemon.android.buildSystem import io.lemon.android.buildSystem.extensions.type.FlavorType -import io.lemon.android.buildSystem.extensions.type.ResourceType import org.gradle.api.JavaVersion import org.jetbrains.kotlin.gradle.dsl.JvmTarget import java.time.ZonedDateTime diff --git a/buildSystem/src/main/kotlin/io/lemon/android/buildSystem/extensions/Android.kt b/build-system/src/main/kotlin/io/lemon/android/buildSystem/extensions/Android.kt similarity index 100% rename from buildSystem/src/main/kotlin/io/lemon/android/buildSystem/extensions/Android.kt rename to build-system/src/main/kotlin/io/lemon/android/buildSystem/extensions/Android.kt diff --git a/buildSystem/src/main/kotlin/io/lemon/android/buildSystem/extensions/AndroidCompose.kt b/build-system/src/main/kotlin/io/lemon/android/buildSystem/extensions/AndroidCompose.kt similarity index 100% rename from buildSystem/src/main/kotlin/io/lemon/android/buildSystem/extensions/AndroidCompose.kt rename to build-system/src/main/kotlin/io/lemon/android/buildSystem/extensions/AndroidCompose.kt diff --git a/buildSystem/src/main/kotlin/io/lemon/android/buildSystem/extensions/Flavor.kt b/build-system/src/main/kotlin/io/lemon/android/buildSystem/extensions/Flavor.kt similarity index 100% rename from buildSystem/src/main/kotlin/io/lemon/android/buildSystem/extensions/Flavor.kt rename to build-system/src/main/kotlin/io/lemon/android/buildSystem/extensions/Flavor.kt diff --git a/buildSystem/src/main/kotlin/io/lemon/android/buildSystem/extensions/type/BuildConfigField.kt b/build-system/src/main/kotlin/io/lemon/android/buildSystem/extensions/type/BuildConfigField.kt similarity index 100% rename from buildSystem/src/main/kotlin/io/lemon/android/buildSystem/extensions/type/BuildConfigField.kt rename to build-system/src/main/kotlin/io/lemon/android/buildSystem/extensions/type/BuildConfigField.kt diff --git a/buildSystem/src/main/kotlin/io/lemon/android/buildSystem/extensions/type/FlavorType.kt b/build-system/src/main/kotlin/io/lemon/android/buildSystem/extensions/type/FlavorType.kt similarity index 100% rename from buildSystem/src/main/kotlin/io/lemon/android/buildSystem/extensions/type/FlavorType.kt rename to build-system/src/main/kotlin/io/lemon/android/buildSystem/extensions/type/FlavorType.kt diff --git a/buildSystem/src/main/kotlin/io/lemon/android/buildSystem/extensions/type/ResourceType.kt b/build-system/src/main/kotlin/io/lemon/android/buildSystem/extensions/type/ResourceType.kt similarity index 100% rename from buildSystem/src/main/kotlin/io/lemon/android/buildSystem/extensions/type/ResourceType.kt rename to build-system/src/main/kotlin/io/lemon/android/buildSystem/extensions/type/ResourceType.kt diff --git a/buildSystem/src/main/kotlin/io/lemon/android/buildSystem/plugin/AndroidApplicationComposePlugin.kt b/build-system/src/main/kotlin/io/lemon/android/buildSystem/plugin/AndroidApplicationComposePlugin.kt similarity index 100% rename from buildSystem/src/main/kotlin/io/lemon/android/buildSystem/plugin/AndroidApplicationComposePlugin.kt rename to build-system/src/main/kotlin/io/lemon/android/buildSystem/plugin/AndroidApplicationComposePlugin.kt diff --git a/buildSystem/src/main/kotlin/io/lemon/android/buildSystem/plugin/AndroidApplicationPlugin.kt b/build-system/src/main/kotlin/io/lemon/android/buildSystem/plugin/AndroidApplicationPlugin.kt similarity index 98% rename from buildSystem/src/main/kotlin/io/lemon/android/buildSystem/plugin/AndroidApplicationPlugin.kt rename to build-system/src/main/kotlin/io/lemon/android/buildSystem/plugin/AndroidApplicationPlugin.kt index f691b88..d9bd063 100644 --- a/buildSystem/src/main/kotlin/io/lemon/android/buildSystem/plugin/AndroidApplicationPlugin.kt +++ b/build-system/src/main/kotlin/io/lemon/android/buildSystem/plugin/AndroidApplicationPlugin.kt @@ -1,7 +1,6 @@ package io.lemon.android.buildSystem.plugin import com.android.build.api.dsl.ApplicationExtension -import io.lemon.android.buildSystem.Config import io.lemon.android.buildSystem.Config.COMPILE_SDK import io.lemon.android.buildSystem.Config.MIN_SDK import io.lemon.android.buildSystem.Config.TARGET_SDK diff --git a/buildSystem/src/main/kotlin/io/lemon/android/buildSystem/plugin/AndroidComposeExtPlugin.kt b/build-system/src/main/kotlin/io/lemon/android/buildSystem/plugin/AndroidComposeExtPlugin.kt similarity index 100% rename from buildSystem/src/main/kotlin/io/lemon/android/buildSystem/plugin/AndroidComposeExtPlugin.kt rename to build-system/src/main/kotlin/io/lemon/android/buildSystem/plugin/AndroidComposeExtPlugin.kt diff --git a/buildSystem/src/main/kotlin/io/lemon/android/buildSystem/plugin/AndroidFeaturePlugin.kt b/build-system/src/main/kotlin/io/lemon/android/buildSystem/plugin/AndroidFeaturePlugin.kt similarity index 94% rename from buildSystem/src/main/kotlin/io/lemon/android/buildSystem/plugin/AndroidFeaturePlugin.kt rename to build-system/src/main/kotlin/io/lemon/android/buildSystem/plugin/AndroidFeaturePlugin.kt index f93e6f1..ee73d20 100644 --- a/buildSystem/src/main/kotlin/io/lemon/android/buildSystem/plugin/AndroidFeaturePlugin.kt +++ b/build-system/src/main/kotlin/io/lemon/android/buildSystem/plugin/AndroidFeaturePlugin.kt @@ -12,6 +12,7 @@ class AndroidFeaturePlugin : Plugin { add("implementation", versionCatalog.findLibrary("androidx-appcompat").get()) add("implementation", versionCatalog.findLibrary("androidx-core-ktx").get()) add("implementation", versionCatalog.findLibrary("androidx-core-splashscreen").get()) + add("implementation", versionCatalog.findLibrary("androidx-lifecycle-service").get()) add("implementation", versionCatalog.findLibrary("androidx-lifecycle-viewmodel-ktx").get()) add("implementation", versionCatalog.findLibrary("androidx-lifecycle-runtime-ktx").get()) add("implementation", versionCatalog.findLibrary("androidx-navigation-runtime-ktx").get()) diff --git a/buildSystem/src/main/kotlin/io/lemon/android/buildSystem/plugin/AndroidLibraryComposePlugin.kt b/build-system/src/main/kotlin/io/lemon/android/buildSystem/plugin/AndroidLibraryComposePlugin.kt similarity index 100% rename from buildSystem/src/main/kotlin/io/lemon/android/buildSystem/plugin/AndroidLibraryComposePlugin.kt rename to build-system/src/main/kotlin/io/lemon/android/buildSystem/plugin/AndroidLibraryComposePlugin.kt diff --git a/buildSystem/src/main/kotlin/io/lemon/android/buildSystem/plugin/AndroidLibraryPlugin.kt b/build-system/src/main/kotlin/io/lemon/android/buildSystem/plugin/AndroidLibraryPlugin.kt similarity index 94% rename from buildSystem/src/main/kotlin/io/lemon/android/buildSystem/plugin/AndroidLibraryPlugin.kt rename to build-system/src/main/kotlin/io/lemon/android/buildSystem/plugin/AndroidLibraryPlugin.kt index ad5930b..cf8c30d 100644 --- a/buildSystem/src/main/kotlin/io/lemon/android/buildSystem/plugin/AndroidLibraryPlugin.kt +++ b/build-system/src/main/kotlin/io/lemon/android/buildSystem/plugin/AndroidLibraryPlugin.kt @@ -1,7 +1,6 @@ package io.lemon.android.buildSystem.plugin import com.android.build.gradle.LibraryExtension -import io.lemon.android.buildSystem.Config import io.lemon.android.buildSystem.Config.BuildType.DEBUG import io.lemon.android.buildSystem.Config.BuildType.RELEASE import io.lemon.android.buildSystem.Config.COMPILE_SDK @@ -36,11 +35,11 @@ class AndroidLibraryPlugin : Plugin { isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" + "proguard-rules.pro", ) } } } } } -} \ No newline at end of file +} diff --git a/buildSystem/src/main/kotlin/io/lemon/android/buildSystem/plugin/DataStorePlugin.kt b/build-system/src/main/kotlin/io/lemon/android/buildSystem/plugin/DataStorePlugin.kt similarity index 100% rename from buildSystem/src/main/kotlin/io/lemon/android/buildSystem/plugin/DataStorePlugin.kt rename to build-system/src/main/kotlin/io/lemon/android/buildSystem/plugin/DataStorePlugin.kt diff --git a/buildSystem/src/main/kotlin/io/lemon/android/buildSystem/plugin/HiltPlugin.kt b/build-system/src/main/kotlin/io/lemon/android/buildSystem/plugin/HiltPlugin.kt similarity index 100% rename from buildSystem/src/main/kotlin/io/lemon/android/buildSystem/plugin/HiltPlugin.kt rename to build-system/src/main/kotlin/io/lemon/android/buildSystem/plugin/HiltPlugin.kt diff --git a/buildSystem/src/main/kotlin/io/lemon/android/buildSystem/plugin/KotlinPlugin.kt b/build-system/src/main/kotlin/io/lemon/android/buildSystem/plugin/KotlinPlugin.kt similarity index 100% rename from buildSystem/src/main/kotlin/io/lemon/android/buildSystem/plugin/KotlinPlugin.kt rename to build-system/src/main/kotlin/io/lemon/android/buildSystem/plugin/KotlinPlugin.kt diff --git a/buildSystem/src/main/kotlin/io/lemon/android/buildSystem/plugin/OkhttpPlugin.kt b/build-system/src/main/kotlin/io/lemon/android/buildSystem/plugin/OkhttpPlugin.kt similarity index 100% rename from buildSystem/src/main/kotlin/io/lemon/android/buildSystem/plugin/OkhttpPlugin.kt rename to build-system/src/main/kotlin/io/lemon/android/buildSystem/plugin/OkhttpPlugin.kt diff --git a/buildSystem/src/main/kotlin/io/lemon/android/buildSystem/plugin/RoomPlugin.kt b/build-system/src/main/kotlin/io/lemon/android/buildSystem/plugin/RoomPlugin.kt similarity index 100% rename from buildSystem/src/main/kotlin/io/lemon/android/buildSystem/plugin/RoomPlugin.kt rename to build-system/src/main/kotlin/io/lemon/android/buildSystem/plugin/RoomPlugin.kt diff --git a/buildSystem/src/main/kotlin/io/lemon/android/buildSystem/plugin/TestExtPlugin.kt b/build-system/src/main/kotlin/io/lemon/android/buildSystem/plugin/TestExtPlugin.kt similarity index 100% rename from buildSystem/src/main/kotlin/io/lemon/android/buildSystem/plugin/TestExtPlugin.kt rename to build-system/src/main/kotlin/io/lemon/android/buildSystem/plugin/TestExtPlugin.kt diff --git a/buildSystem/src/main/kotlin/io/lemon/android/buildSystem/util/Extensions.kt b/build-system/src/main/kotlin/io/lemon/android/buildSystem/util/Extensions.kt similarity index 100% rename from buildSystem/src/main/kotlin/io/lemon/android/buildSystem/util/Extensions.kt rename to build-system/src/main/kotlin/io/lemon/android/buildSystem/util/Extensions.kt diff --git a/build.gradle.kts b/build.gradle.kts index 28b80bd..1fff3bb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,6 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. +import org.jlleitschuh.gradle.ktlint.reporter.ReporterType +import org.jlleitschuh.gradle.ktlint.tasks.GenerateReportsTask + plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.android.library) apply false @@ -10,5 +12,33 @@ plugins { alias(libs.plugins.kotlin.serialization) apply false alias(libs.plugins.ksp) apply false alias(libs.plugins.parcelize) apply false + alias(libs.plugins.ktlint) apply true +} + +allprojects { + apply { + plugin("org.jlleitschuh.gradle.ktlint") + } + ktlint { + reporters { + reporter(ReporterType.JSON) + reporter(ReporterType.CHECKSTYLE) + } + } + tasks.withType { + reportsOutputDirectory.set( + rootProject.layout.buildDirectory.dir("reports/ktlint/${project.name}") + ) + } +} -} \ No newline at end of file +tasks.register("assembleAAR") { + from( + project.provider { + subprojects.flatMap { subproject -> + subproject.layout.buildDirectory.dir("outputs/aar").get().asFile.listFiles()?.toList() ?: emptyList() + } + } + ) + into(rootProject.layout.buildDirectory.dir("outputs/aar")) +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1d6422e..247f115 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,6 +21,7 @@ espresso = "3.0.2" gradle = "8.5.1" hilt = "2.49" junit = "4.13.2" +ktlint = "10.3.0" kotlin = "2.0.0" kotlinxCoroutines = "1.8.0" kotlinxDatetime = "0.5.0" @@ -38,6 +39,7 @@ android-library = { id = "com.android.library", version.ref = "gradle" } android-test = { id = "com.android.test", version.ref = "gradle" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } +ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } @@ -86,6 +88,7 @@ androidx-test-core = { group = "androidx.test", name = "core", version.ref = "an androidx-test-rules = { group = "androidx.test", name = "rules", version.ref = "androidxTestRules" } androidx-test-runner = { group = "androidx.test", name = "runner", version.ref = "androidxTestRunner" } androidx-test-ext = { group = "androidx.test.ext", name = "junit", version.ref = "androidxTestExt" } +androidx-lifecycle-service = { group = "androidx.lifecycle", name = "lifecycle-service", version.ref = "androidxLifecycle" } androidx-lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "androidxLifecycle" } androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "androidxLifecycle" } androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidxLifecycle" } diff --git a/init_lint_settings.sh b/init_lint_settings.sh new file mode 100755 index 0000000..a118577 --- /dev/null +++ b/init_lint_settings.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +chmod +x "./gradlew" + +# pre commit 린트 검사 훅 등록 +./gradlew addKtlintCheckGitPreCommitHook + +echo "린트 검사 규칙이 적용되었습니다." diff --git a/ui/architecture/.gitignore b/lemon-core-android/component/.gitignore similarity index 100% rename from ui/architecture/.gitignore rename to lemon-core-android/component/.gitignore diff --git a/lemon-core-android/component/README.md b/lemon-core-android/component/README.md new file mode 100644 index 0000000..0f309ad --- /dev/null +++ b/lemon-core-android/component/README.md @@ -0,0 +1,18 @@ +# Android-Component +android component 유틸리티 + +### Intent +`Intent` 설정을 빌드하는 모듈입니다. `IntentBuilder`를 사용하여 Intent를 구성할 수 있으며, 이는 `Component` 의 `Launcher`와 연동하여 사용할 수 있습니다. +또한 특수한 목적으로 사용되는 `Intent`를 빠르게 구성하는 확장 람다 함수가 존재합니다. (예를 들어, URL에 대한 사이트를 빠르게 불러오는 `getUrlIntent`, Application 설정으로 빠르게 이동하는 `getSettingIntent`) +`Intent`를 컴포넌트 목적에 따른 `PendingIntent`로 변환하는 함수는 `PendingIntent` Object 내에 존재합니다. + +### Launcher +`Android Component` 설정을 빠르게 구성하는 모듈입니다. +`Launcher` 생성 시 `Intent` 가 구성되며, 컴포넌트를 수행할 수 있는 함수가 존재합니다. 내부 `Intent` 정보는 `IntentBuilder`를 사용하여 수정할 수 있습니다. 이를 통해 extra,data 그리고 특수한 flag등을 설정할 수 있습니다. + + + + + + + diff --git a/lemon-core-android/component/build.gradle.kts b/lemon-core-android/component/build.gradle.kts new file mode 100644 index 0000000..494e303 --- /dev/null +++ b/lemon-core-android/component/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + alias(libs.plugins.lemon.android.library) + alias(libs.plugins.lemon.android.feature) + alias(libs.plugins.lemon.android.kotlin) +} + +android { + namespace = "io.lemon.android.core.android.component" +} diff --git a/lemon-core-android/component/consumer-rules.pro b/lemon-core-android/component/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/ui/architecture/proguard-rules.pro b/lemon-core-android/component/proguard-rules.pro similarity index 100% rename from ui/architecture/proguard-rules.pro rename to lemon-core-android/component/proguard-rules.pro diff --git a/lemon-core-android/component/src/main/AndroidManifest.xml b/lemon-core-android/component/src/main/AndroidManifest.xml new file mode 100644 index 0000000..8bdb7e1 --- /dev/null +++ b/lemon-core-android/component/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + diff --git a/lemon-core-android/component/src/main/java/io/remon/android/core/android/component/intent/Intent.kt b/lemon-core-android/component/src/main/java/io/remon/android/core/android/component/intent/Intent.kt new file mode 100644 index 0000000..e36b2b7 --- /dev/null +++ b/lemon-core-android/component/src/main/java/io/remon/android/core/android/component/intent/Intent.kt @@ -0,0 +1,89 @@ +package io.remon.android.core.android.component.intent + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.Parcelable +import android.provider.Settings +import java.io.Serializable + +/** + * [Intent] + * + * Intent 유틸리티 + * + * @author raine@lemoncloud.io + */ +object Intent { + + /** + * [intentBuilder] + * + * 인텐트 빌더를 생성합니다. + * + * context를 포함하지 않는 인텐트의 경우 해당 메서드를 사용합니다. + * + * @see IntentBuilder + */ + fun intentBuilder(): IntentBuilder = IntentBuilder() + + /** + * [intentBuilder] + * + * 인텐트 빌더를 생성합니다. + * + * intent 타겟을 포함할 경우 헤당 메서드를 사용합니다. + * + * @see IntentBuilder + */ + fun intentBuilder(context: Context, `class`: Class<*>): IntentBuilder = IntentBuilder(context, `class`) + + /** + * [getParcelableExtraExt] + * + * intent extra에 포함되어 있는 parcelable 객체를 가져올 때 사용합니다. + */ + fun Intent.getParcelableExtraExt(key: String, `class`: Class): T? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) + getParcelableExtra(key, `class`) + else getParcelableExtra(key) + } + + /** + * [getSerializableExtraExt] + * + * intent extra에 포함되어 있는 serializable 객체를 가져올 때 사용합니다. + */ + @Suppress("UNCHECKED_CAST") + fun Intent.getSerializableExtraExt(key: String, `class`: Class): T? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + this.getSerializableExtra(key, `class`) + } else { + this.getSerializableExtra(key) as T? + } + } + + /** + * [getUrlIntent] + * + * url 주소를 포함한 intent를 생성합니다. + * + * `startActivity()`와 연계하여 사용합니다. + */ + val getUrlIntent: (String) -> Intent = + { url -> intentBuilder().setAction(Intent.ACTION_VIEW).setData(Uri.parse(url)).build() } + + /** + * [getSettingsIntent] + * + * 애플리케이션의 설정으로 이동하는 intent를 생성합니다. + * + * `startActivity()`와 연계하여 사용합니다. + */ + val getSettingsIntent: (Context) -> Intent = { context -> + intentBuilder().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).setData( + Uri.parse("package:${context.packageName}") + ).build() + } +} diff --git a/lemon-core-android/component/src/main/java/io/remon/android/core/android/component/intent/IntentBuilder.kt b/lemon-core-android/component/src/main/java/io/remon/android/core/android/component/intent/IntentBuilder.kt new file mode 100644 index 0000000..9955ae9 --- /dev/null +++ b/lemon-core-android/component/src/main/java/io/remon/android/core/android/component/intent/IntentBuilder.kt @@ -0,0 +1,117 @@ +package io.remon.android.core.android.component.intent + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Parcelable +import androidx.core.os.bundleOf +import java.io.Serializable + +/** + * [IntentBuilder] + * + * intent 생성을 도와주는 Builder 클래스 + * + * @author raine@lemoncloud.io + */ +open class IntentBuilder() { + + private var intent: Intent = Intent() + + constructor(context: Context, `class`: Class<*>) : this() { + intent = Intent(context, `class`) + } + + /** + * [putExtras] + * + * intent 에 담아서 보낼 데이터인, extra를 정의합니다. + */ + open fun putExtra(extra: T, key: String = DEFAULT_KEY) = apply { + intent.putExtra(key, extra) + } + + open fun putExtra(extra: T, key: String = DEFAULT_KEY) = apply { + intent.putExtra(key, extra) + } + + open fun putExtra(extra: ArrayList, key: String = DEFAULT_KEY) = apply { + intent.putExtra(key, extra) + } + + open fun putExtra(extra: String, key: String = DEFAULT_KEY) = apply { + intent.putExtra(key, extra) + } + + open fun putExtra(extra: Long, key: String = DEFAULT_KEY) = apply { + intent.putExtra(key, extra) + } + + open fun putExtra(extra: Int, key: String = DEFAULT_KEY) = apply { + intent.putExtra(key, extra) + } + + open fun putExtra(extra: Float, key: String = DEFAULT_KEY) = apply { + intent.putExtra(key, extra) + } + + open fun putExtra(extra: Boolean, key: String = DEFAULT_KEY) = apply { + intent.putExtra(key, extra) + } + + /** + * [putExtras] + * + * 번들 단위로 데이터 추가 + */ + open fun putExtras(vararg pairs: Pair) = apply { + intent.putExtras(bundleOf(*pairs)) + } + + /** + * [setAction] + * + * action 추가 + */ + open fun setAction(action: String) = apply { + intent.setAction(action) + } + + /** + * [setAction] + * + * uri 형태 데이터 추가 + */ + open fun setData(uri: Uri) = apply { + intent.setData(uri) + } + + /** + * [setFlags] + * + * 플래그 설정 + */ + open fun setFlags(flag: Int) = apply { + intent.setFlags(flag) + } + + /** + * [addFlags] + * + * 플래그 추가 + */ + open fun addFlags(flag: Int) = apply { + intent.addFlags(flag) + } + + /** + * [build] + * + * IntentBuilder를 통해 조합한 intent 객체를 반환합니다. + */ + open fun build(): Intent = intent + + companion object { + const val DEFAULT_KEY = "KEY" + } +} diff --git a/lemon-core-android/component/src/main/java/io/remon/android/core/android/component/intent/PendingIntent.kt b/lemon-core-android/component/src/main/java/io/remon/android/core/android/component/intent/PendingIntent.kt new file mode 100644 index 0000000..7eb2105 --- /dev/null +++ b/lemon-core-android/component/src/main/java/io/remon/android/core/android/component/intent/PendingIntent.kt @@ -0,0 +1,123 @@ +package io.remon.android.core.android.component.intent + +import android.app.PendingIntent +import android.content.Context +import android.content.Intent + +/** + * [PendingIntent] + * + * PendingIntent 관련 유틸리티 + * + * @author raine@lemoncloud.io + */ +object PendingIntent { + + /** + * [getActivityPendingIntent] + * + * Activity를 포함한 pendingIntent 생성 + * + * [PendingIntent.FLAG_CANCEL_CURRENT] + * 이전에 생성한 PendingIntent 취소 후 새로 생성함 + * + * [PendingIntent.FLAG_NO_CREATE] + * 이미 생성된 PendingIntent 가 있을 경우 재사용 없을 경우, null 반환 + * + * [PendingIntent.FLAG_ONE_SHOT] + * 일회성 사용 + * + * [PendingIntent.FLAG_UPDATE_CURRENT] + * 이미 생성된 PendingIntent 가 있을 경우 extraData를 교체함 + * + * [PendingIntent.FLAG_IMMUTABLE] + * PendingIntent를 수정할 수 없도록 함 (보안 이슈) + */ + private fun getActivityPendingIntent( + context: Context, + intent: Intent, + requestCode: Int, + flags: Int = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ): PendingIntent = PendingIntent.getActivity(context, requestCode, intent, flags) + + /** + * [getActivitiesPendingIntent] + * + * 여러 개의 Activity를 포함한 pendingIntent 생성 + * + * [PendingIntent.FLAG_CANCEL_CURRENT] + * 이전에 생성한 PendingIntent 취소 후 새로 생성함 + * + * [PendingIntent.FLAG_NO_CREATE] + * 이미 생성된 PendingIntent 가 있을 경우 재사용 없을 경우, null 반환 + * + * [PendingIntent.FLAG_ONE_SHOT] + * 일회성 사용 + * + * [PendingIntent.FLAG_UPDATE_CURRENT] + * 이미 생성된 PendingIntent 가 있을 경우 extraData를 교체함 + * + * [PendingIntent.FLAG_IMMUTABLE] + * PendingIntent를 수정할 수 없도록 함 (보안 이슈) + */ + private fun getActivitiesPendingIntent( + context: Context, + intents: Array, + requestCode: Int, + flags: Int = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ): PendingIntent = PendingIntent.getActivities(context, requestCode, intents, flags) + + /** + * [getBroadcastPendingIntent] + * + * Broadcast를 포함한 pendingIntent 생성 + * + * [PendingIntent.FLAG_CANCEL_CURRENT] + * 이전에 생성한 PendingIntent 취소 후 새로 생성함 + * + * [PendingIntent.FLAG_NO_CREATE] + * 이미 생성된 PendingIntent 가 있을 경우 재사용 없을 경우, null 반환 + * + * [PendingIntent.FLAG_ONE_SHOT] + * 일회성 사용 + * + * [PendingIntent.FLAG_UPDATE_CURRENT] + * 이미 생성된 PendingIntent 가 있을 경우 extraData를 교체함 + * + * [PendingIntent.FLAG_IMMUTABLE] + * PendingIntent를 수정할 수 없도록 함 (보안 이슈) + */ + private fun getBroadcastPendingIntent( + context: Context, + intent: Intent, + requestCode: Int, + flags: Int = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ): PendingIntent = PendingIntent.getBroadcast(context, requestCode, intent, flags) + + /** + * [getServicePendingIntent] + * + * Serivce를 포함한 pendingIntent 생성 + * + * [PendingIntent.FLAG_CANCEL_CURRENT] + * 이전에 생성한 PendingIntent 취소 후 새로 생성함 + * + * [PendingIntent.FLAG_NO_CREATE] + * 이미 생성된 PendingIntent 가 있을 경우 재사용 없을 경우, null 반환 + * + * [PendingIntent.FLAG_ONE_SHOT] + * 일회성 사용 + * + * [PendingIntent.FLAG_UPDATE_CURRENT] + * 이미 생성된 PendingIntent 가 있을 경우 extraData를 교체함 + * + * [PendingIntent.FLAG_IMMUTABLE] + * PendingIntent를 수정할 수 없도록 함 (보안 이슈) + */ + private fun getServicePendingIntent( + context: Context, + intent: Intent, + requestCode: Int, + flags: Int = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ): PendingIntent = PendingIntent.getService(context, requestCode, intent, flags) +} diff --git a/lemon-core-android/component/src/main/java/io/remon/android/core/android/component/launcher/ActivityLauncher.kt b/lemon-core-android/component/src/main/java/io/remon/android/core/android/component/launcher/ActivityLauncher.kt new file mode 100644 index 0000000..796a585 --- /dev/null +++ b/lemon-core-android/component/src/main/java/io/remon/android/core/android/component/launcher/ActivityLauncher.kt @@ -0,0 +1,91 @@ +package io.remon.android.core.android.component.launcher + +import android.app.Activity +import android.app.Service +import android.content.Context +import android.content.Intent +import android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP +import android.content.Intent.FLAG_ACTIVITY_NO_HISTORY +import android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP +import io.remon.android.core.android.component.intent.IntentBuilder +import kotlin.reflect.KClass + +/** + * [ActivityLauncher] + * + * 활동(Activity) 관련한 초기화 및 시작 작업을 도와주는 클래스 + * + * @author raine@lemoncloud.io + */ +class ActivityLauncher( + private val context: Context, + private val activityClass: KClass +) { + private var intent: Intent = Intent(context, activityClass.java) + + /** + * [intentBuilder] + * + * [Service] 에 대한 Intent를 작업하기 위한 [IntentBuilder]를 생성합니다. + * + * [IntentBuilder]를 통해 생성한 `Intent` 를 [updateIntent]를 사용하여 업데이트할 수 있습니다. + * + * @see IntentBuilder + */ + fun intentBuilder(): IntentBuilder = IntentBuilder(context, activityClass.java) + + /** + * [updateIntent] + * + * 인텐트에 별도 설정을 추가하여 업데이트 해야하는 상황에서 사용합니다 + * + * [intentBuilder] 를 통해 IntentBuilder를 연결할 수 있습니다. + * + * @see IntentBuilder + */ + fun updateIntent(intent: Intent) { + this.intent = intent + } + + /** + * [startActivity] + * + * 활동을 시작합니다. + */ + fun startActivity() { + context.startActivity(intent) + } + + /** + * [startActivityNoHistory] + * + * 활동이 테스크 스택에 쌓이지 않고 시작됩니다. + */ + fun startActivityNoHistory(context: Context) { + context.startActivity( + intent.also { it.setFlags(FLAG_ACTIVITY_NO_HISTORY) } + ) + } + + /** + * [startActivityClearTop] + * + * 활동을 테스크 스택 최상위로 올리고 그 사이 테스크 스택을 전부 제거합니다. + */ + fun startActivityClearTop(context: Context) { + context.startActivity( + intent.also { it.setFlags(FLAG_ACTIVITY_CLEAR_TOP) } + ) + } + + /** + * [startActivitySingleTop] + * + * 활동을 태스크 스택 최상위로 올림 만약 테스크 내에 동일한 활동이 존재하지 않을경우, 새로 생성합니다. + */ + fun startActivitySingleTop(context: Context) { + context.startActivity( + intent.also { it.setFlags(FLAG_ACTIVITY_SINGLE_TOP) } + ) + } +} diff --git a/lemon-core-android/component/src/main/java/io/remon/android/core/android/component/launcher/ReceiverLauncher.kt b/lemon-core-android/component/src/main/java/io/remon/android/core/android/component/launcher/ReceiverLauncher.kt new file mode 100644 index 0000000..29d0334 --- /dev/null +++ b/lemon-core-android/component/src/main/java/io/remon/android/core/android/component/launcher/ReceiverLauncher.kt @@ -0,0 +1,46 @@ +package io.remon.android.core.android.component.launcher + +import android.app.Service +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import io.remon.android.core.android.component.intent.IntentBuilder +import kotlin.reflect.KClass + +/** + * [ReceiverLauncher] + * + * 수신자(Receiver) 관련한 초기화 및 시작 작업을 도와주는 클래스 + * + * @author raine@lemoncloud.io + */ +class ReceiverLauncher( + private val context: Context, + private val receiverClass: KClass +) { + private var intent: Intent = Intent(context, receiverClass.java) + + /** + * [intentBuilder] + * + * [Service] 에 대한 Intent를 작업하기 위한 [IntentBuilder]를 생성합니다. + * + * [IntentBuilder]를 통해 생성한 `Intent` 를 [updateIntent]를 사용하여 업데이트할 수 있습니다. + * + * @see IntentBuilder + */ + fun intentBuilder(): IntentBuilder = IntentBuilder(context, receiverClass.java) + + /** + * [updateIntent] + * + * 인텐트에 별도 설정을 추가하여 업데이트 해야하는 상황에서 사용합니다 + * + * [intentBuilder] 를 통해 IntentBuilder를 연결할 수 있습니다. + * + * @see IntentBuilder + */ + fun updateIntent(intent: Intent) { + this.intent = intent + } +} diff --git a/lemon-core-android/component/src/main/java/io/remon/android/core/android/component/launcher/ServiceLauncher.kt b/lemon-core-android/component/src/main/java/io/remon/android/core/android/component/launcher/ServiceLauncher.kt new file mode 100644 index 0000000..1658274 --- /dev/null +++ b/lemon-core-android/component/src/main/java/io/remon/android/core/android/component/launcher/ServiceLauncher.kt @@ -0,0 +1,76 @@ +package io.remon.android.core.android.component.launcher + +import android.app.Service +import android.content.Context +import android.content.Intent +import android.os.Build +import io.remon.android.core.android.component.intent.IntentBuilder +import kotlin.reflect.KClass + +/** + * [ServiceLauncher] + * + * 서비스(Service) 관련한 초기화 및 시작 작업을 도와주는 클래스 + * + * @author raine@lemoncloud.io + */ +class ServiceLauncher( + private val context: Context, + private val serviceClass: KClass +) { + private var intent: Intent = Intent(context, serviceClass.java) + + /** + * [intentBuilder] + * + * [Service] 에 대한 Intent를 작업하기 위한 [IntentBuilder]를 생성합니다. + * + * [IntentBuilder]를 통해 생성한 `Intent` 를 [updateIntent]를 사용하여 업데이트할 수 있습니다. + * + * @see IntentBuilder + */ + fun intentBuilder(): IntentBuilder = IntentBuilder(context, serviceClass.java) + + /** + * [updateIntent] + * + * 인텐트에 별도 설정을 추가하여 업데이트 해야하는 상황에서 사용합니다 + * + * [intentBuilder] 를 통해 IntentBuilder를 연결할 수 있습니다. + * + * @see IntentBuilder + */ + fun updateIntent(intent: Intent) { + this.intent = intent + } + + /** + * [startService] + * + * 서비스를 시작합니다. + */ + fun startService() { + context.startService(intent) + } + + /** + * [startService] + * + * 서비스를 종료합니다. + */ + fun stopService() { + context.stopService(intent) + } + + /** + * [startForegroundService] + * + * Foreground 서비스를 시작합니다. + */ + fun startForegroundService(context: Context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + context.startForegroundService(intent) + else + context.startService(intent) + } +} diff --git a/lemon-core-ui/architecture/.gitignore b/lemon-core-ui/architecture/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/lemon-core-ui/architecture/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lemon-core-ui/architecture/README.md b/lemon-core-ui/architecture/README.md new file mode 100644 index 0000000..a6c95b2 --- /dev/null +++ b/lemon-core-ui/architecture/README.md @@ -0,0 +1,20 @@ +# UI-Architecture +안드로이드 UI 구조를 효과적으로 빌딩하기 위한 아키텍처 라이브러리. MVI 기반의 아키텍처로 State Event Effect를 제어하여 사용자와 UI간의 상태 및 이벤트 흐름과 사이에 발생하는 이펙트를 효과적으로 처리할 수 있습니다. + +### 사용방법 + +sample 프로젝트 참고 +1. `BaseViewModel`를 확장한 `ViewModel` 추가 +2. `BaseState`를 확장한 `State` data class 추가 +3. `BaseEvent`를 확장한 `Event` sealed interface 추가 +4. `BaseEffect`를 확장한 `Effect` sealed interface 추가 +5. (필요시) `BaseError`를 확장한 `Error` data class 추가 +6. override 되는 `createConfig()` 메서드를 바탕으로 Config 초기 값 설정 +7. `BaseViewModel`에 정의한 bind 메서드를 사용하여 event, effect, error 플로우 제어 +8. state의 경우 자체 `StateFlow`를 관측하여 UI 상태 감지 가능 + + +### Architecture + +![ui-architecture](./assets/ui-architecture.jpeg) + diff --git a/ui/architecture/assets/ui-architecture.jpeg b/lemon-core-ui/architecture/assets/ui-architecture.jpeg similarity index 100% rename from ui/architecture/assets/ui-architecture.jpeg rename to lemon-core-ui/architecture/assets/ui-architecture.jpeg diff --git a/ui/architecture/build.gradle.kts b/lemon-core-ui/architecture/build.gradle.kts similarity index 73% rename from ui/architecture/build.gradle.kts rename to lemon-core-ui/architecture/build.gradle.kts index 78afd12..b4051c6 100644 --- a/ui/architecture/build.gradle.kts +++ b/lemon-core-ui/architecture/build.gradle.kts @@ -5,5 +5,5 @@ plugins { } android { - namespace = "io.lemon.android.ui.architecture" -} \ No newline at end of file + namespace = "io.lemon.android.core.ui.architecture" +} diff --git a/ui/architecture/consumer-rules.pro b/lemon-core-ui/architecture/consumer-rules.pro similarity index 100% rename from ui/architecture/consumer-rules.pro rename to lemon-core-ui/architecture/consumer-rules.pro diff --git a/lemon-core-ui/architecture/proguard-rules.pro b/lemon-core-ui/architecture/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/lemon-core-ui/architecture/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/ui/architecture/src/main/kotlin/io/lemon/android/core/ui/architecture/BaseEffect.kt b/lemon-core-ui/architecture/src/main/kotlin/io/lemon/android/core/ui/architecture/BaseEffect.kt similarity index 100% rename from ui/architecture/src/main/kotlin/io/lemon/android/core/ui/architecture/BaseEffect.kt rename to lemon-core-ui/architecture/src/main/kotlin/io/lemon/android/core/ui/architecture/BaseEffect.kt diff --git a/ui/architecture/src/main/kotlin/io/lemon/android/core/ui/architecture/BaseError.kt b/lemon-core-ui/architecture/src/main/kotlin/io/lemon/android/core/ui/architecture/BaseError.kt similarity index 100% rename from ui/architecture/src/main/kotlin/io/lemon/android/core/ui/architecture/BaseError.kt rename to lemon-core-ui/architecture/src/main/kotlin/io/lemon/android/core/ui/architecture/BaseError.kt diff --git a/ui/architecture/src/main/kotlin/io/lemon/android/core/ui/architecture/BaseEvent.kt b/lemon-core-ui/architecture/src/main/kotlin/io/lemon/android/core/ui/architecture/BaseEvent.kt similarity index 100% rename from ui/architecture/src/main/kotlin/io/lemon/android/core/ui/architecture/BaseEvent.kt rename to lemon-core-ui/architecture/src/main/kotlin/io/lemon/android/core/ui/architecture/BaseEvent.kt diff --git a/ui/architecture/src/main/kotlin/io/lemon/android/core/ui/architecture/BaseState.kt b/lemon-core-ui/architecture/src/main/kotlin/io/lemon/android/core/ui/architecture/BaseState.kt similarity index 100% rename from ui/architecture/src/main/kotlin/io/lemon/android/core/ui/architecture/BaseState.kt rename to lemon-core-ui/architecture/src/main/kotlin/io/lemon/android/core/ui/architecture/BaseState.kt diff --git a/ui/architecture/src/main/kotlin/io/lemon/android/core/ui/architecture/BaseViewModel.kt b/lemon-core-ui/architecture/src/main/kotlin/io/lemon/android/core/ui/architecture/BaseViewModel.kt similarity index 100% rename from ui/architecture/src/main/kotlin/io/lemon/android/core/ui/architecture/BaseViewModel.kt rename to lemon-core-ui/architecture/src/main/kotlin/io/lemon/android/core/ui/architecture/BaseViewModel.kt diff --git a/ui/architecture/src/main/kotlin/io/lemon/android/core/ui/architecture/Config.kt b/lemon-core-ui/architecture/src/main/kotlin/io/lemon/android/core/ui/architecture/Config.kt similarity index 100% rename from ui/architecture/src/main/kotlin/io/lemon/android/core/ui/architecture/Config.kt rename to lemon-core-ui/architecture/src/main/kotlin/io/lemon/android/core/ui/architecture/Config.kt diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts index 86cbf5e..5416df0 100644 --- a/sample/build.gradle.kts +++ b/sample/build.gradle.kts @@ -10,11 +10,9 @@ plugins { alias(libs.plugins.lemon.android.kotlin) } - -android{ - namespace = "io.lemon.android" +android { + namespace = "io.lemon.android.core" } dependencies { - implementation(project(":ui:architecture")) + implementation(project(":lemon-core-ui:architecture")) } - diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index f5dd57f..c20dfba 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -10,7 +10,7 @@ android:theme="@style/Theme.Lemon"> @@ -19,4 +19,4 @@ - \ No newline at end of file + diff --git a/sample/src/main/java/io/lemon/android/MainActivity.kt b/sample/src/main/java/io/lemon/android/MainActivity.kt index f6a1045..74caa81 100644 --- a/sample/src/main/java/io/lemon/android/MainActivity.kt +++ b/sample/src/main/java/io/lemon/android/MainActivity.kt @@ -10,9 +10,8 @@ class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContent { HomeScreen() } } -} \ No newline at end of file +} diff --git a/sample/src/main/java/io/lemon/android/core/home/HomeScreen.kt b/sample/src/main/java/io/lemon/android/core/home/HomeScreen.kt index e650939..b3739d5 100644 --- a/sample/src/main/java/io/lemon/android/core/home/HomeScreen.kt +++ b/sample/src/main/java/io/lemon/android/core/home/HomeScreen.kt @@ -8,11 +8,11 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.Text import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -29,11 +29,10 @@ import io.lemon.android.core.home.data.HomeEvent import io.lemon.android.core.home.data.HomeState import io.lemon.android.core.home.data.HomeViewModel - @Composable fun HomeScreen( modifier: Modifier = Modifier, - viewModel: HomeViewModel = viewModel() + viewModel: HomeViewModel = viewModel(), ) { val state: HomeState by viewModel.state.collectAsStateWithLifecycle() val snackbarHostState = remember { SnackbarHostState() } @@ -50,7 +49,7 @@ fun HomeScreen( modifier = modifier, snackbarHost = { SnackbarHost(hostState = snackbarHostState) - } + }, ) { Column( modifier = modifier @@ -59,29 +58,32 @@ fun HomeScreen( .padding(horizontal = 20.dp) .padding(it), verticalArrangement = Arrangement.spacedBy(48.dp, Alignment.CenterVertically), - horizontalAlignment = Alignment.CenterHorizontally + horizontalAlignment = Alignment.CenterHorizontally, ) { - if (state.isLoading) CircularProgressIndicator() - else { + if (state.isLoading) { + CircularProgressIndicator() + } else { Text( text = "${state.count}", - fontSize = 36.sp + fontSize = 36.sp, ) Column( horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(12.dp) + verticalArrangement = Arrangement.spacedBy(12.dp), ) { Row( - horizontalArrangement = Arrangement.spacedBy(24.dp) + horizontalArrangement = Arrangement.spacedBy(24.dp), ) { Button( modifier = modifier.weight(1f), - onClick = { viewModel.onEvent(HomeEvent.OnClickCountUpButton) }) { + onClick = { viewModel.onEvent(HomeEvent.OnClickCountUpButton) }, + ) { Text(text = "Count Up") } Button( modifier = modifier.weight(1f), - onClick = { viewModel.onEvent(HomeEvent.OnClickCountDownButton) }) { + onClick = { viewModel.onEvent(HomeEvent.OnClickCountDownButton) }, + ) { Text(text = "Count Down") } } @@ -91,11 +93,8 @@ fun HomeScreen( } } - @Composable @Preview -fun HomeScreenPreview( - modifier: Modifier = Modifier, -) { +fun HomeScreenPreview(modifier: Modifier = Modifier) { HomeScreen() -} \ No newline at end of file +} diff --git a/sample/src/main/java/io/lemon/android/core/home/data/HomeEffect.kt b/sample/src/main/java/io/lemon/android/core/home/data/HomeEffect.kt index cd0a21a..afff276 100644 --- a/sample/src/main/java/io/lemon/android/core/home/data/HomeEffect.kt +++ b/sample/src/main/java/io/lemon/android/core/home/data/HomeEffect.kt @@ -4,4 +4,4 @@ import io.lemon.android.core.ui.architecture.BaseEffect sealed interface HomeEffect : BaseEffect { data class ShowSnackbar(val message: String) : HomeEffect -} \ No newline at end of file +} diff --git a/sample/src/main/java/io/lemon/android/core/home/data/HomeError.kt b/sample/src/main/java/io/lemon/android/core/home/data/HomeError.kt index a0aa981..14d6df1 100644 --- a/sample/src/main/java/io/lemon/android/core/home/data/HomeError.kt +++ b/sample/src/main/java/io/lemon/android/core/home/data/HomeError.kt @@ -5,4 +5,4 @@ import io.lemon.android.core.ui.architecture.BaseError data class HomeError( override val message: String, override val exception: Throwable?, -) : BaseError \ No newline at end of file +) : BaseError diff --git a/sample/src/main/java/io/lemon/android/core/home/data/HomeEvent.kt b/sample/src/main/java/io/lemon/android/core/home/data/HomeEvent.kt index 69ff9c2..6b351d9 100644 --- a/sample/src/main/java/io/lemon/android/core/home/data/HomeEvent.kt +++ b/sample/src/main/java/io/lemon/android/core/home/data/HomeEvent.kt @@ -2,9 +2,9 @@ package io.lemon.android.core.home.data import io.lemon.android.core.ui.architecture.BaseEvent - sealed interface HomeEvent : BaseEvent { data object OnClickCountUpButton : HomeEvent + data object OnClickCountDownButton : HomeEvent -} \ No newline at end of file +} diff --git a/sample/src/main/java/io/lemon/android/core/home/data/HomeState.kt b/sample/src/main/java/io/lemon/android/core/home/data/HomeState.kt index 85688ac..fa640d1 100644 --- a/sample/src/main/java/io/lemon/android/core/home/data/HomeState.kt +++ b/sample/src/main/java/io/lemon/android/core/home/data/HomeState.kt @@ -2,8 +2,7 @@ package io.lemon.android.core.home.data import io.lemon.android.core.ui.architecture.BaseState - data class HomeState( override val isLoading: Boolean = false, val count: Int = 0, -) : BaseState \ No newline at end of file +) : BaseState diff --git a/sample/src/main/java/io/lemon/android/core/home/data/HomeViewModel.kt b/sample/src/main/java/io/lemon/android/core/home/data/HomeViewModel.kt index 7b28b68..c6f0e2f 100644 --- a/sample/src/main/java/io/lemon/android/core/home/data/HomeViewModel.kt +++ b/sample/src/main/java/io/lemon/android/core/home/data/HomeViewModel.kt @@ -5,16 +5,14 @@ import io.lemon.android.core.ui.architecture.BaseViewModel class HomeViewModel : BaseViewModel() { - override fun createInitialState(): HomeState = HomeState() override fun createConfig(): Config = Config() - init { bindEvent { when (it) { HomeEvent.OnClickCountDownButton -> { - if (state.value.count <= 0) tryEmitError(HomeError(message = "0 이하로 내릴 수 없습니다.", exception = null)) + if (state.value.count <= 0) tryEmitError(HomeError(message = "0 이하로 내릴 수 없습니다.", exception = null)) else updateState { copy(count = count - 1) } } @@ -24,5 +22,4 @@ class HomeViewModel : BaseViewModel bindError { tryEmitEffect(HomeEffect.ShowSnackbar(it.message)) } } - -} \ No newline at end of file +} diff --git a/settings.gradle.kts b/settings.gradle.kts index e12deda..4fa1353 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,5 +1,5 @@ pluginManagement { - includeBuild("buildSystem") + includeBuild("build-system") repositories { google { @@ -22,4 +22,5 @@ dependencyResolutionManagement { } include(":sample") -include(":ui:architecture") +include(":lemon-core-ui:architecture") +include(":lemon-core-android:component") diff --git a/ui/architecture/README.md b/ui/architecture/README.md deleted file mode 100644 index e4e2ae2..0000000 --- a/ui/architecture/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# UI-Architecture -안드로이드 UI 구조를 효과적으로 빌딩하기 위한 아키텍처 라이브러리. MVI 기반의 아키텍처로 State Event Effect를 제어하여 사용자와 UI간의 상태 및 이벤트 흐름과 사이에 발생하는 이펙트를 효과적으로 처리할 수 있습니다. - -### 사용방법 - -sample 프로젝트 참고 -1. BaseViewModel를 확장한 ViewModel 추가 -2. BaseState를 확장한 State data class 추가 -3. BaseEvent를 확장한 Event sealed interface 추가 -4. BaseEffect를 확장한 Effect sealed interface 추가 -5. (필요시) BaseError를 확장한 data class 추가 -6. override 되는 createConfig() 메서드를 바탕으로 Config 초기 값 설정 -7. BaseViewModel에 정의한 bind 메서드를 사용하여 event, effect, error 플로우 제어 -8. state의 경우 자체 StateFlow를 관측하여 UI 상태 감지 가능 - - -### Architecture - -![ui-architecture](./assets/ui-architecture.jpeg) -