diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 613dd5e49..d41668200 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,18 +1,23 @@
+import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties
+
plugins {
alias(libs.plugins.android.application)
- alias(libs.plugins.jetbrains.kotlin.android)
+ alias(libs.plugins.kotlin.android)
+ alias(libs.plugins.hilt)
+ alias(libs.plugins.kotlin.parcelize)
+ alias(libs.plugins.ksp)
}
android {
namespace = "com.terning.point"
- compileSdk = 34
+ compileSdk = libs.versions.compileSdk.get().toInt()
defaultConfig {
applicationId = "com.terning.point"
- minSdk = 28
- targetSdk = 34
- versionCode = 1
- versionName = "1.0"
+ minSdk = libs.versions.minSdk.get().toInt()
+ targetSdk = libs.versions.targetSdk.get().toInt()
+ versionCode = libs.versions.versionCode.get().toInt()
+ versionName = libs.versions.versionName.get()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
@@ -21,6 +26,13 @@ android {
}
buildTypes {
+ debug {
+ buildConfigField(
+ "String",
+ "OPEN_BASE_URL",
+ gradleLocalProperties(rootDir, providers).getProperty("open.base.url")
+ )
+ }
release {
isMinifyEnabled = false
proguardFiles(
@@ -34,36 +46,59 @@ android {
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
- jvmTarget = "1.8"
+ jvmTarget = libs.versions.jvmTarget.get()
}
buildFeatures {
compose = true
+ buildConfig = true
}
composeOptions {
- kotlinCompilerExtensionVersion = "1.5.1"
+ kotlinCompilerExtensionVersion = libs.versions.kotlinCompilerExtensionVersion.get()
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
+ excludes += "/META-INF/gradle/incremental.annotation.processors"
}
}
}
dependencies {
+ implementation(project(":core"))
+ implementation(project(":data"))
+ implementation(project(":domain"))
+ implementation(project(":feature"))
+
+ implementation(libs.kotlinx.serialization.json)
+ implementation(libs.coroutines.android)
+ implementation(libs.kotlin)
implementation(libs.androidx.core.ktx)
- implementation(libs.androidx.lifecycle.runtime.ktx)
- implementation(libs.androidx.activity.compose)
- implementation(platform(libs.androidx.compose.bom))
- implementation(libs.androidx.ui)
- implementation(libs.androidx.ui.graphics)
- implementation(libs.androidx.ui.tooling.preview)
- implementation(libs.androidx.material3)
+ implementation(libs.androidx.appcompat)
+ implementation(libs.hilt.android)
+ ksp(libs.hilt.android.compiler)
+ ksp(libs.hilt.manager)
+ implementation(libs.androidx.workManager)
+ implementation(libs.androidx.hiltWorkManager)
+
+ implementation(libs.hilt.compiler)
+ implementation(libs.androidx.hiltWorkManagerCompiler)
+
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
+
+ implementation(platform(libs.okhttp.bom))
+ implementation(libs.okhttp)
+ implementation(libs.okhttp.logging)
+ implementation(libs.retrofit.core)
+ implementation(libs.retrofit.kotlin.serialization)
+ implementation(libs.retrofit2.kotlinx.serialization.converter)
+ implementation(libs.timber)
+ implementation(libs.ossLicense)
+
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
-}
\ No newline at end of file
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 776ed2b80..301aea41d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,7 +2,11 @@
+
+
+
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/java/com/terning/point/MainActivity.kt b/app/src/main/java/com/terning/point/MainActivity.kt
deleted file mode 100644
index 044df2a91..000000000
--- a/app/src/main/java/com/terning/point/MainActivity.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-package com.terning.point
-
-import android.os.Bundle
-import androidx.activity.ComponentActivity
-import androidx.activity.compose.setContent
-import androidx.activity.enableEdgeToEdge
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.Scaffold
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.tooling.preview.Preview
-import com.terning.point.ui.theme.TerningAndroidTheme
-
-class MainActivity : ComponentActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- enableEdgeToEdge()
- setContent {
- TerningAndroidTheme {
- Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
- Greeting(
- name = "Android",
- modifier = Modifier.padding(innerPadding)
- )
- }
- }
- }
- }
-}
-
-@Composable
-fun Greeting(name: String, modifier: Modifier = Modifier) {
- Text(
- text = "Hello $name!",
- modifier = modifier
- )
-}
-
-@Preview(showBackground = true)
-@Composable
-fun GreetingPreview() {
- TerningAndroidTheme {
- Greeting("Android")
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/terning/point/MyApp.kt b/app/src/main/java/com/terning/point/MyApp.kt
new file mode 100644
index 000000000..98ac3bc11
--- /dev/null
+++ b/app/src/main/java/com/terning/point/MyApp.kt
@@ -0,0 +1,25 @@
+package com.terning.point
+
+import android.app.Application
+import androidx.appcompat.app.AppCompatDelegate
+import dagger.hilt.android.HiltAndroidApp
+import timber.log.Timber
+
+@HiltAndroidApp
+class MyApp : Application() {
+
+ override fun onCreate() {
+ super.onCreate()
+
+ initTimber()
+ setDayMode()
+ }
+
+ private fun initTimber() {
+ if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
+ }
+
+ private fun setDayMode() {
+ AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/terning/point/di/DataSourceModule.kt b/app/src/main/java/com/terning/point/di/DataSourceModule.kt
new file mode 100644
index 000000000..c1541df6a
--- /dev/null
+++ b/app/src/main/java/com/terning/point/di/DataSourceModule.kt
@@ -0,0 +1,18 @@
+package com.terning.point.di
+
+import com.terning.data.datasource.MockDataSource
+import com.terning.data.datasourceimpl.MockDataSourceImpl
+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 DataSourceModule {
+
+ @Binds
+ @Singleton
+ abstract fun bindMockDataSource(mockDataSourceImpl: MockDataSourceImpl): MockDataSource
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/terning/point/di/RepositoryModule.kt b/app/src/main/java/com/terning/point/di/RepositoryModule.kt
new file mode 100644
index 000000000..9de02a07a
--- /dev/null
+++ b/app/src/main/java/com/terning/point/di/RepositoryModule.kt
@@ -0,0 +1,18 @@
+package com.terning.point.di
+
+import com.terning.data.repositoryimpl.MockRepositoryImpl
+import com.terning.domain.repository.MockRepository
+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 bindMockRepository(mockRepositoryImpl: MockRepositoryImpl): MockRepository
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/terning/point/di/RetrofitModule.kt b/app/src/main/java/com/terning/point/di/RetrofitModule.kt
new file mode 100644
index 000000000..8d086aeb3
--- /dev/null
+++ b/app/src/main/java/com/terning/point/di/RetrofitModule.kt
@@ -0,0 +1,78 @@
+package com.terning.point.di
+
+import com.terning.core.extension.isJsonArray
+import com.terning.core.extension.isJsonObject
+import com.terning.point.BuildConfig
+import com.terning.point.di.qualifier.OPEN
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import kotlinx.serialization.json.Json
+import okhttp3.Interceptor
+import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.OkHttpClient
+import okhttp3.logging.HttpLoggingInterceptor
+import org.json.JSONObject
+import retrofit2.Converter
+import retrofit2.Retrofit
+import retrofit2.converter.kotlinx.serialization.asConverterFactory
+import timber.log.Timber
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object RetrofitModule {
+ private const val APPLICATION_JSON = "application/json"
+
+ @Provides
+ @Singleton
+ fun provideJson(): Json = Json {
+ ignoreUnknownKeys = true
+ prettyPrint = true
+ }
+
+ @Provides
+ @Singleton
+ fun provideJsonConverter(json: Json): Converter.Factory =
+ json.asConverterFactory(APPLICATION_JSON.toMediaType())
+
+ @Provides
+ @Singleton
+ fun provideHttpLoggingInterceptor(): Interceptor = HttpLoggingInterceptor { message ->
+ when {
+ message.isJsonObject() ->
+ Timber.tag("okhttp").d(JSONObject(message).toString(4))
+
+ message.isJsonArray() ->
+ Timber.tag("okhttp").d(JSONObject(message).toString(4))
+
+ else -> {
+ Timber.tag("okhttp").d("CONNECTION INFO -> $message")
+ }
+ }
+ }.apply {
+ level = HttpLoggingInterceptor.Level.BODY
+ }
+
+ @Provides
+ @Singleton
+ fun provideOkHttpClient(
+ loggingInterceptor: Interceptor
+ ): OkHttpClient = OkHttpClient.Builder()
+ .addInterceptor(loggingInterceptor)
+ .build()
+
+ @Provides
+ @Singleton
+ @OPEN
+ fun provideOpenRetrofit(
+ client: OkHttpClient,
+ factory: Converter.Factory
+ ): Retrofit = Retrofit.Builder()
+ .baseUrl(BuildConfig.OPEN_BASE_URL)
+ .addConverterFactory(factory)
+ .client(client)
+ .build()
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/terning/point/di/ServiceModule.kt b/app/src/main/java/com/terning/point/di/ServiceModule.kt
new file mode 100644
index 000000000..317c02f92
--- /dev/null
+++ b/app/src/main/java/com/terning/point/di/ServiceModule.kt
@@ -0,0 +1,21 @@
+package com.terning.point.di
+
+import com.terning.data.service.MockService
+import com.terning.point.di.qualifier.OPEN
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import retrofit2.Retrofit
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object ServiceModule {
+
+ @Provides
+ @Singleton
+ fun provideMockService(@OPEN retrofit: Retrofit): MockService =
+ retrofit.create(MockService::class.java)
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/terning/point/di/qualifier/RetrofitQualifier.kt b/app/src/main/java/com/terning/point/di/qualifier/RetrofitQualifier.kt
new file mode 100644
index 000000000..fe7176d0b
--- /dev/null
+++ b/app/src/main/java/com/terning/point/di/qualifier/RetrofitQualifier.kt
@@ -0,0 +1,8 @@
+package com.terning.point.di.qualifier
+
+
+import javax.inject.Qualifier
+
+@Qualifier
+@Retention(AnnotationRetention.BINARY)
+annotation class OPEN
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
deleted file mode 100644
index f8c6127d3..000000000
--- a/app/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
- #FFBB86FC
- #FF6200EE
- #FF3700B3
- #FF03DAC5
- #FF018786
- #FF000000
- #FFFFFFFF
-
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
deleted file mode 100644
index 97015239f..000000000
--- a/app/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
- Terning-Android
-
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index f74b04bf2..c5adb7afd 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,5 +1,23 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.android.application) apply false
- alias(libs.plugins.jetbrains.kotlin.android) apply false
+ alias(libs.plugins.kotlin.android) apply false
+ alias(libs.plugins.kotlin.jvm) apply false
+ alias(libs.plugins.android.library) apply false
+ alias(libs.plugins.kotlin.serialization) apply false
+ alias(libs.plugins.hilt) apply false
+ alias(libs.plugins.android.test) apply false
+ alias(libs.plugins.ksp) apply false
+ alias(libs.plugins.kotlin.parcelize) apply false
+}
+
+buildscript {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+apply {
+ from("gradle/projectDependencyGraph.gradle")
}
\ No newline at end of file
diff --git a/core/.gitignore b/core/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/core/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core/build.gradle.kts b/core/build.gradle.kts
new file mode 100644
index 000000000..74813a68f
--- /dev/null
+++ b/core/build.gradle.kts
@@ -0,0 +1,58 @@
+plugins {
+ alias(libs.plugins.android.library)
+ alias(libs.plugins.kotlin.android)
+}
+
+android {
+ namespace = "com.terning.core"
+ compileSdk = libs.versions.compileSdk.get().toInt()
+
+ defaultConfig {
+ minSdk = libs.versions.minSdk.get().toInt()
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+
+ }
+ kotlinOptions {
+ jvmTarget = libs.versions.jvmTarget.get()
+ }
+ buildFeatures {
+ compose = true
+ buildConfig = true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion = libs.versions.kotlinCompilerExtensionVersion.get()
+ }
+}
+
+dependencies {
+ implementation(libs.kotlin)
+
+ implementation(libs.androidx.lifecycle.runtime.ktx)
+ implementation(libs.hilt.android)
+
+ implementation(libs.material)
+ implementation(libs.androidx.ui)
+ implementation(libs.androidx.ui.graphics)
+ implementation(libs.androidx.foundation.android)
+ implementation(libs.androidx.material3.android)
+
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.junit)
+ androidTestImplementation(libs.androidx.espresso.core)
+}
\ No newline at end of file
diff --git a/core/consumer-rules.pro b/core/consumer-rules.pro
new file mode 100644
index 000000000..e69de29bb
diff --git a/core/proguard-rules.pro b/core/proguard-rules.pro
new file mode 100644
index 000000000..481bb4348
--- /dev/null
+++ b/core/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/core/src/androidTest/java/com/terning/core/ExampleInstrumentedTest.kt b/core/src/androidTest/java/com/terning/core/ExampleInstrumentedTest.kt
new file mode 100644
index 000000000..62607fa89
--- /dev/null
+++ b/core/src/androidTest/java/com/terning/core/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.terning.core
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.terning.core.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/AndroidManifest.xml b/core/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..a5918e68a
--- /dev/null
+++ b/core/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/core/src/main/java/com/terning/core/extension/ContextExt.kt b/core/src/main/java/com/terning/core/extension/ContextExt.kt
new file mode 100644
index 000000000..7deeaaec9
--- /dev/null
+++ b/core/src/main/java/com/terning/core/extension/ContextExt.kt
@@ -0,0 +1,16 @@
+package com.terning.core.extension
+
+import android.app.Activity
+import android.content.Context
+import android.view.View
+import android.view.inputmethod.InputMethodManager
+import android.widget.Toast
+import androidx.annotation.StringRes
+
+fun Context.toast(@StringRes message: Int) {
+ Toast.makeText(this, getString(message), Toast.LENGTH_SHORT).show()
+}
+
+fun Context.longToast(@StringRes message: Int) {
+ Toast.makeText(this, getString(message), Toast.LENGTH_SHORT).show()
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/terning/core/extension/Modifier.kt b/core/src/main/java/com/terning/core/extension/Modifier.kt
new file mode 100644
index 000000000..57cca1101
--- /dev/null
+++ b/core/src/main/java/com/terning/core/extension/Modifier.kt
@@ -0,0 +1,27 @@
+package com.terning.core.extension
+
+import android.annotation.SuppressLint
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.focus.FocusManager
+import androidx.compose.ui.input.pointer.pointerInput
+
+@SuppressLint("ModifierFactoryUnreferencedReceiver")
+inline fun Modifier.noRippleClickable(crossinline onClick: () -> Unit): Modifier = composed {
+ clickable(indication = null,
+ interactionSource = remember { MutableInteractionSource() }) {
+ onClick()
+ }
+}
+
+fun Modifier.addFocusCleaner(focusManager: FocusManager): Modifier {
+ return this.pointerInput(Unit) {
+ detectTapGestures(onTap = {
+ focusManager.clearFocus()
+ })
+ }
+}
diff --git a/core/src/main/java/com/terning/core/extension/Retrofit.kt b/core/src/main/java/com/terning/core/extension/Retrofit.kt
new file mode 100644
index 000000000..27dd9c5ca
--- /dev/null
+++ b/core/src/main/java/com/terning/core/extension/Retrofit.kt
@@ -0,0 +1,6 @@
+package com.terning.core.extension
+
+
+fun String?.isJsonObject(): Boolean = this?.startsWith("{") == true && this.endsWith("}")
+
+fun String?.isJsonArray(): Boolean = this?.startsWith("[") == true && this.endsWith("]")
\ No newline at end of file
diff --git a/core/src/main/java/com/terning/core/navigation/MainTabRoute.kt b/core/src/main/java/com/terning/core/navigation/MainTabRoute.kt
new file mode 100644
index 000000000..6e85354da
--- /dev/null
+++ b/core/src/main/java/com/terning/core/navigation/MainTabRoute.kt
@@ -0,0 +1,3 @@
+package com.terning.core.navigation
+
+interface MainTabRoute : Route
\ No newline at end of file
diff --git a/core/src/main/java/com/terning/core/navigation/Route.kt b/core/src/main/java/com/terning/core/navigation/Route.kt
new file mode 100644
index 000000000..fe968eb16
--- /dev/null
+++ b/core/src/main/java/com/terning/core/navigation/Route.kt
@@ -0,0 +1,3 @@
+package com.terning.core.navigation
+
+interface Route
\ No newline at end of file
diff --git a/core/src/main/java/com/terning/core/state/UiState.kt b/core/src/main/java/com/terning/core/state/UiState.kt
new file mode 100644
index 000000000..0c78263a2
--- /dev/null
+++ b/core/src/main/java/com/terning/core/state/UiState.kt
@@ -0,0 +1,15 @@
+package com.terning.core.state
+
+sealed interface UiState {
+ data object Empty : UiState
+
+ data object Loading : UiState
+
+ data class Success(
+ val data: T,
+ ) : UiState
+
+ data class Failure(
+ val msg: String,
+ ) : UiState
+}
diff --git a/app/src/main/java/com/terning/point/ui/theme/Color.kt b/core/src/main/java/com/terning/core/ui/theme/Color.kt
similarity index 87%
rename from app/src/main/java/com/terning/point/ui/theme/Color.kt
rename to core/src/main/java/com/terning/core/ui/theme/Color.kt
index 8b4754781..847dcc821 100644
--- a/app/src/main/java/com/terning/point/ui/theme/Color.kt
+++ b/core/src/main/java/com/terning/core/ui/theme/Color.kt
@@ -1,4 +1,4 @@
-package com.terning.point.ui.theme
+package com.terning.core.ui.theme
import androidx.compose.ui.graphics.Color
diff --git a/app/src/main/java/com/terning/point/ui/theme/Theme.kt b/core/src/main/java/com/terning/core/ui/theme/Theme.kt
similarity index 96%
rename from app/src/main/java/com/terning/point/ui/theme/Theme.kt
rename to core/src/main/java/com/terning/core/ui/theme/Theme.kt
index 600ba77a1..f826a9f05 100644
--- a/app/src/main/java/com/terning/point/ui/theme/Theme.kt
+++ b/core/src/main/java/com/terning/core/ui/theme/Theme.kt
@@ -1,6 +1,5 @@
-package com.terning.point.ui.theme
+package com.terning.core.ui.theme
-import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
diff --git a/app/src/main/java/com/terning/point/ui/theme/Type.kt b/core/src/main/java/com/terning/core/ui/theme/Type.kt
similarity index 96%
rename from app/src/main/java/com/terning/point/ui/theme/Type.kt
rename to core/src/main/java/com/terning/core/ui/theme/Type.kt
index afc585a0e..bb904414b 100644
--- a/app/src/main/java/com/terning/point/ui/theme/Type.kt
+++ b/core/src/main/java/com/terning/core/ui/theme/Type.kt
@@ -1,4 +1,4 @@
-package com.terning.point.ui.theme
+package com.terning.core.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
diff --git a/core/src/test/java/com/terning/core/ExampleUnitTest.kt b/core/src/test/java/com/terning/core/ExampleUnitTest.kt
new file mode 100644
index 000000000..5881a2b99
--- /dev/null
+++ b/core/src/test/java/com/terning/core/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.terning.core
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/data/.gitignore b/data/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/data/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/data/build.gradle.kts b/data/build.gradle.kts
new file mode 100644
index 000000000..33eedaaac
--- /dev/null
+++ b/data/build.gradle.kts
@@ -0,0 +1,60 @@
+plugins {
+ alias(libs.plugins.android.library)
+ alias(libs.plugins.kotlin.android)
+ alias(libs.plugins.kotlin.serialization)
+}
+
+android {
+ namespace = "com.terning.data"
+ compileSdk = libs.versions.compileSdk.get().toInt()
+
+ defaultConfig {
+ minSdk = libs.versions.minSdk.get().toInt()
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = libs.versions.jvmTarget.get()
+ }
+ buildFeatures{
+ buildConfig = true
+ }
+}
+
+dependencies {
+ implementation(project(":domain"))
+
+ implementation(libs.hilt.android)
+ implementation(libs.androidx.security)
+ implementation(libs.androidx.core.ktx)
+
+ implementation(libs.kotlin)
+ implementation(libs.kotlinx.serialization.json.v151)
+ implementation(libs.coroutines.android)
+
+ implementation(libs.retrofit.core)
+ implementation(libs.okhttp)
+ implementation(platform(libs.okhttp.bom))
+ implementation(libs.okhttp.logging)
+ implementation (libs.retrofit2.kotlinx.serialization.converter.v080)
+ implementation(libs.timber)
+
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.junit)
+ androidTestImplementation(libs.androidx.espresso.core)
+}
\ No newline at end of file
diff --git a/data/consumer-rules.pro b/data/consumer-rules.pro
new file mode 100644
index 000000000..e69de29bb
diff --git a/data/proguard-rules.pro b/data/proguard-rules.pro
new file mode 100644
index 000000000..481bb4348
--- /dev/null
+++ b/data/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/data/src/androidTest/java/com/terning/data/ExampleInstrumentedTest.kt b/data/src/androidTest/java/com/terning/data/ExampleInstrumentedTest.kt
new file mode 100644
index 000000000..e97b860eb
--- /dev/null
+++ b/data/src/androidTest/java/com/terning/data/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.terning.data
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.terning.data.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/data/src/main/AndroidManifest.xml b/data/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..a5918e68a
--- /dev/null
+++ b/data/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/data/src/main/java/com/terning/data/datasource/MockDataSource.kt b/data/src/main/java/com/terning/data/datasource/MockDataSource.kt
new file mode 100644
index 000000000..468b21bca
--- /dev/null
+++ b/data/src/main/java/com/terning/data/datasource/MockDataSource.kt
@@ -0,0 +1,7 @@
+package com.terning.data.datasource
+
+import com.terning.data.dto.response.MockResponseDto
+
+interface MockDataSource {
+ suspend fun getMock(page: Int): MockResponseDto
+}
\ No newline at end of file
diff --git a/data/src/main/java/com/terning/data/datasourceimpl/MockDataSourceImpl.kt b/data/src/main/java/com/terning/data/datasourceimpl/MockDataSourceImpl.kt
new file mode 100644
index 000000000..12aa9731f
--- /dev/null
+++ b/data/src/main/java/com/terning/data/datasourceimpl/MockDataSourceImpl.kt
@@ -0,0 +1,13 @@
+package com.terning.data.datasourceimpl
+
+import com.terning.data.datasource.MockDataSource
+import com.terning.data.dto.response.MockResponseDto
+import com.terning.data.service.MockService
+import javax.inject.Inject
+
+class MockDataSourceImpl @Inject constructor(
+ private val mockService: MockService
+) : MockDataSource {
+ override suspend fun getMock(page: Int): MockResponseDto =
+ mockService.getMockListFromServer(page)
+}
\ No newline at end of file
diff --git a/data/src/main/java/com/terning/data/dto/BaseResponse.kt b/data/src/main/java/com/terning/data/dto/BaseResponse.kt
new file mode 100644
index 000000000..0de489ff1
--- /dev/null
+++ b/data/src/main/java/com/terning/data/dto/BaseResponse.kt
@@ -0,0 +1,16 @@
+package com.terning.data.dto
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class BaseResponse(
+ @SerialName("status")
+ val status: Int,
+ @SerialName("code")
+ val code: String,
+ @SerialName("message")
+ val message: String,
+ @SerialName("data")
+ val data: T,
+)
diff --git a/data/src/main/java/com/terning/data/dto/NonDataBaseResponse.kt b/data/src/main/java/com/terning/data/dto/NonDataBaseResponse.kt
new file mode 100644
index 000000000..34db1f722
--- /dev/null
+++ b/data/src/main/java/com/terning/data/dto/NonDataBaseResponse.kt
@@ -0,0 +1,14 @@
+package com.terning.data.dto
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class NonDataBaseResponse(
+ @SerialName("status")
+ val status: Int,
+ @SerialName("code")
+ val code: String,
+ @SerialName("message")
+ val message: String,
+)
diff --git a/data/src/main/java/com/terning/data/dto/response/MockResponseDto.kt b/data/src/main/java/com/terning/data/dto/response/MockResponseDto.kt
new file mode 100644
index 000000000..6163bd8bd
--- /dev/null
+++ b/data/src/main/java/com/terning/data/dto/response/MockResponseDto.kt
@@ -0,0 +1,53 @@
+package com.terning.data.dto.response
+
+import com.terning.domain.entity.response.MockResponseModel
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class MockResponseDto(
+ @SerialName("page")
+ val page: Int,
+ @SerialName("per_page")
+ val per_page: Int,
+ @SerialName("total")
+ val total: Int,
+ @SerialName("total_pages")
+ val total_pages: Int,
+ @SerialName("data")
+ val data: List,
+ @SerialName("support")
+ val support: Support,
+) {
+ @Serializable
+ data class MockData(
+ @SerialName("id")
+ val id: Int,
+ @SerialName("email")
+ val email: String,
+ @SerialName("first_name")
+ val first_name: String,
+ @SerialName("last_name")
+ val last_name: String,
+ @SerialName("avatar")
+ val avatar: String
+ )
+
+ @Serializable
+ data class Support(
+ @SerialName("url")
+ val url: String,
+ @SerialName("text")
+ val text: String,
+ )
+
+ fun toMockEntity(): List = data.map {
+ MockResponseModel(
+ avatar = it.avatar,
+ email = it.email,
+ firstName = it.first_name,
+ lastName = it.last_name
+ )
+ }
+
+}
\ No newline at end of file
diff --git a/data/src/main/java/com/terning/data/repositoryimpl/MockRepositoryImpl.kt b/data/src/main/java/com/terning/data/repositoryimpl/MockRepositoryImpl.kt
new file mode 100644
index 000000000..d8cd7b4fa
--- /dev/null
+++ b/data/src/main/java/com/terning/data/repositoryimpl/MockRepositoryImpl.kt
@@ -0,0 +1,14 @@
+package com.terning.data.repositoryimpl
+
+import com.terning.data.datasource.MockDataSource
+import com.terning.domain.entity.response.MockResponseModel
+import com.terning.domain.repository.MockRepository
+import javax.inject.Inject
+
+class MockRepositoryImpl @Inject constructor(private val mockDataSource: MockDataSource) :
+ MockRepository {
+ override suspend fun getMockList(page: Int): Result> =
+ runCatching {
+ mockDataSource.getMock(page).toMockEntity()
+ }
+}
\ No newline at end of file
diff --git a/data/src/main/java/com/terning/data/service/MockService.kt b/data/src/main/java/com/terning/data/service/MockService.kt
new file mode 100644
index 000000000..fddfe0b4b
--- /dev/null
+++ b/data/src/main/java/com/terning/data/service/MockService.kt
@@ -0,0 +1,12 @@
+package com.terning.data.service
+
+import com.terning.data.dto.response.MockResponseDto
+import retrofit2.http.GET
+import retrofit2.http.Query
+
+interface MockService {
+ @GET("api/users")
+ suspend fun getMockListFromServer(
+ @Query("page") page: Int,
+ ): MockResponseDto
+}
\ No newline at end of file
diff --git a/data/src/test/java/com/terning/data/ExampleUnitTest.kt b/data/src/test/java/com/terning/data/ExampleUnitTest.kt
new file mode 100644
index 000000000..12399759e
--- /dev/null
+++ b/data/src/test/java/com/terning/data/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.terning.data
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/domain/.gitignore b/domain/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/domain/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts
new file mode 100644
index 000000000..49fc1a9d2
--- /dev/null
+++ b/domain/build.gradle.kts
@@ -0,0 +1,15 @@
+plugins {
+ id("java-library")
+ alias(libs.plugins.ksp)
+ alias(libs.plugins.kotlin.jvm)
+}
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+}
+
+dependencies{
+ implementation(libs.kotlin)
+ implementation(libs.coroutines.android)
+}
\ No newline at end of file
diff --git a/domain/src/main/java/com/terning/domain/entity/response/MockResponseModel.kt b/domain/src/main/java/com/terning/domain/entity/response/MockResponseModel.kt
new file mode 100644
index 000000000..5b272ff6b
--- /dev/null
+++ b/domain/src/main/java/com/terning/domain/entity/response/MockResponseModel.kt
@@ -0,0 +1,8 @@
+package com.terning.domain.entity.response
+
+data class MockResponseModel(
+ val avatar: String,
+ val email: String,
+ val firstName: String,
+ val lastName: String,
+)
\ No newline at end of file
diff --git a/domain/src/main/java/com/terning/domain/repository/MockRepository.kt b/domain/src/main/java/com/terning/domain/repository/MockRepository.kt
new file mode 100644
index 000000000..ca5e06da8
--- /dev/null
+++ b/domain/src/main/java/com/terning/domain/repository/MockRepository.kt
@@ -0,0 +1,7 @@
+package com.terning.domain.repository
+
+import com.terning.domain.entity.response.MockResponseModel
+
+interface MockRepository {
+ suspend fun getMockList(page: Int): Result>
+}
\ No newline at end of file
diff --git a/feature/.gitignore b/feature/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/feature/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/feature/build.gradle.kts b/feature/build.gradle.kts
new file mode 100644
index 000000000..d82320d54
--- /dev/null
+++ b/feature/build.gradle.kts
@@ -0,0 +1,88 @@
+plugins {
+ alias(libs.plugins.android.library)
+ alias(libs.plugins.kotlin.android)
+ alias(libs.plugins.hilt)
+ alias(libs.plugins.ksp)
+ alias(libs.plugins.kotlin.parcelize)
+ alias(libs.plugins.kotlin.serialization)
+}
+
+android {
+ namespace = "com.terning.feature"
+ compileSdk = libs.versions.compileSdk.get().toInt()
+
+ defaultConfig {
+ minSdk = libs.versions.minSdk.get().toInt()
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = libs.versions.jvmTarget.get()
+ }
+ buildFeatures {
+ compose = true
+ buildConfig = true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion = libs.versions.kotlinCompilerExtensionVersion.get()
+ }
+ packaging {
+ resources {
+ excludes += "/META-INF/{AL2.0,LGPL2.1}"
+ excludes += "/META-INF/gradle/incremental.annotation.processors"
+ }
+ }
+}
+
+dependencies {
+ implementation(project(":core"))
+ implementation(project(":domain"))
+
+ implementation(libs.kotlin)
+ implementation(libs.coroutines.android)
+ implementation(libs.kotlinx.serialization.json)
+
+ implementation(libs.androidx.core.ktx)
+ implementation(libs.androidx.appcompat)
+ implementation(libs.hilt.android)
+ implementation(libs.hilt.navigation.compose)
+ implementation(libs.androidx.navigation.compose)
+ implementation(libs.androidx.lifecycle.runtime.ktx)
+ implementation(libs.androidx.lifecycle.viewModelCompose)
+ implementation(libs.androidx.workManager)
+
+ implementation(libs.hilt.compiler)
+ implementation(libs.androidx.lifecycle.runtime.compose.android)
+ ksp(libs.hilt.android.compiler)
+ ksp(libs.hilt.manager)
+
+ implementation(libs.material)
+ implementation(libs.androidx.material3.android)
+ implementation(libs.androidx.ui.tooling.preview)
+ implementation(libs.androidx.ui.graphics)
+
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.junit)
+ androidTestImplementation(libs.androidx.espresso.core)
+
+ implementation(libs.compose.coil)
+ implementation(libs.timber)
+ implementation(libs.ossLicense)
+ implementation(libs.lottie)
+
+}
\ No newline at end of file
diff --git a/feature/consumer-rules.pro b/feature/consumer-rules.pro
new file mode 100644
index 000000000..e69de29bb
diff --git a/feature/proguard-rules.pro b/feature/proguard-rules.pro
new file mode 100644
index 000000000..481bb4348
--- /dev/null
+++ b/feature/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/feature/src/androidTest/java/com/terning/feature/ExampleInstrumentedTest.kt b/feature/src/androidTest/java/com/terning/feature/ExampleInstrumentedTest.kt
new file mode 100644
index 000000000..77b9600ed
--- /dev/null
+++ b/feature/src/androidTest/java/com/terning/feature/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.terning.feature
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.terning.feature.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/feature/src/main/AndroidManifest.xml b/feature/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..f2df3a230
--- /dev/null
+++ b/feature/src/main/AndroidManifest.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/feature/src/main/java/com/terning/feature/first/FirstRoute.kt b/feature/src/main/java/com/terning/feature/first/FirstRoute.kt
new file mode 100644
index 000000000..cb68304e1
--- /dev/null
+++ b/feature/src/main/java/com/terning/feature/first/FirstRoute.kt
@@ -0,0 +1,19 @@
+package com.terning.feature.first
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+
+@Composable
+fun FirstRoute() {
+ FirstScreen()
+}
+
+@Composable
+fun FirstScreen() {
+ Column(modifier = Modifier.fillMaxSize()) {
+ Text(text = "This is FirstScreen")
+ }
+}
\ No newline at end of file
diff --git a/feature/src/main/java/com/terning/feature/first/navigation/FirstNavigation.kt b/feature/src/main/java/com/terning/feature/first/navigation/FirstNavigation.kt
new file mode 100644
index 000000000..3ef921901
--- /dev/null
+++ b/feature/src/main/java/com/terning/feature/first/navigation/FirstNavigation.kt
@@ -0,0 +1,25 @@
+package com.terning.feature.first.navigation
+
+import androidx.navigation.NavController
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavOptions
+import androidx.navigation.compose.composable
+import com.terning.core.navigation.MainTabRoute
+import com.terning.feature.first.FirstRoute
+import kotlinx.serialization.Serializable
+
+fun NavController.navigateFirst(navOptions: NavOptions? = null) {
+ navigate(
+ route = First,
+ navOptions = navOptions
+ )
+}
+
+fun NavGraphBuilder.firstNavGraph() {
+ composable {
+ FirstRoute()
+ }
+}
+
+@Serializable
+data object First : MainTabRoute
\ No newline at end of file
diff --git a/feature/src/main/java/com/terning/feature/main/MainActivity.kt b/feature/src/main/java/com/terning/feature/main/MainActivity.kt
new file mode 100644
index 000000000..655098740
--- /dev/null
+++ b/feature/src/main/java/com/terning/feature/main/MainActivity.kt
@@ -0,0 +1,22 @@
+package com.terning.feature.main
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
+import com.terning.core.ui.theme.TerningAndroidTheme
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class MainActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enableEdgeToEdge()
+ setContent {
+ val navigator: MainNavigator = rememberMainNavigator()
+ TerningAndroidTheme {
+ MainScreen(navigator)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/feature/src/main/java/com/terning/feature/main/MainNavigator.kt b/feature/src/main/java/com/terning/feature/main/MainNavigator.kt
new file mode 100644
index 000000000..4ca92ceec
--- /dev/null
+++ b/feature/src/main/java/com/terning/feature/main/MainNavigator.kt
@@ -0,0 +1,68 @@
+package com.terning.feature.main
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.navigation.NavDestination
+import androidx.navigation.NavDestination.Companion.hasRoute
+import androidx.navigation.NavGraph.Companion.findStartDestination
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.currentBackStackEntryAsState
+import androidx.navigation.compose.rememberNavController
+import androidx.navigation.navOptions
+import com.terning.feature.first.navigation.First
+import com.terning.feature.first.navigation.navigateFirst
+import com.terning.feature.mock.navigation.navigateMock
+
+class MainNavigator(
+ val navController: NavHostController
+) {
+ private val currentDestination: NavDestination?
+ @Composable get() = navController
+ .currentBackStackEntryAsState().value?.destination
+
+ val startDestination = First
+
+ val currentTab: MainTab?
+ @Composable get() = MainTab.find { tab ->
+ currentDestination?.hasRoute(tab::class) == true
+ }
+
+ fun navigate(tab: MainTab) {
+ val navOptions = navOptions {
+ popUpTo(navController.graph.findStartDestination().id) {
+ saveState = true
+ }
+ launchSingleTop = true
+ restoreState = true
+ }
+
+ when (tab) {
+ MainTab.FIRST -> navController.navigateFirst(navOptions)
+ MainTab.MOCK -> navController.navigateMock(navOptions)
+ }
+ }
+
+ fun navigateToFirst() {
+ navController.navigateFirst()
+ }
+
+ fun navigateToMock() {
+ navController.navigateMock()
+ }
+
+ private fun navigateUp() {
+ navController.navigateUp()
+ }
+
+ @Composable
+ fun showBottomBar() = MainTab.contains {
+ currentDestination?.hasRoute(it::class) == true
+ }
+}
+
+@Composable
+fun rememberMainNavigator(
+ navController: NavHostController = rememberNavController(),
+): MainNavigator = remember(navController) {
+ MainNavigator(navController)
+}
\ No newline at end of file
diff --git a/feature/src/main/java/com/terning/feature/main/MainScreen.kt b/feature/src/main/java/com/terning/feature/main/MainScreen.kt
new file mode 100644
index 000000000..29167b458
--- /dev/null
+++ b/feature/src/main/java/com/terning/feature/main/MainScreen.kt
@@ -0,0 +1,85 @@
+package com.terning.feature.main
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Icon
+import androidx.compose.material3.NavigationBar
+import androidx.compose.material3.NavigationBarItem
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.navigation.compose.NavHost
+import com.terning.feature.first.navigation.firstNavGraph
+import com.terning.feature.mock.navigation.mockNavGraph
+
+@Composable
+fun MainScreen(
+ navigator: MainNavigator = rememberMainNavigator(),
+) {
+ Scaffold(
+ bottomBar = {
+ MainBottomBar(
+ isVisible = navigator.showBottomBar(),
+ tabs = MainTab.entries.toList(),
+ currentTab = navigator.currentTab,
+ onTabSelected = navigator::navigate
+ )
+ },
+ ) { innerPadding ->
+ Column(
+ modifier = Modifier
+ .padding(innerPadding),
+ verticalArrangement = Arrangement.spacedBy(16.dp),
+ ) {
+ NavHost(
+ navController = navigator.navController,
+ startDestination = navigator.startDestination
+ ) {
+ firstNavGraph()
+ mockNavGraph()
+ }
+ }
+ }
+}
+
+@Composable
+private fun MainBottomBar(
+ isVisible: Boolean,
+ tabs: List,
+ currentTab: MainTab?,
+ onTabSelected: (MainTab) -> Unit,
+) {
+ AnimatedVisibility(
+ visible = isVisible,
+ ) {
+ NavigationBar {
+ tabs.forEach { itemType ->
+ NavigationBarItem(
+ selected = currentTab == itemType,
+ onClick = {
+ onTabSelected(itemType)
+ },
+ icon = {
+ Icon(
+ painter = painterResource(id = (itemType.icon)),
+ contentDescription = stringResource(id = itemType.contentDescription)
+ )
+ },
+ label = {
+ Text(
+ stringResource(id = itemType.contentDescription),
+ fontSize = 9.sp
+ )
+ },
+ )
+ }
+ }
+ }
+}
diff --git a/feature/src/main/java/com/terning/feature/main/MainTab.kt b/feature/src/main/java/com/terning/feature/main/MainTab.kt
new file mode 100644
index 000000000..cca6b166d
--- /dev/null
+++ b/feature/src/main/java/com/terning/feature/main/MainTab.kt
@@ -0,0 +1,41 @@
+package com.terning.feature.main
+
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+import androidx.compose.runtime.Composable
+import com.terning.core.navigation.MainTabRoute
+import com.terning.core.navigation.Route
+import com.terning.feature.R
+import com.terning.feature.first.navigation.First
+import com.terning.feature.mock.navigation.Mock
+
+
+enum class MainTab(
+ @DrawableRes val icon: Int,
+ @StringRes val contentDescription: Int,
+ val route: MainTabRoute,
+) {
+ FIRST(
+ icon = R.drawable.ic_launcher_foreground,
+ contentDescription = R.string.first,
+ route = First
+ ),
+ MOCK(
+ icon = R.drawable.ic_launcher_foreground,
+ contentDescription = R.string.mock,
+ route = Mock
+ );
+
+ companion object {
+ @Composable
+ fun find(predicate: @Composable (MainTabRoute) -> Boolean): MainTab? {
+ return entries.find { predicate(it.route) }
+ }
+
+ @Composable
+ fun contains(predicate: @Composable (Route) -> Boolean): Boolean {
+ return entries.map { it.route }.any { predicate(it) }
+ }
+
+ }
+}
diff --git a/feature/src/main/java/com/terning/feature/mock/MockItem.kt b/feature/src/main/java/com/terning/feature/mock/MockItem.kt
new file mode 100644
index 000000000..2cd3c9135
--- /dev/null
+++ b/feature/src/main/java/com/terning/feature/mock/MockItem.kt
@@ -0,0 +1,52 @@
+package com.terning.feature.mock
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import coil.compose.rememberImagePainter
+
+@Composable
+fun MockItem(
+ name: String,
+ profileImage: String,
+ email: String,
+ modifier: Modifier = Modifier
+) {
+ Row(
+ modifier = modifier
+ .fillMaxWidth()
+ .padding(vertical = 10.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Image(
+ painter = rememberImagePainter(data = profileImage),
+ contentDescription = "img_profile",
+ modifier = modifier
+ .size(60.dp)
+ .aspectRatio(1f),
+ )
+ Spacer(modifier = modifier.width(10.dp))
+ Text(
+ text = name,
+ fontSize = 18.sp,
+ fontWeight = FontWeight.Bold
+ )
+ Spacer(modifier = modifier.weight(1f))
+ Text(
+ text = email,
+ fontSize = 14.sp,
+ )
+ }
+}
diff --git a/feature/src/main/java/com/terning/feature/mock/MockScreen.kt b/feature/src/main/java/com/terning/feature/mock/MockScreen.kt
new file mode 100644
index 000000000..a9a958ddf
--- /dev/null
+++ b/feature/src/main/java/com/terning/feature/mock/MockScreen.kt
@@ -0,0 +1,71 @@
+package com.terning.feature.mock
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import androidx.lifecycle.flowWithLifecycle
+import com.terning.core.extension.toast
+import com.terning.core.state.UiState
+import com.terning.domain.entity.response.MockResponseModel
+
+@Composable
+fun MockRoute(
+ viewModel: MockViewModel = hiltViewModel()
+) {
+
+ val context = LocalContext.current
+ val lifecycleOwner = LocalLifecycleOwner.current
+
+ val state by viewModel.state.collectAsStateWithLifecycle(lifecycleOwner = LocalLifecycleOwner.current)
+
+ LaunchedEffect(key1 = true) {
+ viewModel.getFriendsInfo(2)
+ }
+
+ LaunchedEffect(viewModel.sideEffect, lifecycleOwner) {
+ viewModel.sideEffect.flowWithLifecycle(lifecycle = lifecycleOwner.lifecycle)
+ .collect { sideEffect ->
+ when (sideEffect) {
+ is MockSideEffect.Toast -> context.toast(sideEffect.message)
+ }
+ }
+ }
+
+ when (state.followers) {
+ is UiState.Empty -> {}
+ is UiState.Loading -> {}
+ is UiState.Failure -> {}
+ is UiState.Success -> {
+ MockScreen(mockList = (state.followers as UiState.Success>).data)
+ }
+ }
+}
+
+@Composable
+fun MockScreen(
+ mockList: List
+) {
+ LazyColumn(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(20.dp)
+ ) {
+ items(mockList) { friend ->
+ MockItem(
+ name = friend.firstName,
+ profileImage = friend.avatar,
+ email = friend.email
+ )
+ }
+ }
+}
diff --git a/feature/src/main/java/com/terning/feature/mock/MockSideEffect.kt b/feature/src/main/java/com/terning/feature/mock/MockSideEffect.kt
new file mode 100644
index 000000000..faf43e51f
--- /dev/null
+++ b/feature/src/main/java/com/terning/feature/mock/MockSideEffect.kt
@@ -0,0 +1,7 @@
+package com.terning.feature.mock
+
+import androidx.annotation.StringRes
+
+sealed class MockSideEffect {
+ data class Toast(@StringRes val message: Int) : MockSideEffect()
+}
\ No newline at end of file
diff --git a/feature/src/main/java/com/terning/feature/mock/MockState.kt b/feature/src/main/java/com/terning/feature/mock/MockState.kt
new file mode 100644
index 000000000..92e560987
--- /dev/null
+++ b/feature/src/main/java/com/terning/feature/mock/MockState.kt
@@ -0,0 +1,8 @@
+package com.terning.feature.mock
+
+import com.terning.core.state.UiState
+import com.terning.domain.entity.response.MockResponseModel
+
+data class MockState(
+ var followers: UiState> = UiState.Loading
+)
diff --git a/feature/src/main/java/com/terning/feature/mock/MockViewModel.kt b/feature/src/main/java/com/terning/feature/mock/MockViewModel.kt
new file mode 100644
index 000000000..fc95339bb
--- /dev/null
+++ b/feature/src/main/java/com/terning/feature/mock/MockViewModel.kt
@@ -0,0 +1,48 @@
+package com.terning.feature.mock
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.terning.core.state.UiState
+import com.terning.domain.entity.response.MockResponseModel
+import com.terning.domain.repository.MockRepository
+import com.terning.feature.R
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class MockViewModel @Inject constructor(private val repository: MockRepository) : ViewModel() {
+
+ private val _state: MutableStateFlow =
+ MutableStateFlow(MockState())
+ val state: StateFlow get() = _state.asStateFlow()
+
+ private val _sideEffect: MutableSharedFlow = MutableSharedFlow()
+ val sideEffect: SharedFlow get() = _sideEffect
+
+ fun getFriendsInfo(page: Int) {
+ viewModelScope.launch {
+ repository.getMockList(
+ page
+ ).onSuccess { response ->
+ val mockDataList = response.map { entity ->
+ MockResponseModel(
+ avatar = entity.avatar,
+ email = entity.email,
+ firstName = entity.firstName,
+ lastName = entity.lastName
+ )
+ }
+ _state.value = _state.value.copy(followers = UiState.Success(mockDataList))
+ _sideEffect.emit(MockSideEffect.Toast(R.string.server_success))
+ }.onFailure {
+ _sideEffect.emit(MockSideEffect.Toast(R.string.server_failure))
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/feature/src/main/java/com/terning/feature/mock/navigation/MockNavigation.kt b/feature/src/main/java/com/terning/feature/mock/navigation/MockNavigation.kt
new file mode 100644
index 000000000..0aef1ddf3
--- /dev/null
+++ b/feature/src/main/java/com/terning/feature/mock/navigation/MockNavigation.kt
@@ -0,0 +1,25 @@
+package com.terning.feature.mock.navigation
+
+import androidx.navigation.NavController
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavOptions
+import androidx.navigation.compose.composable
+import com.terning.core.navigation.MainTabRoute
+import com.terning.feature.mock.MockRoute
+import kotlinx.serialization.Serializable
+
+fun NavController.navigateMock(navOptions: NavOptions? = null) {
+ navigate(
+ route = Mock,
+ navOptions = navOptions
+ )
+}
+
+fun NavGraphBuilder.mockNavGraph() {
+ composable {
+ MockRoute()
+ }
+}
+
+@Serializable
+data object Mock : MainTabRoute
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/feature/src/main/res/drawable/ic_launcher_background.xml
similarity index 100%
rename from app/src/main/res/drawable/ic_launcher_background.xml
rename to feature/src/main/res/drawable/ic_launcher_background.xml
diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/feature/src/main/res/drawable/ic_launcher_foreground.xml
similarity index 100%
rename from app/src/main/res/drawable/ic_launcher_foreground.xml
rename to feature/src/main/res/drawable/ic_launcher_foreground.xml
diff --git a/app/src/main/res/mipmap-anydpi/ic_launcher.xml b/feature/src/main/res/mipmap-anydpi/ic_launcher.xml
similarity index 100%
rename from app/src/main/res/mipmap-anydpi/ic_launcher.xml
rename to feature/src/main/res/mipmap-anydpi/ic_launcher.xml
diff --git a/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/feature/src/main/res/mipmap-anydpi/ic_launcher_round.xml
similarity index 100%
rename from app/src/main/res/mipmap-anydpi/ic_launcher_round.xml
rename to feature/src/main/res/mipmap-anydpi/ic_launcher_round.xml
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/feature/src/main/res/mipmap-hdpi/ic_launcher.webp
similarity index 100%
rename from app/src/main/res/mipmap-hdpi/ic_launcher.webp
rename to feature/src/main/res/mipmap-hdpi/ic_launcher.webp
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/feature/src/main/res/mipmap-hdpi/ic_launcher_round.webp
similarity index 100%
rename from app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
rename to feature/src/main/res/mipmap-hdpi/ic_launcher_round.webp
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/feature/src/main/res/mipmap-mdpi/ic_launcher.webp
similarity index 100%
rename from app/src/main/res/mipmap-mdpi/ic_launcher.webp
rename to feature/src/main/res/mipmap-mdpi/ic_launcher.webp
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/feature/src/main/res/mipmap-mdpi/ic_launcher_round.webp
similarity index 100%
rename from app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
rename to feature/src/main/res/mipmap-mdpi/ic_launcher_round.webp
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/feature/src/main/res/mipmap-xhdpi/ic_launcher.webp
similarity index 100%
rename from app/src/main/res/mipmap-xhdpi/ic_launcher.webp
rename to feature/src/main/res/mipmap-xhdpi/ic_launcher.webp
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/feature/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
similarity index 100%
rename from app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
rename to feature/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/feature/src/main/res/mipmap-xxhdpi/ic_launcher.webp
similarity index 100%
rename from app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
rename to feature/src/main/res/mipmap-xxhdpi/ic_launcher.webp
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/feature/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
similarity index 100%
rename from app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
rename to feature/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/feature/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
similarity index 100%
rename from app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
rename to feature/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/feature/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
similarity index 100%
rename from app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
rename to feature/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
diff --git a/feature/src/main/res/values/strings.xml b/feature/src/main/res/values/strings.xml
new file mode 100644
index 000000000..51ab71090
--- /dev/null
+++ b/feature/src/main/res/values/strings.xml
@@ -0,0 +1,11 @@
+
+
+ Terning-Android
+
+ 서버통신 성공
+ 서버통신 실패
+
+ first
+ mock
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/feature/src/main/res/values/themes.xml
similarity index 99%
rename from app/src/main/res/values/themes.xml
rename to feature/src/main/res/values/themes.xml
index 7462ea656..65770c519 100644
--- a/app/src/main/res/values/themes.xml
+++ b/feature/src/main/res/values/themes.xml
@@ -1,5 +1,4 @@
-
\ No newline at end of file
diff --git a/app/src/main/res/xml/backup_rules.xml b/feature/src/main/res/xml/backup_rules.xml
similarity index 100%
rename from app/src/main/res/xml/backup_rules.xml
rename to feature/src/main/res/xml/backup_rules.xml
diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/feature/src/main/res/xml/data_extraction_rules.xml
similarity index 100%
rename from app/src/main/res/xml/data_extraction_rules.xml
rename to feature/src/main/res/xml/data_extraction_rules.xml
diff --git a/feature/src/test/java/com/terning/feature/ExampleUnitTest.kt b/feature/src/test/java/com/terning/feature/ExampleUnitTest.kt
new file mode 100644
index 000000000..511c25089
--- /dev/null
+++ b/feature/src/test/java/com/terning/feature/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.terning.feature
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 2349b7448..bcce3048f 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,31 +1,161 @@
[versions]
-agp = "8.5.0"
+compileSdk = "34"
+minSdk = "28"
+targetSdk = "34"
+versionName = "1.0.0"
+versionCode = "10000"
+kotlinCompilerExtensionVersion = "1.5.0"
+jvmTarget = "1.8"
+
+## Android gradle plugin
+androidGradlePlugin = "8.5.0"
+androidDesugarJdkLibs = "2.0.4"
+
+## AndroidX
+androidxCore = "1.13.1"
+androidxAppCompat = "1.6.1"
+androidxLifecycle = "2.7.0"
+androidxActivity = "1.9.0"
+androidxDatastore = "1.1.1"
+
+## Kotlin Symbol Processing
+ksp = "1.9.0-1.0.12"
+
+## Compose
+androidxComposeBom = "2024.04.01"
+androidxComposeCompiler = "1.5.14"
+androidxComposeMaterial3 = "1.2.1"
+composeNavigation = "2.8.0-beta04"
+
+## Hilt
+hilt = "2.48.1"
+hiltNavigationCompose = "1.2.0"
+hiltManager = "1.0.0"
+
+## Network
+okhttp = "4.12.0"
+retrofit = "2.11.0"
+retrofitJsonConverter = "1.0.0"
+retrofit2KotlinxSerializationConverter = "0.8.0"
+workManagerVersion = "2.8.1"
+ossVersion = "17.0.0"
+
+## Kotlin
kotlin = "1.9.0"
-coreKtx = "1.10.1"
+kotlinxImmutable = "0.3.7"
+kotlinxSerializationJsonVersion = "1.5.1"
+serialization = "1.6.3"
+kotlinParcelize = "1.8.20"
+
+## Coroutine
+coroutine = "1.8.1"
+
+## Timber
+timber = "5.0.1"
+
+## Coil
+coil = "1.4.0"
+
+## Security
+securityVersion = "1.1.0-alpha06"
+
+## Lottie
+lottieVersion = "6.0.0"
+
+## Test
junit = "4.13.2"
junitVersion = "1.1.5"
espressoCore = "3.5.1"
+kotest = "5.9.0"
+espressoCoreVersion = "3.5.1"
+material = "1.10.0"
lifecycleRuntimeKtx = "2.6.1"
-activityCompose = "1.8.0"
-composeBom = "2024.04.01"
+foundationAndroid = "1.6.8"
+material3Android = "1.2.1"
+lifecycleRuntimeComposeAndroid = "2.8.2"
[libraries]
-androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
+androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidxCore" }
+androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidxAppCompat" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
-androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
+androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "androidxActivity" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
-androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
-androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
+androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
+androidx-workManager = {group = "androidx.work", name = "work-runtime-ktx", version.ref = "workManagerVersion"}
+androidx-hiltWorkManager = {group = "androidx.hilt", name = "hilt-work", version.ref = "hiltManager"}
+androidx-hiltWorkManagerCompiler = {group = "androidx.hilt", name = "hilt-compiler" , version.ref = "hiltManager"}
+androidx-navigation-compose = {module = "androidx.navigation:navigation-compose", version.ref = "composeNavigation"}
+
+androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "androidxComposeBom" }
+androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "androidxComposeMaterial3" }
+androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
+androidx-compose-ui-test = { group = "androidx.compose.ui", name = "ui-test-junit4" }
+androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
+androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
+androidx-compose-ui-testManifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
+androidx-compose-navigation = { group = "androidx.navigation", name = "navigation-compose", version.ref = "composeNavigation" }
+androidx-compose-navigation-test = { group = "androidx.navigation", name = "navigation-testing", version.ref = "composeNavigation" }
+compose-compiler-gradle-plugin = { module = "org.jetbrains.kotlin:compose-compiler-gradle-plugin", version.ref = "kotlin" }
+
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
-androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
+androidx-foundation-android = { group = "androidx.compose.foundation", name = "foundation-android", version.ref = "foundationAndroid" }
+androidx-material3-android = { group = "androidx.compose.material3", name = "material3-android", version.ref = "material3Android" }
+androidx-lifecycle-runtime-compose-android = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose-android", version.ref = "lifecycleRuntimeComposeAndroid" }
+androidx-security = {group = "androidx.security", name = "security-crypto" , version.ref = "securityVersion"}
+android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" }
+android-desugarJdkLibs = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "androidDesugarJdkLibs" }
+androidx-lifecycle-runtimeCompose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidxLifecycle" }
+androidx-lifecycle-viewModelCompose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "androidxLifecycle" }
+androidx-datastore = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "androidxDatastore" }
-[plugins]
-android-application = { id = "com.android.application", version.ref = "agp" }
-jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
+hilt-core = { group = "com.google.dagger", name = "hilt-core", version.ref = "hilt" }
+hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" }
+hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
+hilt-android-testing = { group = "com.google.dagger", name = "hilt-android-testing", version.ref = "hilt" }
+hilt-android-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" }
+hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hiltNavigationCompose" }
+hilt-manager = {group = "androidx.hilt" , name = "hilt-compiler", version.ref = "hiltManager"}
+okhttp = {group = "com.squareup.okhttp3", name = "okhttp"}
+okhttp-bom = {group = "com.squareup.okhttp3" , name = "okhttp-bom" , version.ref = "okhttp"}
+okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" }
+retrofit-core = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
+retrofit-kotlin-serialization = { module = "com.squareup.retrofit2:converter-kotlinx-serialization", version.ref = "retrofit" }
+retrofit2-kotlinx-serialization-converter = { module = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter", version.ref = "retrofitJsonConverter" }
+retrofit2-kotlinx-serialization-converter-v080 = { module = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter", version.ref = "retrofit2KotlinxSerializationConverter" }
+
+kotlinx-serialization-json-v151 = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJsonVersion" }
+kotlinx-serialization-json = {module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization"}
+kotlinx-immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable", version.ref = "kotlinxImmutable" }
+coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutine" }
+coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutine" }
+coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutine" }
+kotlin = {group = "org.jetbrains.kotlin" , name = "kotlin-stdlib", version.ref = "kotlin"}
+kotlin-gradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
+
+kotest-runner = { group = "io.kotest", name = "kotest-runner-junit5", version.ref = "kotest" }
+kotest-assertions = { group = "io.kotest", name = "kotest-assertions-core", version.ref = "kotest" }
+androidx-test-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCoreVersion" }
+
+compose-coil = {group = "io.coil-kt", name = "coil-compose", version.ref = "coil"}
+material = { group = "com.google.android.material", name = "material", version.ref = "material" }
+timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" }
+ossLicense = {group = "com.google.android.gms", name = "play-services-oss-licenses", version.ref = "ossVersion"}
+lottie = {group = "com.airbnb.android", name = "lottie", version.ref = "lottieVersion"}
+
+[plugins]
+android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
+kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
+android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" }
+kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
+kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
+hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
+android-test = { id = "com.android.test", version.ref = "androidGradlePlugin" }
+ksp = {id = "com.google.devtools.ksp", version.ref = "ksp"}
+kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlinParcelize" }
\ No newline at end of file
diff --git a/gradle/projectDependencyGraph.gradle b/gradle/projectDependencyGraph.gradle
new file mode 100644
index 000000000..e8de4991d
--- /dev/null
+++ b/gradle/projectDependencyGraph.gradle
@@ -0,0 +1,127 @@
+task projectDependencyGraph {
+ doLast {
+ def dot = new File(rootProject.rootDir, 'project.dot')
+ dot.parentFile.mkdirs()
+ dot.delete()
+
+ dot << 'digraph {\n'
+ dot << " graph [label=\"${rootProject.name}\\n \",labelloc=t,fontsize=30,ranksep=1.4];\n"
+ dot << ' node [style=filled, fillcolor="#bbbbbb"];\n'
+ dot << ' rankdir=TB;\n'
+
+ def rootProjects = []
+ def queue = [rootProject]
+ while (!queue.isEmpty()) {
+ def project = queue.remove(0)
+ rootProjects.add(project)
+ queue.addAll(project.childProjects.values())
+ }
+
+ def projects = new LinkedHashSet()
+ def dependencies = new LinkedHashMap, List>()
+ def multiplatformProjects = []
+ def jsProjects = []
+ def androidProjects = []
+ def androidDynamicFeatureProjects = []
+ def javaProjects = []
+
+ queue = [rootProject]
+ while (!queue.isEmpty()) {
+ def project = queue.remove(0)
+ queue.addAll(project.childProjects.values())
+
+ if (project.plugins.hasPlugin('org.jetbrains.kotlin.multiplatform')) {
+ multiplatformProjects.add(project)
+ }
+ if (project.plugins.hasPlugin('kotlin2js')) {
+ jsProjects.add(project)
+ }
+ if (project.plugins.hasPlugin('com.android.library') ||
+ project.plugins.hasPlugin('com.android.application')) {
+ androidProjects.add(project)
+ }
+ if (project.plugins.hasPlugin('com.android.dynamic-feature')) {
+ androidDynamicFeatureProjects.add(project)
+ }
+ if (project.plugins.hasPlugin('java-library') || project.plugins.hasPlugin('java')) {
+ javaProjects.add(project)
+ }
+
+ project.configurations.all { config ->
+ if (config.name.toLowerCase().contains("test")) return
+ config.dependencies
+ .withType(ProjectDependency)
+ .collect { it.dependencyProject }
+ .each { dependency ->
+ projects.add(project)
+ projects.add(dependency)
+ rootProjects.remove(dependency)
+
+ def graphKey = new Tuple2(project, dependency)
+ def traits = dependencies.computeIfAbsent(graphKey) { new ArrayList() }
+
+ if (config.name.toLowerCase().endsWith('implementation')) {
+ traits.add('style=dotted')
+ }
+ }
+ }
+ }
+
+ projects = projects.sort { it.path }
+
+ dot << '\n # Projects\n\n'
+ for (project in projects) {
+ def traits = []
+
+ if (rootProjects.contains(project)) {
+ traits.add('shape=box')
+ }
+
+ if (multiplatformProjects.contains(project)) {
+ traits.add('fillcolor="#ffd2b3"')
+ } else if (jsProjects.contains(project)) {
+ traits.add('fillcolor="#ffffba"')
+ } else if (androidProjects.contains(project)) {
+ traits.add('fillcolor="#baffc9"')
+ } else if (androidDynamicFeatureProjects.contains(project)) {
+ traits.add('fillcolor="#c9baff"')
+ } else if (javaProjects.contains(project)) {
+ traits.add('fillcolor="#ffb3ba"')
+ } else {
+ traits.add('fillcolor="#eeeeee"')
+ }
+
+ dot << " \"${project.path}\" [${traits.join(", ")}];\n"
+ }
+
+ dot << '\n {rank = same;'
+ for (project in projects) {
+ if (rootProjects.contains(project)) {
+ dot << " \"${project.path}\";"
+ }
+ }
+ dot << '}\n'
+
+ dot << '\n # Dependencies\n\n'
+ dependencies.forEach { key, traits ->
+ dot << " \"${key.first.path}\" -> \"${key.second.path}\""
+ if (!traits.isEmpty()) {
+ dot << " [${traits.join(", ")}]"
+ }
+ dot << '\n'
+ }
+
+ dot << '}\n'
+
+ def dotPath = "C:/Program Files/Graphviz/bin/dot"
+ def p = "${dotPath} -Tpng -O project.dot".execute([], dot.parentFile)
+
+ p.waitFor()
+ if (p.exitValue() != 0) {
+ throw new RuntimeException(p.errorStream.text)
+ }
+ dot.delete()
+
+ println("Project module dependency graph created at ${dot.absolutePath}.png")
+ }
+}
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 9110da121..8a2c140ec 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -21,4 +21,7 @@ dependencyResolutionManagement {
rootProject.name = "Terning-Android"
include(":app")
-
\ No newline at end of file
+include(":feature")
+include(":data")
+include(":domain")
+include(":core")