diff --git a/.gitignore b/.gitignore
index 38780069..733ddcf6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -154,6 +154,7 @@ render.experimental.xml
.idea/gradle.xml
.idea/jarRepositories.xml
.idea/navEditor.xml
+.idea/kotlinc.xml
# Legacy Eclipse project files
.classpath
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 86756297..00000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# CodeStream ignored files
-/codestream.xml
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
deleted file mode 100644
index 69e86158..00000000
--- a/.idea/kotlinc.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
deleted file mode 100644
index 50499c99..00000000
--- a/app/build.gradle
+++ /dev/null
@@ -1,43 +0,0 @@
-plugins {
- id 'com.android.application'
- id 'org.jetbrains.kotlin.android'
-}
-
-android {
- namespace 'com.going.going'
- compileSdk 33
-
- defaultConfig {
- applicationId "com.going.going"
- minSdk 28
- targetSdk 33
- versionCode 1
- versionName "1.0"
-
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- }
-
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
- }
- }
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
- kotlinOptions {
- jvmTarget = '1.8'
- }
-}
-
-dependencies {
-
- implementation 'androidx.core:core-ktx:1.8.0'
- implementation 'androidx.appcompat:appcompat:1.6.1'
- implementation 'com.google.android.material:material:1.11.0'
- testImplementation 'junit:junit:4.13.2'
- androidTestImplementation 'androidx.test.ext:junit:1.1.5'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
-}
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
new file mode 100644
index 00000000..d600c635
--- /dev/null
+++ b/app/build.gradle.kts
@@ -0,0 +1,98 @@
+import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties
+
+plugins {
+ id("com.android.application")
+ kotlin("android")
+ kotlin("kapt")
+ id("kotlin-parcelize")
+ id("dagger.hilt.android.plugin")
+ id("com.google.android.gms.oss-licenses-plugin")
+}
+
+android {
+ namespace = Constants.packageName
+ compileSdk = Constants.compileSdk
+
+ defaultConfig {
+ applicationId = Constants.packageName
+ minSdk = Constants.minSdk
+ targetSdk = Constants.targetSdk
+ versionCode = Constants.versionCode
+ versionName = Constants.versionName
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+
+ buildConfigField(
+ "String",
+ "BASE_URL",
+ gradleLocalProperties(rootDir).getProperty("base.url"),
+ )
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro",
+ )
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility = Versions.javaVersion
+ targetCompatibility = Versions.javaVersion
+ }
+
+ kotlinOptions {
+ jvmTarget = Versions.jvmVersion
+ }
+
+ buildFeatures {
+ buildConfig = true
+ dataBinding = true
+ viewBinding = true
+ }
+}
+
+dependencies {
+ implementation(project(":core-ui"))
+ implementation(project(":data"))
+ implementation(project(":domain"))
+ implementation(project(":presentation"))
+
+ KotlinDependencies.run {
+ implementation(kotlin)
+ implementation(coroutines)
+ implementation(jsonSerialization)
+ }
+
+ AndroidXDependencies.run {
+ implementation(coreKtx)
+ implementation(appCompat)
+ implementation(hilt)
+ implementation(workManager)
+ implementation(hiltWorkManager)
+ }
+
+ KaptDependencies.run {
+ kapt(hiltCompiler)
+ kapt(hiltWorkManagerCompiler)
+ }
+
+ TestDependencies.run {
+ testImplementation(jUnit)
+ androidTestImplementation(androidTest)
+ androidTestImplementation(espresso)
+ }
+
+ ThirdPartyDependencies.run {
+ implementation(platform(okHttpBom))
+ implementation(okHttp)
+ implementation(okHttpLoggingInterceptor)
+ implementation(retrofit)
+ implementation(retrofitJsonConverter)
+ implementation(timber)
+ implementation(ossLicense)
+ }
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index 481bb434..ff59496d 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -1,6 +1,6 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
-# proguardFiles setting in build.gradle.
+# proguardFiles setting in build.gradle.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 59096d25..2f2fb6c8 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,15 +2,30 @@
+
+
+ android:usesCleartextTraffic="true"
+ tools:targetApi="31">
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/going/going/MyApp.kt b/app/src/main/java/com/going/going/MyApp.kt
new file mode 100644
index 00000000..766cf55b
--- /dev/null
+++ b/app/src/main/java/com/going/going/MyApp.kt
@@ -0,0 +1,26 @@
+package com.going.going
+
+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/going/going/di/DataSourceModule.kt b/app/src/main/java/com/going/going/di/DataSourceModule.kt
new file mode 100644
index 00000000..292409d3
--- /dev/null
+++ b/app/src/main/java/com/going/going/di/DataSourceModule.kt
@@ -0,0 +1,20 @@
+package com.going.going.di
+
+import com.going.data.datasource.MockDataSource
+import com.going.data.datasourceImpl.MockDataSourceImpl
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object DataSourceModule {
+
+ @Provides
+ @Singleton
+ fun provideMockDataSource(mockDataSourceImpl: MockDataSourceImpl): MockDataSource =
+ mockDataSourceImpl
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/going/going/di/RepositoryModule.kt b/app/src/main/java/com/going/going/di/RepositoryModule.kt
new file mode 100644
index 00000000..5d46141b
--- /dev/null
+++ b/app/src/main/java/com/going/going/di/RepositoryModule.kt
@@ -0,0 +1,20 @@
+package com.going.going.di
+
+import com.going.data.repositoryImpl.MockRepositoryImpl
+import com.going.domain.repository.MockRepository
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object RepositoryModule {
+
+ @Provides
+ @Singleton
+ fun provideMockRepository(mockRepositoryImpl: MockRepositoryImpl): MockRepository =
+ mockRepositoryImpl
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/going/going/di/RetrofitModule.kt b/app/src/main/java/com/going/going/di/RetrofitModule.kt
new file mode 100644
index 00000000..790a221a
--- /dev/null
+++ b/app/src/main/java/com/going/going/di/RetrofitModule.kt
@@ -0,0 +1,59 @@
+package com.going.going.di
+
+import com.going.going.BuildConfig.BASE_URL
+import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import kotlinx.serialization.json.Json
+import okhttp3.Interceptor
+import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.OkHttpClient
+import okhttp3.logging.HttpLoggingInterceptor
+import retrofit2.Converter
+import retrofit2.Retrofit
+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().apply {
+ level = HttpLoggingInterceptor.Level.BODY
+ }
+
+ @Provides
+ @Singleton
+ fun provideOkHttpClient(
+ loggingInterceptor: Interceptor
+ ): OkHttpClient = OkHttpClient.Builder()
+ .addInterceptor(loggingInterceptor)
+ .build()
+
+ @Provides
+ @Singleton
+ fun provideRetrofit(
+ client: OkHttpClient,
+ factory: Converter.Factory,
+ ): Retrofit = Retrofit.Builder()
+ .baseUrl(BASE_URL)
+ .client(client)
+ .addConverterFactory(factory)
+ .build()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/going/going/di/ServiceModule.kt b/app/src/main/java/com/going/going/di/ServiceModule.kt
new file mode 100644
index 00000000..18e3873f
--- /dev/null
+++ b/app/src/main/java/com/going/going/di/ServiceModule.kt
@@ -0,0 +1,20 @@
+package com.going.going.di
+
+import com.going.data.service.MockService
+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(retrofit: Retrofit): MockService =
+ retrofit.create(MockService::class.java)
+
+}
\ No newline at end of file
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
deleted file mode 100644
index b057ebc8..00000000
--- a/app/src/main/res/values-night/themes.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
\ 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 be675850..00000000
--- a/app/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
- GoingGoing
-
\ No newline at end of file
diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml
deleted file mode 100644
index fa0f996d..00000000
--- a/app/src/main/res/xml/backup_rules.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml
deleted file mode 100644
index 9ee9997b..00000000
--- a/app/src/main/res/xml/data_extraction_rules.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
deleted file mode 100644
index fd3d4789..00000000
--- a/build.gradle
+++ /dev/null
@@ -1,6 +0,0 @@
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
-plugins {
- id 'com.android.application' version '8.0.2' apply false
- id 'com.android.library' version '8.0.2' apply false
- id 'org.jetbrains.kotlin.android' version '1.8.20' apply false
-}
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 00000000..b5df03ba
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,17 @@
+buildscript {
+ repositories {
+ google()
+ mavenCentral()
+ }
+
+ dependencies {
+ classpath(ClassPathPlugins.gradle)
+ classpath(ClassPathPlugins.kotlinGradle)
+ classpath(ClassPathPlugins.hilt)
+ classpath(ClassPathPlugins.oss)
+ }
+}
+
+tasks.register("clean", Delete::class) {
+ delete(rootProject.buildDir)
+}
\ No newline at end of file
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
new file mode 100644
index 00000000..b22ed732
--- /dev/null
+++ b/buildSrc/build.gradle.kts
@@ -0,0 +1,7 @@
+plugins {
+ `kotlin-dsl`
+}
+
+repositories {
+ mavenCentral()
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/Constants.kt b/buildSrc/src/main/kotlin/Constants.kt
new file mode 100644
index 00000000..b8d6f325
--- /dev/null
+++ b/buildSrc/src/main/kotlin/Constants.kt
@@ -0,0 +1,8 @@
+object Constants {
+ const val packageName = "com.going.going"
+ const val compileSdk = 34
+ const val minSdk = 28
+ const val targetSdk = 34
+ const val versionCode = 1
+ const val versionName = "1.0"
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt
new file mode 100644
index 00000000..d8043bfa
--- /dev/null
+++ b/buildSrc/src/main/kotlin/Dependencies.kt
@@ -0,0 +1,95 @@
+object KotlinDependencies {
+ const val kotlin = "org.jetbrains.kotlin:kotlin-stdlib:${Versions.kotlinVersion}"
+ const val coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.coroutinesAndroidVersion}"
+ const val jsonSerialization = "org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.kotlinSerializationJsonVersion}"
+ const val dateTime = "org.jetbrains.kotlinx:kotlinx-datetime:${Versions.kotlinDateTimeVersion}"
+}
+
+object AndroidXDependencies {
+ const val coreKtx = "androidx.core:core-ktx:${Versions.coreKtxVersion}"
+ const val splashScreen = "androidx.core:core-splashscreen:${Versions.splashVersion}"
+
+ const val appCompat = "androidx.appcompat:appcompat:${Versions.appCompatVersion}"
+ const val constraintLayout = "androidx.constraintlayout:constraintlayout:${Versions.constraintLayoutVersion}"
+ const val startup = "androidx.startup:startup-runtime:${Versions.appStartUpVersion}"
+ const val activity = "androidx.activity:activity-ktx:${Versions.activityKtxVersion}"
+ const val fragment = "androidx.fragment:fragment-ktx:${Versions.fragmentKtxVersion}"
+ const val legacy = "androidx.legacy:legacy-support-v4:${Versions.legacySupportVersion}"
+ const val security = "androidx.security:security-crypto:${Versions.securityVersion}"
+
+ const val lifeCycleKtx = "androidx.lifecycle:lifecycle-runtime-ktx:${Versions.lifecycleVersion}"
+ const val lifeCycleLiveDataKtx = "androidx.lifecycle:lifecycle-livedata-ktx:${Versions.lifecycleVersion}"
+ const val lifecycleJava8 = "androidx.lifecycle:lifecycle-common-java8:${Versions.lifecycleVersion}"
+
+ const val paging = "androidx.paging:paging-common-ktx:${Versions.pagingVersion}"
+ const val pagingRuntime = "androidx.paging:paging-runtime:${Versions.pagingVersion}"
+ const val workManager = "androidx.work:work-runtime-ktx:${Versions.workManagerVersion}"
+
+ const val hiltWorkManager = "androidx.hilt:hilt-work:1.0.0"
+ const val hilt = "com.google.dagger:hilt-android:${Versions.hiltVersion}"
+ const val ossLicense = "com.google.android.gms:play-services-oss-licenses:${Versions.ossVersion}"
+
+ const val navigationFragment = "androidx.navigation:navigation-fragment-ktx:${Versions.navigationVersion}"
+ const val navigationUi = "androidx.navigation:navigation-ui-ktx:${Versions.navigationVersion}"
+ const val navigationDynamic = "androidx.navigation:navigation-dynamic-features-fragment:${Versions.navigationVersion}"
+}
+
+object TestDependencies {
+ const val jUnit = "junit:junit:${Versions.junitVersion}"
+ const val androidTest = "androidx.test.ext:junit:${Versions.androidTestVersion}"
+ const val espresso = "androidx.test.espresso:espresso-core:${Versions.espressoVersion}"
+}
+
+object MaterialDesignDependencies {
+ const val materialDesign = "com.google.android.material:material:${Versions.materialDesignVersion}"
+}
+
+object KaptDependencies {
+ const val hiltAndroidCompiler = "com.google.dagger:hilt-compiler:${Versions.hiltVersion}"
+ const val hiltCompiler = "com.google.dagger:hilt-compiler:${Versions.hiltVersion}"
+ const val hiltWorkManagerCompiler = "androidx.hilt:hilt-compiler:1.0.0"
+}
+
+object ThirdPartyDependencies {
+ const val coil = "io.coil-kt:coil:${Versions.coilVersion}"
+
+ const val retrofit = "com.squareup.retrofit2:retrofit:${Versions.retrofitVersion}"
+ const val retrofitJsonConverter = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:${Versions.kotlinSerializationConverterVersion}"
+ const val okHttpBom = "com.squareup.okhttp3:okhttp-bom:${Versions.okHttpVersion}"
+ const val okHttp = "com.squareup.okhttp3:okhttp"
+ const val okHttpLoggingInterceptor = "com.squareup.okhttp3:logging-interceptor"
+
+ const val timber = "com.jakewharton.timber:timber:${Versions.timberVersion}"
+
+ const val ossLicense = "com.google.android.gms:play-services-oss-licenses:${Versions.ossVersion}"
+ const val hiltCore = "com.google.dagger:hilt-core:${Versions.hiltVersion}"
+
+ const val progressView = "com.github.skydoves:progressview:${Versions.progressViewVersion}"
+ const val balloon = "com.github.skydoves:balloon:${Versions.balloonVersion}"
+ const val lottie = "com.airbnb.android:lottie:${Versions.lottieVersion}"
+ const val circularProgressBar = "com.mikhaellopez:circularprogressbar:${Versions.circularProgressBar}"
+ const val circleIndicator = "me.relex:circleindicator:${Versions.circleIndicatorVersion}"
+ const val shimmer = "com.facebook.shimmer:shimmer:${Versions.shimmerVersion}"
+
+ const val kakaoLogin = "com.kakao.sdk:v2-user:${Versions.kakaoVersion}"
+ const val kakaoAuth = "com.kakao.sdk:v2-auth:${Versions.kakaoVersion}"
+ const val kakaoTalk = "com.kakao.sdk:v2-talk:${Versions.kakaoVersion}"
+ const val kakaoShare = "com.kakao.sdk:v2-share:${Versions.kakaoVersion}"
+
+ const val amplitude = "com.amplitude:android-sdk:${Versions.amplitudeVersion}"
+}
+
+object ClassPathPlugins {
+ const val gradle = "com.android.tools.build:gradle:${Versions.gradleVersion}"
+ const val kotlinGradle = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlinVersion}"
+ const val hilt = "com.google.dagger:hilt-android-gradle-plugin:${Versions.hiltVersion}"
+ const val oss = "com.google.android.gms:oss-licenses-plugin:${Versions.ossPluginVersion}"
+}
+
+object FirebaseDependencies {
+ const val bom = "com.google.firebase:firebase-bom:32.2.0"
+ const val messaging = "com.google.firebase:firebase-messaging-ktx"
+ const val crashlytics = "com.google.firebase:firebase-crashlytics-ktx"
+ const val analytics = "com.google.firebase:firebase-analytics-ktx"
+ const val remoteConfig = "com.google.firebase:firebase-config-ktx"
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt
new file mode 100644
index 00000000..151e8489
--- /dev/null
+++ b/buildSrc/src/main/kotlin/Versions.kt
@@ -0,0 +1,47 @@
+import org.gradle.api.JavaVersion
+
+object Versions {
+ const val gradleVersion = "8.0.2"
+
+ const val buildToolsVersion = "30.0.3"
+ const val kotlinVersion = "1.8.20"
+ const val kotlinSerializationJsonVersion = "1.5.1"
+ const val kotlinDateTimeVersion = "0.4.0"
+ const val coreKtxVersion = "1.10.1"
+ const val appCompatVersion = "1.6.1"
+ const val materialDesignVersion = "1.9.0"
+ const val constraintLayoutVersion = "2.1.4"
+ const val appStartUpVersion = "1.1.1"
+ const val legacySupportVersion = "1.0.0"
+ const val securityVersion = "1.1.0-alpha06"
+ const val hiltVersion = "2.46.1"
+ const val activityKtxVersion = "1.7.2"
+ const val fragmentKtxVersion = "1.5.7"
+ const val coroutinesAndroidVersion = "1.7.1"
+ const val pagingVersion = "3.1.1"
+ const val lifecycleVersion = "2.6.1"
+ const val ossPluginVersion = "0.10.4"
+ const val ossVersion = "17.0.0"
+ const val splashVersion = "1.0.1"
+ const val workManagerVersion = "2.8.1"
+ const val coilVersion = "2.4.0"
+ const val retrofitVersion = "2.9.0"
+ const val kotlinSerializationConverterVersion = "1.0.0"
+ const val okHttpVersion = "4.11.0"
+ const val timberVersion = "5.0.1"
+ const val progressViewVersion = "1.1.3"
+ const val balloonVersion = "1.4.5"
+ const val lottieVersion = "6.0.0"
+ const val circularProgressBar = "3.1.0"
+ const val kakaoVersion = "2.14.0"
+ const val circleIndicatorVersion = "2.1.6"
+ const val shimmerVersion = "0.5.0"
+ const val navigationVersion = "2.6.0"
+ const val amplitudeVersion = "2.23.2"
+ const val junitVersion = "4.13.2"
+ const val espressoVersion = "3.3.0"
+ const val androidTestVersion = "1.1.2"
+
+ val javaVersion = JavaVersion.VERSION_17
+ const val jvmVersion = "17"
+}
\ No newline at end of file
diff --git a/core-ui/.gitignore b/core-ui/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/core-ui/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core-ui/build.gradle.kts b/core-ui/build.gradle.kts
new file mode 100644
index 00000000..c9e7e617
--- /dev/null
+++ b/core-ui/build.gradle.kts
@@ -0,0 +1,50 @@
+plugins {
+ id("com.android.library")
+ kotlin("android")
+ kotlin("kapt")
+ id("dagger.hilt.android.plugin")
+}
+
+android {
+ namespace = "com.going.ui"
+ compileSdk = Constants.compileSdk
+
+ defaultConfig {
+ minSdk = Constants.minSdk
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+ compileOptions {
+ sourceCompatibility = Versions.javaVersion
+ targetCompatibility = Versions.javaVersion
+ }
+ kotlinOptions {
+ jvmTarget = Versions.jvmVersion
+ }
+
+ buildFeatures {
+ dataBinding = true
+ viewBinding = true
+ }
+}
+
+dependencies {
+ // Kotlin
+ implementation(KotlinDependencies.kotlin)
+
+ // Lifecycle Ktx
+ implementation(AndroidXDependencies.lifeCycleKtx)
+
+ // Material Design
+ implementation(MaterialDesignDependencies.materialDesign)
+
+ // Hilt
+ implementation(AndroidXDependencies.hilt)
+ kapt(KaptDependencies.hiltAndroidCompiler)
+
+ // Test Dependency
+ testImplementation(TestDependencies.jUnit)
+ androidTestImplementation(TestDependencies.androidTest)
+ androidTestImplementation(TestDependencies.espresso)
+}
\ No newline at end of file
diff --git a/core-ui/consumer-rules.pro b/core-ui/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/core-ui/src/androidTest/java/com/going/ui/ExampleInstrumentedTest.kt b/core-ui/src/androidTest/java/com/going/ui/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..05cdae17
--- /dev/null
+++ b/core-ui/src/androidTest/java/com/going/ui/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.going.ui
+
+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.going.ui", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/core-ui/src/main/AndroidManifest.xml b/core-ui/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5918e68
--- /dev/null
+++ b/core-ui/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/core-ui/src/main/java/com/going/ui/base/BaseActivity.kt b/core-ui/src/main/java/com/going/ui/base/BaseActivity.kt
new file mode 100644
index 00000000..080cf4a3
--- /dev/null
+++ b/core-ui/src/main/java/com/going/ui/base/BaseActivity.kt
@@ -0,0 +1,28 @@
+package com.going.ui.base
+
+import android.os.Bundle
+import android.view.MotionEvent
+import android.view.View
+import androidx.annotation.LayoutRes
+import androidx.appcompat.app.AppCompatActivity
+import androidx.databinding.DataBindingUtil
+import androidx.databinding.ViewDataBinding
+import com.going.ui.extension.hideKeyboard
+
+abstract class BaseActivity(
+ @LayoutRes private val layoutResId: Int,
+) : AppCompatActivity() {
+
+ protected lateinit var binding: T
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = DataBindingUtil.setContentView(this, layoutResId)
+ binding.lifecycleOwner = this
+ }
+
+ override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
+ hideKeyboard(currentFocus ?: View(this))
+ return super.dispatchTouchEvent(ev)
+ }
+}
\ No newline at end of file
diff --git a/core-ui/src/main/java/com/going/ui/base/BaseBottomSheet.kt b/core-ui/src/main/java/com/going/ui/base/BaseBottomSheet.kt
new file mode 100644
index 00000000..28a039bf
--- /dev/null
+++ b/core-ui/src/main/java/com/going/ui/base/BaseBottomSheet.kt
@@ -0,0 +1,34 @@
+package com.going.ui.base
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.LayoutRes
+import androidx.databinding.DataBindingUtil
+import androidx.databinding.ViewDataBinding
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment
+
+abstract class BaseBottomSheet(
+ @LayoutRes private val layoutRes: Int,
+) : BottomSheetDialogFragment() {
+
+ private var _binding: T? = null
+ protected val binding: T
+ get() = requireNotNull(_binding) { "binding object is not initialized" }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View? {
+ _binding = DataBindingUtil.inflate(inflater, layoutRes, container, false)
+ binding.lifecycleOwner = viewLifecycleOwner
+ return binding.root
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+}
\ No newline at end of file
diff --git a/core-ui/src/main/java/com/going/ui/base/BaseDialog.kt b/core-ui/src/main/java/com/going/ui/base/BaseDialog.kt
new file mode 100644
index 00000000..57ff5498
--- /dev/null
+++ b/core-ui/src/main/java/com/going/ui/base/BaseDialog.kt
@@ -0,0 +1,34 @@
+package com.going.ui.base
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.LayoutRes
+import androidx.databinding.DataBindingUtil
+import androidx.databinding.ViewDataBinding
+import androidx.fragment.app.DialogFragment
+
+abstract class BaseDialog(
+ @LayoutRes private val layoutRes: Int,
+) : DialogFragment() {
+
+ private var _binding: T? = null
+ protected val binding: T
+ get() = requireNotNull(_binding) { "binding object is not initialized" }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View? {
+ _binding = DataBindingUtil.inflate(inflater, layoutRes, container, false)
+ binding.lifecycleOwner = viewLifecycleOwner
+ return binding.root
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+}
\ No newline at end of file
diff --git a/core-ui/src/main/java/com/going/ui/base/BaseFragment.kt b/core-ui/src/main/java/com/going/ui/base/BaseFragment.kt
new file mode 100644
index 00000000..f3534780
--- /dev/null
+++ b/core-ui/src/main/java/com/going/ui/base/BaseFragment.kt
@@ -0,0 +1,34 @@
+package com.going.ui.base
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.LayoutRes
+import androidx.databinding.DataBindingUtil
+import androidx.databinding.ViewDataBinding
+import androidx.fragment.app.Fragment
+
+abstract class BaseFragment(
+ @LayoutRes private val layoutRes: Int,
+) : Fragment() {
+
+ private var _binding: T? = null
+ protected val binding: T
+ get() = requireNotNull(_binding) { "binding object is not initialized" }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View? {
+ _binding = DataBindingUtil.inflate(inflater, layoutRes, container, false)
+ binding.lifecycleOwner = viewLifecycleOwner
+ return binding.root
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+}
\ No newline at end of file
diff --git a/core-ui/src/main/java/com/going/ui/extension/ContextExt.kt b/core-ui/src/main/java/com/going/ui/extension/ContextExt.kt
new file mode 100644
index 00000000..a52f974d
--- /dev/null
+++ b/core-ui/src/main/java/com/going/ui/extension/ContextExt.kt
@@ -0,0 +1,35 @@
+package com.going.ui.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.ColorRes
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+import androidx.core.content.ContextCompat
+import com.google.android.material.snackbar.Snackbar
+
+fun Context.toast(message: String) {
+ Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
+}
+
+fun Context.longToast(message: String) {
+ Toast.makeText(this, message, Toast.LENGTH_LONG).show()
+}
+
+fun Context.snackBar(anchorView: View, message: () -> String) {
+ Snackbar.make(anchorView, message(), Snackbar.LENGTH_SHORT).show()
+}
+
+fun Context.stringOf(@StringRes resId: Int) = getString(resId)
+
+fun Context.colorOf(@ColorRes resId: Int) = ContextCompat.getColor(this, resId)
+
+fun Context.drawableOf(@DrawableRes resId: Int) = ContextCompat.getDrawable(this, resId)
+
+fun Context.hideKeyboard(view: View) {
+ val inputMethodManager = getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
+ inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0)
+}
\ No newline at end of file
diff --git a/core-ui/src/main/java/com/going/ui/extension/FragmentExt.kt b/core-ui/src/main/java/com/going/ui/extension/FragmentExt.kt
new file mode 100644
index 00000000..6fba2cf3
--- /dev/null
+++ b/core-ui/src/main/java/com/going/ui/extension/FragmentExt.kt
@@ -0,0 +1,36 @@
+package com.going.ui.extension
+
+import android.view.View
+import android.widget.Toast
+import androidx.annotation.ColorRes
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+import androidx.core.content.ContextCompat
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.lifecycleScope
+import com.google.android.material.snackbar.Snackbar
+
+fun Fragment.toast(message: String) {
+ Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
+}
+
+fun Fragment.longToast(message: String) {
+ Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show()
+}
+
+fun Fragment.snackBar(anchorView: View, message: () -> String) {
+ Snackbar.make(anchorView, message(), Snackbar.LENGTH_SHORT).show()
+}
+
+fun Fragment.stringOf(@StringRes resId: Int) = getString(resId)
+
+fun Fragment.colorOf(@ColorRes resId: Int) = ContextCompat.getColor(requireContext(), resId)
+
+fun Fragment.drawableOf(@DrawableRes resId: Int) =
+ ContextCompat.getDrawable(requireContext(), resId)
+
+val Fragment.viewLifeCycle
+ get() = viewLifecycleOwner.lifecycle
+
+val Fragment.viewLifeCycleScope
+ get() = viewLifecycleOwner.lifecycleScope
\ No newline at end of file
diff --git a/core-ui/src/main/java/com/going/ui/extension/IntExt.kt b/core-ui/src/main/java/com/going/ui/extension/IntExt.kt
new file mode 100644
index 00000000..f21c900e
--- /dev/null
+++ b/core-ui/src/main/java/com/going/ui/extension/IntExt.kt
@@ -0,0 +1,9 @@
+package com.going.ui.extension
+
+import android.content.Context
+import android.util.TypedValue
+
+fun Int.dpToPx(context: Context): Int {
+ val metrics = context.resources.displayMetrics
+ return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), metrics).toInt()
+}
\ No newline at end of file
diff --git a/core-ui/src/main/java/com/going/ui/extension/ItemDiffCallback.kt b/core-ui/src/main/java/com/going/ui/extension/ItemDiffCallback.kt
new file mode 100644
index 00000000..904ec0c2
--- /dev/null
+++ b/core-ui/src/main/java/com/going/ui/extension/ItemDiffCallback.kt
@@ -0,0 +1,18 @@
+package com.going.ui.extension
+
+import androidx.recyclerview.widget.DiffUtil
+
+class ItemDiffCallback(
+ val onItemsTheSame: (T, T) -> Boolean,
+ val onContentsTheSame: (T, T) -> Boolean,
+) : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(
+ oldItem: T,
+ newItem: T,
+ ): Boolean = onItemsTheSame(oldItem, newItem)
+
+ override fun areContentsTheSame(
+ oldItem: T,
+ newItem: T,
+ ): Boolean = onContentsTheSame(oldItem, newItem)
+}
\ No newline at end of file
diff --git a/core-ui/src/main/java/com/going/ui/extension/UiState.kt b/core-ui/src/main/java/com/going/ui/extension/UiState.kt
new file mode 100644
index 00000000..6dae17d6
--- /dev/null
+++ b/core-ui/src/main/java/com/going/ui/extension/UiState.kt
@@ -0,0 +1,15 @@
+package com.going.ui.extension
+
+sealed interface UiState {
+ object Empty : UiState
+
+ object Loading : UiState
+
+ data class Success(
+ val data: T,
+ ) : UiState
+
+ data class Failure(
+ val msg: String,
+ ) : UiState
+}
diff --git a/core-ui/src/main/java/com/going/ui/extension/ViewExt.kt b/core-ui/src/main/java/com/going/ui/extension/ViewExt.kt
new file mode 100644
index 00000000..138416be
--- /dev/null
+++ b/core-ui/src/main/java/com/going/ui/extension/ViewExt.kt
@@ -0,0 +1,17 @@
+package com.going.ui.extension
+
+import android.view.View
+
+inline fun View.setOnSingleClickListener(
+ delay: Long = 1000L,
+ crossinline block: (View) -> Unit,
+) {
+ var previousClickedTime = 0L
+ setOnClickListener { view ->
+ val clickedTime = System.currentTimeMillis()
+ if (clickedTime - previousClickedTime >= delay) {
+ block(view)
+ previousClickedTime = clickedTime
+ }
+ }
+}
\ No newline at end of file
diff --git a/core-ui/src/test/java/com/going/ui/ExampleUnitTest.kt b/core-ui/src/test/java/com/going/ui/ExampleUnitTest.kt
new file mode 100644
index 00000000..f93750d5
--- /dev/null
+++ b/core-ui/src/test/java/com/going/ui/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.going.ui
+
+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 00000000..42afabfd
--- /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 00000000..c9db34c0
--- /dev/null
+++ b/data/build.gradle.kts
@@ -0,0 +1,63 @@
+plugins {
+ id("com.android.library")
+ kotlin("android")
+ kotlin("kapt")
+ kotlin("plugin.serialization") version Versions.kotlinVersion
+}
+
+android {
+ namespace = "com.going.data"
+ compileSdk = Constants.compileSdk
+
+ defaultConfig {
+ minSdk = Constants.minSdk
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ compileOptions {
+ sourceCompatibility = Versions.javaVersion
+ targetCompatibility = Versions.javaVersion
+ }
+
+ kotlinOptions {
+ jvmTarget = Versions.jvmVersion
+ }
+
+ buildFeatures {
+ buildConfig = true
+ }
+}
+
+dependencies {
+ implementation(project(":domain"))
+
+ AndroidXDependencies.run {
+ implementation(hilt)
+ implementation(security)
+ implementation(coreKtx)
+ }
+
+ KotlinDependencies.run {
+ implementation(kotlin)
+ implementation(jsonSerialization)
+ implementation(coroutines)
+ implementation(dateTime)
+ }
+
+ ThirdPartyDependencies.run {
+ implementation(retrofit)
+ implementation(okHttp)
+ implementation(okHttpBom)
+ implementation(okHttpLoggingInterceptor)
+ implementation(retrofitJsonConverter)
+ implementation(timber)
+ }
+
+ TestDependencies.run {
+ testImplementation(jUnit)
+ androidTestImplementation(androidTest)
+ androidTestImplementation(espresso)
+ }
+}
\ No newline at end of file
diff --git a/data/consumer-rules.pro b/data/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/data/src/androidTest/java/com/going/data/ExampleInstrumentedTest.kt b/data/src/androidTest/java/com/going/data/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..31e35610
--- /dev/null
+++ b/data/src/androidTest/java/com/going/data/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.going.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.going.data", 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 00000000..a5918e68
--- /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/going/data/datasource/MockDataSource.kt b/data/src/main/java/com/going/data/datasource/MockDataSource.kt
new file mode 100644
index 00000000..d7aff4d2
--- /dev/null
+++ b/data/src/main/java/com/going/data/datasource/MockDataSource.kt
@@ -0,0 +1,11 @@
+package com.going.data.datasource
+
+import com.going.data.dto.response.MockFollowerResponseDto
+
+interface MockDataSource {
+
+ suspend fun getFollowerListData(
+ page: Int
+ ): MockFollowerResponseDto
+
+}
\ No newline at end of file
diff --git a/data/src/main/java/com/going/data/datasourceImpl/MockDataSourceImpl.kt b/data/src/main/java/com/going/data/datasourceImpl/MockDataSourceImpl.kt
new file mode 100644
index 00000000..82fb7862
--- /dev/null
+++ b/data/src/main/java/com/going/data/datasourceImpl/MockDataSourceImpl.kt
@@ -0,0 +1,18 @@
+package com.going.data.datasourceImpl
+
+import com.going.data.datasource.MockDataSource
+import com.going.data.dto.response.MockFollowerResponseDto
+import com.going.data.service.MockService
+import javax.inject.Inject
+
+class MockDataSourceImpl @Inject constructor(
+ private val mockService: MockService
+) : MockDataSource {
+
+ override suspend fun getFollowerListData(
+ page: Int
+ ): MockFollowerResponseDto {
+ return mockService.getFollowerList(page)
+ }
+
+}
\ No newline at end of file
diff --git a/data/src/main/java/com/going/data/dto/response/MockFollowerResponseDto.kt b/data/src/main/java/com/going/data/dto/response/MockFollowerResponseDto.kt
new file mode 100644
index 00000000..3ad42c86
--- /dev/null
+++ b/data/src/main/java/com/going/data/dto/response/MockFollowerResponseDto.kt
@@ -0,0 +1,52 @@
+package com.going.data.dto.response
+
+import com.going.domain.entity.response.MockFollowerModel
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class MockFollowerResponseDto(
+ @SerialName("page")
+ val page: Int,
+ @SerialName("per_page")
+ val perPage: Int,
+ @SerialName("total")
+ val total: Int,
+ @SerialName("total_pages")
+ val totalPages: Int,
+ @SerialName("data")
+ val data: List,
+ @SerialName("support")
+ val support: Support,
+) {
+ @Serializable
+ data class User(
+ @SerialName("id")
+ val id: Int,
+ @SerialName("email")
+ val email: String,
+ @SerialName("first_name")
+ val firstName: String,
+ @SerialName("last_name")
+ val lastName: String,
+ @SerialName("avatar")
+ val avatar: String
+ )
+
+ @Serializable
+ data class Support(
+ @SerialName("url")
+ val url: String,
+ @SerialName("text")
+ val text: String
+ )
+
+ fun toMockFollowerModel() : List = data.map { user ->
+ MockFollowerModel(
+ id = user.id,
+ name = "${user.firstName} ${user.lastName}",
+ email = user.email,
+ image = user.avatar
+ )
+ }
+}
\ No newline at end of file
diff --git a/data/src/main/java/com/going/data/repositoryImpl/MockRepositoryImpl.kt b/data/src/main/java/com/going/data/repositoryImpl/MockRepositoryImpl.kt
new file mode 100644
index 00000000..039fa9e4
--- /dev/null
+++ b/data/src/main/java/com/going/data/repositoryImpl/MockRepositoryImpl.kt
@@ -0,0 +1,20 @@
+package com.going.data.repositoryImpl
+
+import com.going.data.datasource.MockDataSource
+import com.going.domain.entity.response.MockFollowerModel
+import com.going.domain.repository.MockRepository
+import javax.inject.Inject
+
+class MockRepositoryImpl @Inject constructor(
+ private val mockDataSource: MockDataSource
+) : MockRepository {
+
+ override suspend fun getFollowerList(
+ page: Int
+ ): Result> {
+ return runCatching {
+ mockDataSource.getFollowerListData(page).toMockFollowerModel()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/data/src/main/java/com/going/data/service/MockService.kt b/data/src/main/java/com/going/data/service/MockService.kt
new file mode 100644
index 00000000..d2a7fcd4
--- /dev/null
+++ b/data/src/main/java/com/going/data/service/MockService.kt
@@ -0,0 +1,14 @@
+package com.going.data.service
+
+import com.going.data.dto.response.MockFollowerResponseDto
+import retrofit2.http.GET
+import retrofit2.http.Query
+
+interface MockService {
+
+ @GET("api/users")
+ suspend fun getFollowerList(
+ @Query("page") page: Int
+ ): MockFollowerResponseDto
+
+}
\ No newline at end of file
diff --git a/data/src/test/java/com/going/data/ExampleUnitTest.kt b/data/src/test/java/com/going/data/ExampleUnitTest.kt
new file mode 100644
index 00000000..c9583f73
--- /dev/null
+++ b/data/src/test/java/com/going/data/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.going.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 00000000..42afabfd
--- /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 00000000..649449cf
--- /dev/null
+++ b/domain/build.gradle.kts
@@ -0,0 +1,18 @@
+plugins {
+ id("java-library")
+ kotlin("jvm")
+ kotlin("kapt")
+}
+
+java {
+ sourceCompatibility = Versions.javaVersion
+ targetCompatibility = Versions.javaVersion
+}
+
+dependencies {
+ KotlinDependencies.run {
+ implementation(kotlin)
+ implementation(coroutines)
+ implementation(dateTime)
+ }
+}
\ No newline at end of file
diff --git a/domain/src/main/kotlin/com/going/domain/entity/response/MockFollowerModel.kt b/domain/src/main/kotlin/com/going/domain/entity/response/MockFollowerModel.kt
new file mode 100644
index 00000000..06654205
--- /dev/null
+++ b/domain/src/main/kotlin/com/going/domain/entity/response/MockFollowerModel.kt
@@ -0,0 +1,8 @@
+package com.going.domain.entity.response
+
+data class MockFollowerModel(
+ val id: Int,
+ val name: String,
+ val email: String,
+ val image: String
+)
\ No newline at end of file
diff --git a/domain/src/main/kotlin/com/going/domain/repository/MockRepository.kt b/domain/src/main/kotlin/com/going/domain/repository/MockRepository.kt
new file mode 100644
index 00000000..92d9263e
--- /dev/null
+++ b/domain/src/main/kotlin/com/going/domain/repository/MockRepository.kt
@@ -0,0 +1,11 @@
+package com.going.domain.repository
+
+import com.going.domain.entity.response.MockFollowerModel
+
+interface MockRepository {
+
+ suspend fun getFollowerList(
+ page: Int
+ ): Result>
+
+}
\ No newline at end of file
diff --git a/presentation/.gitignore b/presentation/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/presentation/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/presentation/build.gradle.kts b/presentation/build.gradle.kts
new file mode 100644
index 00000000..391da731
--- /dev/null
+++ b/presentation/build.gradle.kts
@@ -0,0 +1,87 @@
+plugins {
+ id("com.android.library")
+ kotlin("android")
+ kotlin("kapt")
+ id("kotlin-parcelize")
+ id("dagger.hilt.android.plugin")
+}
+
+android {
+ namespace = "com.going.presentation"
+ compileSdk = Constants.compileSdk
+
+ defaultConfig {
+ minSdk = Constants.minSdk
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ compileOptions {
+ sourceCompatibility = Versions.javaVersion
+ targetCompatibility = Versions.javaVersion
+ }
+
+ kotlinOptions {
+ jvmTarget = Versions.jvmVersion
+ }
+
+ buildFeatures {
+ buildConfig = true
+ dataBinding = true
+ viewBinding = true
+ }
+}
+
+dependencies {
+ implementation(project(":core-ui"))
+ implementation(project(":domain"))
+
+ KotlinDependencies.run {
+ implementation(kotlin)
+ implementation(coroutines)
+ implementation(jsonSerialization)
+ implementation(dateTime)
+ }
+
+ AndroidXDependencies.run {
+ implementation(coreKtx)
+ implementation(appCompat)
+ implementation(constraintLayout)
+ implementation(fragment)
+ implementation(startup)
+ implementation(legacy)
+ implementation(security)
+ implementation(hilt)
+ implementation(lifeCycleKtx)
+ implementation(lifecycleJava8)
+ implementation(splashScreen)
+ implementation(pagingRuntime)
+ implementation(workManager)
+ implementation(hiltWorkManager)
+ }
+
+ KaptDependencies.run {
+ kapt(hiltCompiler)
+ kapt(hiltWorkManagerCompiler)
+ }
+
+ implementation(MaterialDesignDependencies.materialDesign)
+
+ TestDependencies.run {
+ testImplementation(jUnit)
+ androidTestImplementation(androidTest)
+ androidTestImplementation(espresso)
+ }
+
+ ThirdPartyDependencies.run {
+ implementation(coil)
+ implementation(timber)
+ implementation(ossLicense)
+ implementation(progressView)
+ implementation(balloon)
+ implementation(lottie)
+ implementation(circularProgressBar)
+ implementation(circleIndicator)
+ }
+}
\ No newline at end of file
diff --git a/presentation/consumer-rules.pro b/presentation/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/presentation/src/androidTest/java/com/going/presentation/ExampleInstrumentedTest.kt b/presentation/src/androidTest/java/com/going/presentation/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..61b3ad9c
--- /dev/null
+++ b/presentation/src/androidTest/java/com/going/presentation/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.going.presentation
+
+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.going.presentation", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/presentation/src/main/AndroidManifest.xml b/presentation/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5918e68
--- /dev/null
+++ b/presentation/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/presentation/src/main/java/com/going/presentation/mock/MockActivity.kt b/presentation/src/main/java/com/going/presentation/mock/MockActivity.kt
new file mode 100644
index 00000000..b34761d9
--- /dev/null
+++ b/presentation/src/main/java/com/going/presentation/mock/MockActivity.kt
@@ -0,0 +1,62 @@
+package com.going.presentation.mock
+
+import android.os.Bundle
+import androidx.activity.viewModels
+import androidx.lifecycle.flowWithLifecycle
+import androidx.lifecycle.lifecycleScope
+import com.going.presentation.R
+import com.going.presentation.databinding.ActivityMockBinding
+import com.going.ui.base.BaseActivity
+import com.going.ui.extension.UiState
+import com.going.ui.extension.toast
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+@AndroidEntryPoint
+class MockActivity() : BaseActivity(R.layout.activity_mock) {
+
+ private var _adapter: MockAdapter? = null
+ private val adapter
+ get() = requireNotNull(_adapter) { getString(R.string.adapter_not_initialized_error_msg) }
+
+ private val viewModel by viewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ initAdapter()
+ setFollowerList()
+ observeFollowerListState()
+ }
+
+ private fun initAdapter() {
+ _adapter = MockAdapter()
+ binding.rvFollower.adapter = adapter
+ }
+
+ private fun setFollowerList() {
+ viewModel.getFollowerListFromServer(0)
+ }
+
+ private fun observeFollowerListState() {
+ viewModel.followerListState.flowWithLifecycle(lifecycle)
+ .onEach { state ->
+ when (state) {
+ is UiState.Success -> adapter.submitList(state.data)
+
+ is UiState.Failure -> toast(getString(R.string.server_error))
+
+ is UiState.Loading -> return@onEach
+
+ is UiState.Empty -> return@onEach
+ }
+ }.launchIn(lifecycleScope)
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ _adapter = null
+ }
+
+}
\ No newline at end of file
diff --git a/presentation/src/main/java/com/going/presentation/mock/MockAdapter.kt b/presentation/src/main/java/com/going/presentation/mock/MockAdapter.kt
new file mode 100644
index 00000000..1d49fe1a
--- /dev/null
+++ b/presentation/src/main/java/com/going/presentation/mock/MockAdapter.kt
@@ -0,0 +1,34 @@
+package com.going.presentation.mock
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.ListAdapter
+import com.going.domain.entity.response.MockFollowerModel
+import com.going.presentation.databinding.ItemMockBinding
+import com.going.ui.extension.ItemDiffCallback
+
+class MockAdapter : ListAdapter(diffUtil) {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MockViewHolder {
+ val binding: ItemMockBinding =
+ ItemMockBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ return MockViewHolder(binding)
+ }
+
+ override fun onBindViewHolder(holder: MockViewHolder, position: Int) {
+ holder.onBind(getItem(position))
+ }
+
+ fun addList(newItems: List) {
+ val currentItems = currentList.toMutableList()
+ currentItems.addAll(newItems)
+ submitList(currentItems)
+ }
+
+ companion object {
+ private val diffUtil = ItemDiffCallback(
+ onItemsTheSame = { old, new -> old.id == new.id },
+ onContentsTheSame = { old, new -> old == new },
+ )
+ }
+}
\ No newline at end of file
diff --git a/presentation/src/main/java/com/going/presentation/mock/MockViewHolder.kt b/presentation/src/main/java/com/going/presentation/mock/MockViewHolder.kt
new file mode 100644
index 00000000..2f13a343
--- /dev/null
+++ b/presentation/src/main/java/com/going/presentation/mock/MockViewHolder.kt
@@ -0,0 +1,21 @@
+package com.going.presentation.mock
+
+import androidx.recyclerview.widget.RecyclerView
+import coil.load
+import coil.transform.RoundedCornersTransformation
+import com.going.domain.entity.response.MockFollowerModel
+import com.going.presentation.databinding.ItemMockBinding
+
+class MockViewHolder(val binding: ItemMockBinding) : RecyclerView.ViewHolder(binding.root) {
+
+ fun onBind(item: MockFollowerModel) {
+ binding.run {
+ tvFollowerName.text = item.name
+ tvFollowerEmail.text = item.email
+ ivFollowerImage.load(item.image) {
+ transformations(RoundedCornersTransformation(10.0F))
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/presentation/src/main/java/com/going/presentation/mock/MockViewModel.kt b/presentation/src/main/java/com/going/presentation/mock/MockViewModel.kt
new file mode 100644
index 00000000..59df791c
--- /dev/null
+++ b/presentation/src/main/java/com/going/presentation/mock/MockViewModel.kt
@@ -0,0 +1,34 @@
+package com.going.presentation.mock
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.going.domain.entity.response.MockFollowerModel
+import com.going.domain.repository.MockRepository
+import com.going.ui.extension.UiState
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class MockViewModel @Inject constructor(
+ private val mockRepository: MockRepository
+) : ViewModel() {
+
+ private val _followerListState = MutableStateFlow>>(UiState.Empty)
+ val followerListState: StateFlow>> = _followerListState
+
+ fun getFollowerListFromServer(page: Int) {
+ _followerListState.value = UiState.Loading
+ viewModelScope.launch {
+ mockRepository.getFollowerList(page)
+ .onSuccess { response ->
+ _followerListState.value = UiState.Success(response)
+ }
+ .onFailure {
+ _followerListState.value = UiState.Failure(it.message.orEmpty())
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/presentation/src/main/res/drawable-v24/ic_launcher_foreground.xml
similarity index 100%
rename from app/src/main/res/drawable-v24/ic_launcher_foreground.xml
rename to presentation/src/main/res/drawable-v24/ic_launcher_foreground.xml
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/presentation/src/main/res/drawable/ic_launcher_background.xml
similarity index 100%
rename from app/src/main/res/drawable/ic_launcher_background.xml
rename to presentation/src/main/res/drawable/ic_launcher_background.xml
diff --git a/presentation/src/main/res/layout/activity_mock.xml b/presentation/src/main/res/layout/activity_mock.xml
new file mode 100644
index 00000000..19d7b853
--- /dev/null
+++ b/presentation/src/main/res/layout/activity_mock.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/presentation/src/main/res/layout/item_mock.xml b/presentation/src/main/res/layout/item_mock.xml
new file mode 100644
index 00000000..cb4e3f6f
--- /dev/null
+++ b/presentation/src/main/res/layout/item_mock.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/presentation/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
similarity index 100%
rename from app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
rename to presentation/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/presentation/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
similarity index 100%
rename from app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
rename to presentation/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/presentation/src/main/res/mipmap-hdpi/ic_launcher.webp
similarity index 100%
rename from app/src/main/res/mipmap-hdpi/ic_launcher.webp
rename to presentation/src/main/res/mipmap-hdpi/ic_launcher.webp
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/presentation/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 presentation/src/main/res/mipmap-hdpi/ic_launcher_round.webp
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/presentation/src/main/res/mipmap-mdpi/ic_launcher.webp
similarity index 100%
rename from app/src/main/res/mipmap-mdpi/ic_launcher.webp
rename to presentation/src/main/res/mipmap-mdpi/ic_launcher.webp
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/presentation/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 presentation/src/main/res/mipmap-mdpi/ic_launcher_round.webp
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/presentation/src/main/res/mipmap-xhdpi/ic_launcher.webp
similarity index 100%
rename from app/src/main/res/mipmap-xhdpi/ic_launcher.webp
rename to presentation/src/main/res/mipmap-xhdpi/ic_launcher.webp
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/presentation/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 presentation/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/presentation/src/main/res/mipmap-xxhdpi/ic_launcher.webp
similarity index 100%
rename from app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
rename to presentation/src/main/res/mipmap-xxhdpi/ic_launcher.webp
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/presentation/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 presentation/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/presentation/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
similarity index 100%
rename from app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
rename to presentation/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/presentation/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 presentation/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
diff --git a/app/src/main/res/values/colors.xml b/presentation/src/main/res/values/colors.xml
similarity index 100%
rename from app/src/main/res/values/colors.xml
rename to presentation/src/main/res/values/colors.xml
diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml
new file mode 100644
index 00000000..af32c4f5
--- /dev/null
+++ b/presentation/src/main/res/values/strings.xml
@@ -0,0 +1,11 @@
+
+ doorip
+
+ Unknown View Type : %d
+ Adapter is not initialized
+ %s
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
+ 서버 통신에 실패했습니다.
+
+
diff --git a/app/src/main/res/values/themes.xml b/presentation/src/main/res/values/themes.xml
similarity index 81%
rename from app/src/main/res/values/themes.xml
rename to presentation/src/main/res/values/themes.xml
index 039e9f5d..61eaf21b 100644
--- a/app/src/main/res/values/themes.xml
+++ b/presentation/src/main/res/values/themes.xml
@@ -1,6 +1,6 @@
-
\ No newline at end of file
diff --git a/presentation/src/test/java/com/going/presentation/ExampleUnitTest.kt b/presentation/src/test/java/com/going/presentation/ExampleUnitTest.kt
new file mode 100644
index 00000000..9feb3e5f
--- /dev/null
+++ b/presentation/src/test/java/com/going/presentation/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.going.presentation
+
+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/settings.gradle b/settings.gradle.kts
similarity index 75%
rename from settings.gradle
rename to settings.gradle.kts
index 4dd996fd..fac4e87a 100644
--- a/settings.gradle
+++ b/settings.gradle.kts
@@ -5,6 +5,7 @@ pluginManagement {
gradlePluginPortal()
}
}
+
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
@@ -12,5 +13,11 @@ dependencyResolutionManagement {
mavenCentral()
}
}
+
rootProject.name = "GoingGoing"
-include ':app'
+
+include(":app")
+include(":core-ui")
+include(":data")
+include(":domain")
+include(":presentation")