├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ └── themes.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── values-night │ │ │ │ └── themes.xml │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ └── drawable │ │ │ │ └── ic_launcher_background.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── pss │ │ │ │ └── check_percentage │ │ │ │ ├── utils │ │ │ │ └── Utils.kt │ │ │ │ └── di │ │ │ │ ├── App.kt │ │ │ │ ├── FirebaseModule.kt │ │ │ │ ├── RepositoryModule.kt │ │ │ │ ├── DataSourceModule.kt │ │ │ │ └── NetworkModule.kt │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── pss │ │ │ └── check_percentage │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── pss │ │ └── check_percentage │ │ └── ExampleInstrumentedTest.kt ├── proguard-rules.pro └── build.gradle.kts ├── data ├── .gitignore ├── consumer-rules.pro ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── pss │ │ │ └── data │ │ │ ├── remote │ │ │ ├── model │ │ │ │ ├── DataLoveResponse.kt │ │ │ │ └── DataScore.kt │ │ │ └── api │ │ │ │ └── LoveCalculatorApi.kt │ │ │ ├── repository │ │ │ ├── remote │ │ │ │ ├── datasource │ │ │ │ │ ├── SplashDataSource.kt │ │ │ │ │ └── MainDataSource.kt │ │ │ │ └── datasourceimpl │ │ │ │ │ ├── SplashDataSourceImpl.kt │ │ │ │ │ └── MainDataSourceImpl.kt │ │ │ ├── SplashRepositoryImpl.kt │ │ │ └── MainRepositoryImpl.kt │ │ │ ├── mapper │ │ │ ├── FirebaseMapper.kt │ │ │ └── MainMapper.kt │ │ │ └── utils │ │ │ └── base │ │ │ └── BaseDataSource.kt │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── pss │ │ │ └── data │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── pss │ │ └── data │ │ └── ExampleInstrumentedTest.kt ├── proguard-rules.pro └── build.gradle.kts ├── domain ├── .gitignore ├── consumer-rules.pro ├── src │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── pss │ │ │ │ └── domain │ │ │ │ ├── utils │ │ │ │ ├── ScreenState.kt │ │ │ │ ├── FirebaseState.kt │ │ │ │ ├── ErrorType.kt │ │ │ │ └── RemoteErrorEmitter.kt │ │ │ │ ├── model │ │ │ │ ├── DomainLoveResponse.kt │ │ │ │ ├── DomainScore.kt │ │ │ │ └── GetFirebaseResponse.kt │ │ │ │ ├── repository │ │ │ │ ├── SplashRepository.kt │ │ │ │ └── MainRepository.kt │ │ │ │ └── usecase │ │ │ │ ├── GetScoreUseCase.kt │ │ │ │ ├── CheckAppVersionUseCase.kt │ │ │ │ ├── SetStatisticsUseCase.kt │ │ │ │ ├── GetStatisticsUseCase.kt │ │ │ │ ├── SetScoreUseCase.kt │ │ │ │ └── CheckLoveCalculatorUseCase.kt │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── pss │ │ │ └── domain │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── pss │ │ └── domain │ │ └── ExampleInstrumentedTest.kt ├── build.gradle.kts └── proguard-rules.pro ├── presentation ├── .gitignore ├── consumer-rules.pro ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ └── colors.xml │ │ │ ├── drawable │ │ │ │ ├── main_btn_frame.xml │ │ │ │ ├── name_setting_btn_frame.xml │ │ │ │ ├── ic_result_love.xml │ │ │ │ └── ic_love_main.xml │ │ │ ├── anim │ │ │ │ ├── slide_in_left.xml │ │ │ │ ├── slide_in_right.xml │ │ │ │ ├── slide_out_left.xml │ │ │ │ └── slide_out_right.xml │ │ │ ├── layout │ │ │ │ ├── activity_main.xml │ │ │ │ ├── activity_splash.xml │ │ │ │ ├── fragment_woman_name.xml │ │ │ │ ├── fragment_man_name.xml │ │ │ │ ├── fragment_result.xml │ │ │ │ ├── score_recycler_view_item.xml │ │ │ │ └── fragment_main.xml │ │ │ └── navigation │ │ │ │ └── main_nav_graph.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── pss │ │ │ │ └── presentation │ │ │ │ ├── viewmodel │ │ │ │ ├── SplashViewModel.kt │ │ │ │ └── MainViewModel.kt │ │ │ │ ├── view │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── WomanNameFragment.kt │ │ │ │ ├── SplashActivity.kt │ │ │ │ ├── MainFragment.kt │ │ │ │ ├── ManNameFragment.kt │ │ │ │ └── ResultFragment.kt │ │ │ │ ├── widget │ │ │ │ ├── extension │ │ │ │ │ ├── ActivityExtension.kt │ │ │ │ │ └── FragmentExtension.kt │ │ │ │ └── utils │ │ │ │ │ └── SingleLiveEvent.kt │ │ │ │ ├── adapter │ │ │ │ ├── ScoreBindingAdapter.kt │ │ │ │ └── ScoreRecyclerViewAdapter.kt │ │ │ │ └── base │ │ │ │ ├── BaseActivity.kt │ │ │ │ └── BaseFragment.kt │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── pss │ │ │ └── presentation │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── pss │ │ └── presentation │ │ └── ExampleInstrumentedTest.kt ├── proguard-rules.pro └── build.gradle.kts ├── .idea ├── .gitignore ├── compiler.xml ├── vcs.xml ├── discord.xml ├── gradle.xml └── misc.xml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── settings.gradle ├── gradle.properties ├── gradlew.bat ├── README.md └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /data/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /domain/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /presentation/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /presentation/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | CheckPercentage 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParkSangSun1/Check_Percentage/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParkSangSun1/Check_Percentage/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParkSangSun1/Check_Percentage/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParkSangSun1/Check_Percentage/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /domain/src/main/java/com/pss/domain/utils/ScreenState.kt: -------------------------------------------------------------------------------- 1 | package com.pss.domain.utils 2 | 3 | enum class ScreenState { 4 | LOADING, 5 | ERROR 6 | } -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParkSangSun1/Check_Percentage/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParkSangSun1/Check_Percentage/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /domain/src/main/java/com/pss/domain/utils/FirebaseState.kt: -------------------------------------------------------------------------------- 1 | package com.pss.domain.utils 2 | 3 | enum class FirebaseState { 4 | SUCCESS, 5 | FAILURE 6 | } -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParkSangSun1/Check_Percentage/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParkSangSun1/Check_Percentage/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParkSangSun1/Check_Percentage/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParkSangSun1/Check_Percentage/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParkSangSun1/Check_Percentage/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /data/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /domain/src/main/java/com/pss/domain/utils/ErrorType.kt: -------------------------------------------------------------------------------- 1 | package com.pss.domain.utils 2 | 3 | enum class ErrorType { 4 | NETWORK, 5 | TIMEOUT, 6 | SESSION_EXPIRED, 7 | UNKNOWN 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/pss/check_percentage/utils/Utils.kt: -------------------------------------------------------------------------------- 1 | package com.pss.check_percentage.utils 2 | 3 | object Utils { 4 | const val BASE_URL = "https://love-calculator.p.rapidapi.com/" 5 | } 6 | -------------------------------------------------------------------------------- /domain/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /presentation/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hello blank fragment 4 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /domain/src/main/java/com/pss/domain/utils/RemoteErrorEmitter.kt: -------------------------------------------------------------------------------- 1 | package com.pss.domain.utils 2 | 3 | interface RemoteErrorEmitter { 4 | fun onError(msg: String) 5 | fun onError(errorType: ErrorType) 6 | } -------------------------------------------------------------------------------- /domain/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id ("org.jetbrains.kotlin.jvm") 3 | id ("kotlin-kapt") 4 | } 5 | 6 | dependencies { 7 | 8 | // dager hilt 9 | implementation (DaggerHilt.DAGGER_HILT_JAVAX) 10 | } -------------------------------------------------------------------------------- /.idea/discord.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /domain/src/main/java/com/pss/domain/model/DomainLoveResponse.kt: -------------------------------------------------------------------------------- 1 | package com.pss.domain.model 2 | 3 | data class DomainLoveResponse( 4 | val fname : String, 5 | val sname : String, 6 | val percentage : Int, 7 | val result : String 8 | ) -------------------------------------------------------------------------------- /data/src/main/java/com/pss/data/remote/model/DataLoveResponse.kt: -------------------------------------------------------------------------------- 1 | package com.pss.data.remote.model 2 | 3 | data class DataLoveResponse( 4 | val fname : String, 5 | val sname : String, 6 | val percentage : Int, 7 | val result : String 8 | ) -------------------------------------------------------------------------------- /presentation/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FF8686 4 | #FF000000 5 | #FFFFFFFF 6 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/main_btn_frame.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /domain/src/main/java/com/pss/domain/repository/SplashRepository.kt: -------------------------------------------------------------------------------- 1 | package com.pss.domain.repository 2 | 3 | import com.pss.domain.model.GetFirebaseResponse 4 | 5 | interface SplashRepository { 6 | suspend fun checkAppVersion() : GetFirebaseResponse 7 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/pss/domain/model/DomainScore.kt: -------------------------------------------------------------------------------- 1 | package com.pss.domain.model 2 | 3 | data class DomainScore( 4 | val man : String, 5 | val woman : String, 6 | val percentage : Int, 7 | val date : String 8 | ){ 9 | constructor() : this("오류","오류",0,"오류") 10 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 13 17:01:11 KST 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /data/src/main/java/com/pss/data/remote/model/DataScore.kt: -------------------------------------------------------------------------------- 1 | package com.pss.data.remote.model 2 | 3 | data class DataScore( 4 | val man : String, 5 | val woman : String, 6 | val percentage : Int, 7 | val date : String 8 | ){ 9 | constructor() : this("오류","오류",0,"오류") 10 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/name_setting_btn_frame.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /domain/src/main/java/com/pss/domain/model/GetFirebaseResponse.kt: -------------------------------------------------------------------------------- 1 | package com.pss.domain.model 2 | 3 | import com.pss.domain.utils.FirebaseState 4 | 5 | data class GetFirebaseResponse( 6 | val state : FirebaseState, 7 | val result : T? 8 | ) 9 | 10 | data class SetFirebaseResponse( 11 | val state : FirebaseState 12 | ) -------------------------------------------------------------------------------- /domain/src/main/java/com/pss/domain/usecase/GetScoreUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.pss.domain.usecase 2 | 3 | import com.pss.domain.repository.MainRepository 4 | import javax.inject.Inject 5 | 6 | class GetScoreUseCase @Inject constructor( 7 | private val mainRepository: MainRepository 8 | ) { 9 | suspend fun execute() = mainRepository.getScore() 10 | } -------------------------------------------------------------------------------- /presentation/src/main/res/anim/slide_in_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /presentation/src/main/res/anim/slide_in_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /presentation/src/main/res/anim/slide_out_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /presentation/src/main/res/anim/slide_out_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /domain/src/main/java/com/pss/domain/usecase/CheckAppVersionUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.pss.domain.usecase 2 | 3 | import com.pss.domain.repository.SplashRepository 4 | import javax.inject.Inject 5 | 6 | class CheckAppVersionUseCase @Inject constructor( 7 | private val splashRepository: SplashRepository 8 | ) { 9 | suspend fun execute() = splashRepository.checkAppVersion() 10 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/pss/domain/usecase/SetStatisticsUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.pss.domain.usecase 2 | 3 | import com.pss.domain.repository.MainRepository 4 | import javax.inject.Inject 5 | 6 | class SetStatisticsUseCase @Inject constructor( 7 | private val mainRepository: MainRepository 8 | ) { 9 | suspend fun execute(plusResult: Int) = mainRepository.setStatistics(plusResult) 10 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/pss/domain/usecase/GetStatisticsUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.pss.domain.usecase 2 | 3 | import com.pss.domain.repository.MainRepository 4 | import com.pss.domain.utils.RemoteErrorEmitter 5 | import javax.inject.Inject 6 | 7 | class GetStatisticsUseCase @Inject constructor( 8 | private val mainRepository: MainRepository 9 | ) { 10 | suspend fun execute() = mainRepository.getStatistics() 11 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/pss/domain/usecase/SetScoreUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.pss.domain.usecase 2 | 3 | import com.pss.domain.model.DomainScore 4 | import com.pss.domain.repository.MainRepository 5 | import javax.inject.Inject 6 | 7 | class SetScoreUseCase @Inject constructor( 8 | private val mainRepository: MainRepository 9 | ) { 10 | suspend fun execute(score: DomainScore) = mainRepository.setScore(score) 11 | } -------------------------------------------------------------------------------- /data/src/main/java/com/pss/data/repository/remote/datasource/SplashDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.pss.data.repository.remote.datasource 2 | 3 | import com.google.android.gms.tasks.Task 4 | import com.google.firebase.database.DataSnapshot 5 | import com.pss.domain.model.DomainScore 6 | import com.pss.domain.model.GetFirebaseResponse 7 | 8 | interface SplashDataSource { 9 | suspend fun checkAppVersion() : GetFirebaseResponse 10 | } -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /data/src/test/java/com/pss/data/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.pss.data 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /domain/src/test/java/com/pss/domain/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.pss.domain 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 3 | repositories { 4 | google() 5 | mavenCentral() 6 | jcenter() // Warning: this repository is going to shut down soon 7 | maven { url 'https://jitpack.io' } 8 | } 9 | } 10 | rootProject.name = "Check_Percentage" 11 | include ':app' 12 | include ':domain' 13 | include ':data' 14 | include ':presentation' 15 | -------------------------------------------------------------------------------- /app/src/test/java/com/pss/check_percentage/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.pss.check_percentage 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /presentation/src/test/java/com/pss/presentation/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.pss.presentation 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/pss/check_percentage/di/App.kt: -------------------------------------------------------------------------------- 1 | package com.pss.check_percentage.di 2 | 3 | import android.app.Application 4 | import dagger.hilt.android.HiltAndroidApp 5 | 6 | @HiltAndroidApp 7 | class App : Application(){ 8 | companion object { 9 | private lateinit var application: App 10 | fun getInstance() : App = application 11 | } 12 | 13 | override fun onCreate(){ 14 | super.onCreate() 15 | application = this 16 | } 17 | } -------------------------------------------------------------------------------- /data/src/main/java/com/pss/data/mapper/FirebaseMapper.kt: -------------------------------------------------------------------------------- 1 | package com.pss.data.mapper 2 | 3 | import com.google.firebase.database.DataSnapshot 4 | import com.pss.domain.model.GetFirebaseResponse 5 | 6 | object FirebaseMapper { 7 | 8 | fun GetFirebaseResponse.toResultString(): GetFirebaseResponse { 9 | return GetFirebaseResponse( 10 | result = this.result?.value.toString(), 11 | state = this.state 12 | ) 13 | } 14 | } -------------------------------------------------------------------------------- /presentation/src/main/java/com/pss/presentation/viewmodel/SplashViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.pss.presentation.viewmodel 2 | 3 | import androidx.lifecycle.ViewModel 4 | import com.pss.domain.usecase.CheckAppVersionUseCase 5 | import dagger.hilt.android.lifecycle.HiltViewModel 6 | import javax.inject.Inject 7 | 8 | @HiltViewModel 9 | class SplashViewModel @Inject constructor( 10 | private val checkAppVersionUseCase: CheckAppVersionUseCase 11 | ): ViewModel() { 12 | 13 | suspend fun checkAppVersion() = checkAppVersionUseCase.execute() 14 | } 15 | -------------------------------------------------------------------------------- /domain/src/main/java/com/pss/domain/usecase/CheckLoveCalculatorUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.pss.domain.usecase 2 | 3 | import com.pss.domain.repository.MainRepository 4 | import com.pss.domain.utils.RemoteErrorEmitter 5 | import javax.inject.Inject 6 | 7 | class CheckLoveCalculatorUseCase @Inject constructor( 8 | private val mainRepository: MainRepository 9 | ) { 10 | suspend fun execute(remoteErrorEmitter: RemoteErrorEmitter, host : String, key : String, mName : String, wName : String) = mainRepository.checkLoveCalculator(remoteErrorEmitter, host, key, mName, wName) 11 | } -------------------------------------------------------------------------------- /presentation/src/main/java/com/pss/presentation/view/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.pss.presentation.view 2 | 3 | import com.pss.barlibrary.CustomBar.Companion.setTransparentBar 4 | import com.pss.presentation.R 5 | import com.pss.presentation.base.BaseActivity 6 | import com.pss.presentation.databinding.ActivityMainBinding 7 | import dagger.hilt.android.AndroidEntryPoint 8 | 9 | @AndroidEntryPoint 10 | class MainActivity : BaseActivity(R.layout.activity_main) { 11 | 12 | 13 | override fun init() { 14 | setTransparentBar(this) 15 | } 16 | } -------------------------------------------------------------------------------- /presentation/src/main/java/com/pss/presentation/widget/extension/ActivityExtension.kt: -------------------------------------------------------------------------------- 1 | package com.pss.presentation.widget.extension 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.recyclerview.widget.LinearLayoutManager 7 | import androidx.recyclerview.widget.RecyclerView 8 | 9 | //Activity Intent 10 | fun AppCompatActivity.startActivityWithFinish(context: Context, activity: Class<*>) { 11 | startActivity(Intent(context, activity).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)) 12 | this.finish() 13 | } -------------------------------------------------------------------------------- /data/src/main/java/com/pss/data/remote/api/LoveCalculatorApi.kt: -------------------------------------------------------------------------------- 1 | package com.pss.data.remote.api 2 | 3 | import com.pss.data.remote.model.DataLoveResponse 4 | import retrofit2.Response 5 | import retrofit2.http.GET 6 | import retrofit2.http.Header 7 | import retrofit2.http.Query 8 | 9 | interface LoveCalculatorApi { 10 | @GET("/getPercentage") 11 | suspend fun getPercentage( 12 | @Header("x-rapidapi-key") key: String, 13 | @Header("x-rapidapi-host") host: String, 14 | @Query("sname") sName : String, 15 | @Query("fname") fName : String 16 | ) : Response 17 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/pss/presentation/widget/extension/FragmentExtension.kt: -------------------------------------------------------------------------------- 1 | package com.pss.presentation.widget.extension 2 | 3 | import android.content.Context 4 | import androidx.recyclerview.widget.LinearLayoutManager 5 | import androidx.recyclerview.widget.RecyclerView 6 | 7 | //Vertical RecyclerView 8 | fun RecyclerView.showVertical(context: Context){ 9 | this.layoutManager = 10 | LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) 11 | } 12 | 13 | //Horizontal RecyclerView 14 | fun RecyclerView.showHorizontal(context: Context){ 15 | this.layoutManager = 16 | LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) 17 | } -------------------------------------------------------------------------------- /presentation/src/main/java/com/pss/presentation/adapter/ScoreBindingAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.pss.presentation.adapter 2 | 3 | import android.widget.TextView 4 | import androidx.databinding.BindingAdapter 5 | 6 | object ScoreBindingAdapter { 7 | 8 | @JvmStatic 9 | @BindingAdapter("set_man") 10 | fun setMan(text: TextView, name: String) { 11 | text.text = name 12 | } 13 | 14 | @JvmStatic 15 | @BindingAdapter("set_woman") 16 | fun setWoman(text: TextView, name: String) { 17 | text.text = name 18 | } 19 | 20 | @JvmStatic 21 | @BindingAdapter("set_percentage") 22 | fun setPercentage(text: TextView, content: Int) { 23 | text.text = content.toString() 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/pss/check_percentage/di/FirebaseModule.kt: -------------------------------------------------------------------------------- 1 | package com.pss.check_percentage.di 2 | 3 | import com.google.firebase.database.FirebaseDatabase 4 | import com.google.firebase.firestore.FirebaseFirestore 5 | import dagger.Module 6 | import dagger.Provides 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.components.SingletonComponent 9 | import javax.inject.Singleton 10 | 11 | @Module 12 | @InstallIn(SingletonComponent::class) 13 | object FirebaseModule { 14 | 15 | @Provides 16 | @Singleton 17 | fun provideFirebaseRTDB() = FirebaseDatabase.getInstance() 18 | 19 | @Provides 20 | @Singleton 21 | fun provideFirebaseStore() : FirebaseFirestore { 22 | return FirebaseFirestore.getInstance() 23 | } 24 | } -------------------------------------------------------------------------------- /presentation/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /data/src/androidTest/java/com/pss/data/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.pss.data 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.pss.data.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /domain/src/androidTest/java/com/pss/domain/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.pss.domain 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.pss.domain.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/pss/check_percentage/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.pss.check_percentage 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.pss.quick_setup", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /presentation/src/androidTest/java/com/pss/presentation/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.pss.presentation 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.pss.presentation.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/pss/domain/repository/MainRepository.kt: -------------------------------------------------------------------------------- 1 | package com.pss.domain.repository 2 | 3 | import com.pss.domain.model.DomainLoveResponse 4 | import com.pss.domain.model.DomainScore 5 | import com.pss.domain.model.GetFirebaseResponse 6 | import com.pss.domain.model.SetFirebaseResponse 7 | import com.pss.domain.utils.RemoteErrorEmitter 8 | 9 | interface MainRepository { 10 | suspend fun checkLoveCalculator(remoteErrorEmitter: RemoteErrorEmitter, host : String, key : String, mName : String, wName : String) : DomainLoveResponse? 11 | 12 | suspend fun getStatistics() : GetFirebaseResponse 13 | 14 | suspend fun setStatistics(plusResult : Int) : SetFirebaseResponse 15 | 16 | suspend fun setScore(score: DomainScore) : SetFirebaseResponse 17 | 18 | suspend fun getScore(): GetFirebaseResponse> 19 | } -------------------------------------------------------------------------------- /data/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /domain/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts.kts.kts.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /presentation/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts.kts.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /data/src/main/java/com/pss/data/repository/remote/datasourceimpl/SplashDataSourceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.pss.data.repository.remote.datasourceimpl 2 | 3 | import com.google.firebase.database.FirebaseDatabase 4 | import com.google.firebase.firestore.FirebaseFirestore 5 | import com.pss.data.mapper.FirebaseMapper.toResultString 6 | import com.pss.data.repository.remote.datasource.SplashDataSource 7 | import com.pss.data.utils.base.BaseDataSource 8 | import com.pss.domain.model.GetFirebaseResponse 9 | import javax.inject.Inject 10 | 11 | class SplashDataSourceImpl @Inject constructor( 12 | private val firebaseRtdb: FirebaseDatabase, 13 | private val fireStore: FirebaseFirestore 14 | ) : SplashDataSource, BaseDataSource() { 15 | override suspend fun checkAppVersion() = safeGetFirebaseRTDBCall{ 16 | firebaseRtdb.reference.child("appVersion").get() 17 | }.toResultString() 18 | } -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /data/src/main/java/com/pss/data/repository/SplashRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.pss.data.repository 2 | 3 | import com.google.android.gms.tasks.Task 4 | import com.google.firebase.database.DataSnapshot 5 | import com.pss.data.mapper.MainMapper 6 | import com.pss.data.repository.remote.datasource.MainDataSource 7 | import com.pss.data.repository.remote.datasource.SplashDataSource 8 | import com.pss.domain.model.DomainLoveResponse 9 | import com.pss.domain.model.GetFirebaseResponse 10 | import com.pss.domain.repository.MainRepository 11 | import com.pss.domain.repository.SplashRepository 12 | import com.pss.domain.utils.RemoteErrorEmitter 13 | import javax.inject.Inject 14 | 15 | class SplashRepositoryImpl @Inject constructor( 16 | private val splashDataSource: SplashDataSource 17 | ) : SplashRepository { 18 | 19 | override suspend fun checkAppVersion(): GetFirebaseResponse { 20 | return splashDataSource.checkAppVersion() 21 | } 22 | } -------------------------------------------------------------------------------- /presentation/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 19 | -------------------------------------------------------------------------------- /data/src/main/java/com/pss/data/repository/remote/datasource/MainDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.pss.data.repository.remote.datasource 2 | 3 | import com.google.android.gms.tasks.Task 4 | import com.google.firebase.firestore.QuerySnapshot 5 | import com.pss.data.remote.model.DataLoveResponse 6 | import com.pss.data.remote.model.DataScore 7 | import com.pss.domain.model.DomainScore 8 | import com.pss.domain.model.GetFirebaseResponse 9 | import com.pss.domain.model.SetFirebaseResponse 10 | import com.pss.domain.utils.RemoteErrorEmitter 11 | 12 | interface MainDataSource { 13 | suspend fun checkLoveCalculator( 14 | remoteErrorEmitter: RemoteErrorEmitter, 15 | host: String, 16 | key: String, 17 | mName: String, 18 | wName: String 19 | ): DataLoveResponse? 20 | 21 | suspend fun getStatistics(): GetFirebaseResponse 22 | 23 | suspend fun setStatistics(plusResult: Int): SetFirebaseResponse 24 | 25 | suspend fun setScore(score: DataScore): SetFirebaseResponse 26 | 27 | suspend fun getScore(): GetFirebaseResponse> 28 | } -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 24 | 25 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/pss/presentation/view/WomanNameFragment.kt: -------------------------------------------------------------------------------- 1 | package com.pss.presentation.view 2 | 3 | import android.text.TextUtils 4 | import android.view.View 5 | import androidx.fragment.app.activityViewModels 6 | import androidx.navigation.fragment.findNavController 7 | import com.pss.presentation.R 8 | import com.pss.presentation.base.BaseFragment 9 | import com.pss.presentation.databinding.FragmentWomanNameBinding 10 | import com.pss.presentation.viewmodel.MainViewModel 11 | 12 | class WomanNameFragment : BaseFragment(R.layout.fragment_woman_name) { 13 | private val mainViewModel by activityViewModels() 14 | 15 | 16 | override fun init() { 17 | binding.fragment = this 18 | } 19 | 20 | fun nextBtnClick(view: View){ 21 | if (TextUtils.isEmpty(binding.editTxt.text.toString())) shortShowToast("이름을 입력해 주세요") 22 | else { 23 | mainViewModel.womanNameResult = binding.editTxt.text.toString() 24 | this.findNavController().navigate(R.id.action_womanNameFragment_to_manNameFragment) 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /data/src/main/java/com/pss/data/mapper/MainMapper.kt: -------------------------------------------------------------------------------- 1 | package com.pss.data.mapper 2 | 3 | import com.pss.data.remote.model.DataLoveResponse 4 | import com.pss.data.remote.model.DataScore 5 | import com.pss.domain.model.DomainLoveResponse 6 | import com.pss.domain.model.DomainScore 7 | import com.pss.domain.utils.ErrorType 8 | import com.pss.domain.utils.RemoteErrorEmitter 9 | 10 | object MainMapper { 11 | 12 | fun loveMapper( 13 | dataResponse: DataLoveResponse? 14 | ): DomainLoveResponse? { 15 | return if (dataResponse != null) { 16 | DomainLoveResponse( 17 | fname = dataResponse.fname, 18 | percentage = dataResponse.percentage, 19 | result = dataResponse.result, 20 | sname = dataResponse.sname 21 | ) 22 | } else dataResponse 23 | } 24 | 25 | fun scoreMapper( 26 | domainResponse : DomainScore 27 | ) : DataScore{ 28 | return DataScore( 29 | man = domainResponse.man, 30 | woman = domainResponse.woman, 31 | percentage = domainResponse.percentage, 32 | date = domainResponse.date 33 | ) 34 | } 35 | } -------------------------------------------------------------------------------- /presentation/src/main/res/layout/activity_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official -------------------------------------------------------------------------------- /app/src/main/java/com/pss/check_percentage/di/RepositoryModule.kt: -------------------------------------------------------------------------------- 1 | package com.pss.check_percentage.di 2 | 3 | import com.pss.data.repository.MainRepositoryImpl 4 | import com.pss.data.repository.SplashRepositoryImpl 5 | import com.pss.data.repository.remote.datasource.MainDataSource 6 | import com.pss.data.repository.remote.datasource.SplashDataSource 7 | import com.pss.data.repository.remote.datasourceimpl.MainDataSourceImpl 8 | import com.pss.data.repository.remote.datasourceimpl.SplashDataSourceImpl 9 | import com.pss.domain.repository.MainRepository 10 | import com.pss.domain.repository.SplashRepository 11 | import dagger.Module 12 | import dagger.Provides 13 | import dagger.hilt.InstallIn 14 | import dagger.hilt.components.SingletonComponent 15 | import javax.inject.Singleton 16 | 17 | @Module 18 | @InstallIn(SingletonComponent::class) 19 | class RepositoryModule { 20 | 21 | @Provides 22 | fun provideMainRepository( 23 | mainDataSource : MainDataSource 24 | ): MainRepository { 25 | return MainRepositoryImpl( 26 | mainDataSource 27 | ) 28 | } 29 | 30 | @Provides 31 | fun provideSplashRepository( 32 | splashDataSource: SplashDataSource 33 | ): SplashRepository { 34 | return SplashRepositoryImpl( 35 | splashDataSource 36 | ) 37 | } 38 | } -------------------------------------------------------------------------------- /presentation/src/main/java/com/pss/presentation/base/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package com.pss.presentation.base 2 | 3 | import android.os.Bundle 4 | import android.widget.Toast 5 | import androidx.annotation.LayoutRes 6 | import androidx.appcompat.app.AppCompatActivity 7 | import androidx.databinding.DataBindingUtil 8 | import androidx.databinding.ViewDataBinding 9 | 10 | //BaseActivity.kt 11 | abstract class BaseActivity(@LayoutRes private val layoutResId: Int) : 12 | AppCompatActivity() { 13 | protected lateinit var binding: T 14 | private var waitTime = 0L 15 | 16 | override fun onCreate(savedInstanceState: Bundle?) { 17 | super.onCreate(savedInstanceState) 18 | binding = DataBindingUtil.setContentView(this, layoutResId) 19 | init() 20 | } 21 | 22 | abstract fun init() 23 | 24 | override fun onBackPressed() { 25 | if (System.currentTimeMillis() - waitTime >= 1500) { 26 | waitTime = System.currentTimeMillis() 27 | Toast.makeText(this, "뒤로가기 버튼을 한번 더 누르면 종료됩니다.", Toast.LENGTH_SHORT).show() 28 | } else finish() 29 | } 30 | 31 | protected fun shortShowToast(msg: String) = 32 | Toast.makeText(this, msg, Toast.LENGTH_SHORT).show() 33 | 34 | protected fun longShowToast(msg: String) = 35 | Toast.makeText(this, msg, Toast.LENGTH_LONG).show() 36 | } -------------------------------------------------------------------------------- /presentation/src/main/java/com/pss/presentation/base/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | package com.pss.presentation.base 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.Toast 8 | import androidx.annotation.LayoutRes 9 | import androidx.databinding.DataBindingUtil 10 | import androidx.databinding.ViewDataBinding 11 | import androidx.fragment.app.Fragment 12 | 13 | abstract class BaseFragment( 14 | @LayoutRes val layoutId: Int 15 | ) : Fragment() { 16 | lateinit var binding: B 17 | 18 | override fun onCreateView( 19 | inflater: LayoutInflater, 20 | container: ViewGroup?, 21 | savedInstanceState: Bundle? 22 | ): View? { 23 | binding = DataBindingUtil.inflate(inflater, layoutId, container, false) 24 | init() 25 | return binding.root 26 | } 27 | 28 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 29 | super.onViewCreated(view, savedInstanceState) 30 | binding.lifecycleOwner = this 31 | } 32 | 33 | abstract fun init() 34 | 35 | protected fun shortShowToast(msg: String) = 36 | Toast.makeText(context, msg, Toast.LENGTH_SHORT).show() 37 | 38 | protected fun longShowToast(msg: String) = 39 | Toast.makeText(context, msg, Toast.LENGTH_LONG).show() 40 | } -------------------------------------------------------------------------------- /presentation/src/main/java/com/pss/presentation/widget/utils/SingleLiveEvent.kt: -------------------------------------------------------------------------------- 1 | package com.pss.presentation.widget.utils 2 | 3 | import android.util.Log 4 | import androidx.annotation.MainThread 5 | import androidx.annotation.Nullable 6 | import androidx.lifecycle.LifecycleOwner 7 | import androidx.lifecycle.MutableLiveData 8 | import androidx.lifecycle.Observer 9 | import java.util.concurrent.atomic.AtomicBoolean; 10 | 11 | 12 | class SingleLiveEvent : MutableLiveData() { 13 | 14 | companion object { 15 | private const val TAG = "SingleLiveEvent" 16 | } 17 | 18 | val mPending: AtomicBoolean = AtomicBoolean(false) 19 | 20 | override fun observe(owner: LifecycleOwner, observer: Observer) { 21 | if (hasActiveObservers()) { 22 | Log.w(TAG,"Multiple observers registered but only one will be notified of changes.") 23 | } 24 | 25 | // Observe the internal MutableLiveData 26 | super.observe(owner, Observer { t -> 27 | if (mPending.compareAndSet(true, false)) { 28 | observer.onChanged(t) 29 | } 30 | }) 31 | } 32 | 33 | @MainThread 34 | override fun setValue(@Nullable t: T?) { 35 | mPending.set(true) 36 | super.setValue(t) 37 | } 38 | 39 | /** 40 | * Used for cases where T is Void, to make calls cleaner. 41 | */ 42 | @MainThread 43 | fun call() { 44 | value = null 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /presentation/src/main/java/com/pss/presentation/view/SplashActivity.kt: -------------------------------------------------------------------------------- 1 | package com.pss.presentation.view 2 | 3 | import android.util.Log 4 | import androidx.activity.viewModels 5 | import androidx.lifecycle.lifecycleScope 6 | import com.pss.barlibrary.CustomBar.Companion.setTransparentBar 7 | import com.pss.domain.utils.FirebaseState 8 | import com.pss.presentation.R 9 | import com.pss.presentation.base.BaseActivity 10 | import com.pss.presentation.databinding.ActivitySplashBinding 11 | import com.pss.presentation.viewmodel.SplashViewModel 12 | import com.pss.presentation.widget.extension.startActivityWithFinish 13 | import dagger.hilt.android.AndroidEntryPoint 14 | import kotlinx.coroutines.launch 15 | 16 | @AndroidEntryPoint 17 | class SplashActivity : BaseActivity(R.layout.activity_splash) { 18 | private val splashViewModel by viewModels() 19 | private val appVersion = "1.0.0" 20 | 21 | override fun init() { 22 | setTransparentBar(this) 23 | checkAppVersion() 24 | } 25 | 26 | private fun checkAppVersion() = lifecycleScope.launch { 27 | with(splashViewModel.checkAppVersion()){ 28 | if (this.state == FirebaseState.SUCCESS){ 29 | if(appVersion == this.result) this@SplashActivity.startActivityWithFinish(this@SplashActivity, MainActivity::class.java) 30 | else longShowToast("앱 버전이 다릅니다!") 31 | }else shortShowToast("알수없는 오류가 발생했습니다") 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/pss/check_percentage/di/DataSourceModule.kt: -------------------------------------------------------------------------------- 1 | package com.pss.check_percentage.di 2 | 3 | import com.google.firebase.database.FirebaseDatabase 4 | import com.google.firebase.firestore.FirebaseFirestore 5 | import com.pss.data.remote.api.LoveCalculatorApi 6 | import com.pss.data.repository.remote.datasource.MainDataSource 7 | import com.pss.data.repository.remote.datasource.SplashDataSource 8 | import com.pss.data.repository.remote.datasourceimpl.MainDataSourceImpl 9 | import com.pss.data.repository.remote.datasourceimpl.SplashDataSourceImpl 10 | import dagger.Module 11 | import dagger.Provides 12 | import dagger.hilt.InstallIn 13 | import dagger.hilt.components.SingletonComponent 14 | import javax.inject.Singleton 15 | 16 | @Module 17 | @InstallIn(SingletonComponent::class) 18 | class DataSourceModule { 19 | 20 | @Provides 21 | fun provideMainDataSource( 22 | loveCalculatorApi: LoveCalculatorApi, 23 | firebaseRtdb : FirebaseDatabase, 24 | fireStore : FirebaseFirestore 25 | ) : MainDataSource { 26 | return MainDataSourceImpl( 27 | loveCalculatorApi, 28 | firebaseRtdb, fireStore 29 | ) 30 | } 31 | 32 | @Provides 33 | fun provideSplashDataSource( 34 | firebaseRtdb : FirebaseDatabase, 35 | fireStore : FirebaseFirestore 36 | ) : SplashDataSource { 37 | return SplashDataSourceImpl( 38 | firebaseRtdb = firebaseRtdb, fireStore = fireStore 39 | ) 40 | } 41 | } -------------------------------------------------------------------------------- /presentation/src/main/java/com/pss/presentation/adapter/ScoreRecyclerViewAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.pss.presentation.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.pss.domain.model.DomainScore 8 | import com.pss.presentation.R 9 | import com.pss.presentation.databinding.ScoreRecyclerViewItemBinding 10 | import com.pss.presentation.viewmodel.MainViewModel 11 | 12 | class ScoreRecyclerViewAdapter( 13 | private val viewModel: MainViewModel 14 | ) : RecyclerView.Adapter() { 15 | 16 | 17 | override fun onCreateViewHolder( 18 | parent: ViewGroup, 19 | viewType: Int 20 | ): ScoreRecyclerViewHolder { 21 | val layoutInflater = LayoutInflater.from(parent.context) 22 | val binding = DataBindingUtil.inflate( 23 | layoutInflater, 24 | R.layout.score_recycler_view_item, 25 | parent, 26 | false 27 | ) 28 | return ScoreRecyclerViewHolder(binding) 29 | } 30 | 31 | override fun onBindViewHolder(holder: ScoreRecyclerViewHolder, position: Int) { 32 | holder.bind(viewModel.scoreList[position]) 33 | } 34 | 35 | override fun getItemCount(): Int { 36 | return viewModel.scoreList.size 37 | } 38 | 39 | inner class ScoreRecyclerViewHolder(val binding: ScoreRecyclerViewItemBinding) : 40 | RecyclerView.ViewHolder(binding.root) { 41 | fun bind(data: DomainScore) { 42 | binding.data = data 43 | binding.executePendingBindings() 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /data/src/main/java/com/pss/data/repository/MainRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.pss.data.repository 2 | 3 | import com.google.android.gms.tasks.Task 4 | import com.google.firebase.firestore.QuerySnapshot 5 | import com.pss.data.mapper.MainMapper 6 | import com.pss.data.repository.remote.datasource.MainDataSource 7 | import com.pss.domain.model.DomainLoveResponse 8 | import com.pss.domain.model.DomainScore 9 | import com.pss.domain.model.GetFirebaseResponse 10 | import com.pss.domain.model.SetFirebaseResponse 11 | import com.pss.domain.repository.MainRepository 12 | import com.pss.domain.utils.RemoteErrorEmitter 13 | import javax.inject.Inject 14 | 15 | class MainRepositoryImpl @Inject constructor( 16 | private val mainDataSource: MainDataSource 17 | ) : MainRepository { 18 | 19 | override suspend fun checkLoveCalculator(remoteErrorEmitter: RemoteErrorEmitter, host : String, key : String, mName : String, wName : String): DomainLoveResponse? { 20 | return MainMapper.loveMapper(mainDataSource.checkLoveCalculator(remoteErrorEmitter = remoteErrorEmitter, host = host, key = key, mName = mName, wName = wName)) 21 | } 22 | 23 | override suspend fun getStatistics(): GetFirebaseResponse { 24 | return mainDataSource.getStatistics() 25 | } 26 | 27 | override suspend fun setStatistics(plusResult: Int): SetFirebaseResponse { 28 | return mainDataSource.setStatistics(plusResult) 29 | } 30 | 31 | override suspend fun setScore(score: DomainScore): SetFirebaseResponse { 32 | return mainDataSource.setScore(MainMapper.scoreMapper(score)) 33 | } 34 | 35 | override suspend fun getScore(): GetFirebaseResponse> { 36 | return mainDataSource.getScore() 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/pss/presentation/view/MainFragment.kt: -------------------------------------------------------------------------------- 1 | package com.pss.presentation.view 2 | 3 | import android.view.View 4 | import androidx.fragment.app.activityViewModels 5 | import androidx.navigation.fragment.findNavController 6 | import com.pss.library.CountNumberEvent.Companion.countNumber 7 | import com.pss.presentation.R 8 | import com.pss.presentation.adapter.ScoreRecyclerViewAdapter 9 | import com.pss.presentation.base.BaseFragment 10 | import com.pss.presentation.databinding.FragmentMainBinding 11 | import com.pss.presentation.viewmodel.MainViewModel 12 | import com.pss.presentation.widget.extension.showVertical 13 | 14 | 15 | class MainFragment : BaseFragment(R.layout.fragment_main) { 16 | private val mainViewModel by activityViewModels() 17 | 18 | 19 | override fun init() { 20 | binding.fragment = this 21 | mainViewModel.getStatisticsDisplay() 22 | mainViewModel.getScore() 23 | if (mainViewModel.getStatisticsDisplayEvent.value != null) 24 | countNumber(0, mainViewModel.getStatisticsDisplayEvent.value!!, binding.number, 1000) 25 | observeViewModel() 26 | } 27 | 28 | fun startBtnClick(view: View) { 29 | this.findNavController().navigate(R.id.action_mainFragment_to_womanNameFragment) 30 | } 31 | 32 | private fun initRecyclerView(){ 33 | binding.scoreRecyclerView.adapter = ScoreRecyclerViewAdapter(mainViewModel) 34 | binding.scoreRecyclerView.showVertical(requireContext()) 35 | } 36 | 37 | private fun observeViewModel() { 38 | mainViewModel.getStatisticsDisplayEvent.observe(this) { 39 | binding.number.text = "" 40 | countNumber(0, it, binding.number, 1000) 41 | } 42 | 43 | mainViewModel.getScoreEvent.observe(this){ 44 | initRecyclerView() 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /data/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id ("com.android.library") 3 | id ("kotlin-android") 4 | id ("kotlin-kapt") 5 | id ("dagger.hilt.android.plugin") 6 | id ("com.google.gms.google-services") 7 | } 8 | 9 | android { 10 | compileSdk = SdkVersions.compileSdk 11 | 12 | defaultConfig { 13 | minSdk = SdkVersions.minSdk 14 | targetSdk = SdkVersions.targetSdk 15 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 16 | consumerProguardFiles("consumer-rules.pro") 17 | } 18 | 19 | buildTypes { 20 | release { 21 | isMinifyEnabled = false 22 | proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") 23 | } 24 | } 25 | 26 | compileOptions { 27 | sourceCompatibility = JavaVersion.VERSION_1_8 28 | targetCompatibility = JavaVersion.VERSION_1_8 29 | } 30 | 31 | kotlinOptions { 32 | jvmTarget = "1.8" 33 | } 34 | } 35 | 36 | dependencies { 37 | implementation (project (":domain")) 38 | 39 | implementation (KTX.CORE) 40 | implementation (AndroidX.APP_COMPAT) 41 | implementation (Google.MATERIAL) 42 | implementation (Firebase.FIREBASE_DATABASE_KTX) 43 | implementation (Firebase.FIREBASE_FIRESTORE_KTX) 44 | testImplementation (TestTool.JUNIT) 45 | androidTestImplementation (TestTool.ANDROID_X_JUNIT) 46 | androidTestImplementation (TestTool.ANDROID_X_ESPRESSO) 47 | 48 | // Retrofit 49 | implementation (Retrofit.RETROFIT) 50 | implementation (Retrofit.CONVERTER_GSON) 51 | implementation (Retrofit.CONVERTER_JAXB) 52 | 53 | //okHttp 54 | implementation (OkHttp.OKHTTP) 55 | implementation (OkHttp.LOGGING_INTERCEPTOR) 56 | 57 | //coroutines 58 | implementation (Coroutines.COROUTINES) 59 | implementation (Coroutines.COROUTINES_PLAY_SERVICES) 60 | 61 | // dager hilt 62 | implementation (DaggerHilt.DAGGER_HILT) 63 | kapt (DaggerHilt.DAGGER_HILT_COMPILER) 64 | kapt (DaggerHilt.DAGGER_HILT_ANDROIDX_COMPILER) 65 | } -------------------------------------------------------------------------------- /app/src/main/java/com/pss/check_percentage/di/NetworkModule.kt: -------------------------------------------------------------------------------- 1 | package com.pss.check_percentage.di 2 | 3 | import com.pss.check_percentage.utils.Utils.BASE_URL 4 | import com.pss.data.remote.api.LoveCalculatorApi 5 | import dagger.Module 6 | import dagger.Provides 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.components.SingletonComponent 9 | import okhttp3.OkHttpClient 10 | import okhttp3.logging.HttpLoggingInterceptor 11 | import retrofit2.Retrofit 12 | import retrofit2.converter.gson.GsonConverterFactory 13 | import java.util.concurrent.TimeUnit 14 | import javax.inject.Singleton 15 | 16 | @Module 17 | @InstallIn(SingletonComponent::class) 18 | object NetworkModule { 19 | 20 | @Provides 21 | @Singleton 22 | //okHttp 의존성 주입 (아래 retrofit 의존성 주입에 사용) 23 | fun provideHttpClient(): OkHttpClient { 24 | return OkHttpClient.Builder() 25 | .readTimeout(10, TimeUnit.SECONDS) 26 | .connectTimeout(10, TimeUnit.SECONDS) 27 | .writeTimeout(15, TimeUnit.SECONDS) 28 | .addInterceptor(getLoggingInterceptor()) 29 | .build() 30 | } 31 | 32 | @Provides 33 | @Singleton 34 | //gson 의존성 주입 (아래 retrofit 의존성 주입에 사용) 35 | fun provideConverterFactory(): GsonConverterFactory { 36 | return GsonConverterFactory.create() 37 | } 38 | 39 | @Singleton 40 | @Provides 41 | //retrofit 의존성 주입 (아래 LoveCalculatorApi 의존성 주입에 사용) 42 | fun provideRetrofitInstance( 43 | okHttpClient: OkHttpClient, 44 | gsonConverterFactory: GsonConverterFactory 45 | ): Retrofit { 46 | return Retrofit.Builder() 47 | .baseUrl(BASE_URL) 48 | .client(okHttpClient) 49 | .addConverterFactory(gsonConverterFactory) 50 | .build() 51 | } 52 | 53 | @Provides 54 | @Singleton 55 | //LoveCalculatorApi interface 의존성 주입 56 | fun provideLoveCalculatorApiService(retrofit: Retrofit): LoveCalculatorApi { 57 | return retrofit.create(LoveCalculatorApi::class.java) 58 | } 59 | 60 | private fun getLoggingInterceptor(): HttpLoggingInterceptor = 61 | HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY } 62 | 63 | } -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/pss/presentation/view/ManNameFragment.kt: -------------------------------------------------------------------------------- 1 | package com.pss.presentation.view 2 | 3 | import android.text.TextUtils 4 | import android.view.View 5 | import androidx.fragment.app.activityViewModels 6 | import androidx.navigation.fragment.findNavController 7 | import com.pss.domain.utils.ErrorType 8 | import com.pss.domain.utils.ScreenState 9 | import com.pss.presentation.R 10 | import com.pss.presentation.base.BaseFragment 11 | import com.pss.presentation.databinding.FragmentManNameBinding 12 | import com.pss.presentation.viewmodel.MainViewModel 13 | 14 | class ManNameFragment : BaseFragment(R.layout.fragment_man_name) { 15 | private val mainViewModel by activityViewModels() 16 | 17 | 18 | override fun init() { 19 | binding.fragment = this 20 | observeViewModel() 21 | } 22 | 23 | fun nextBtnClick(view: View){ 24 | if (TextUtils.isEmpty(binding.editTxt.text.toString())) shortShowToast("이름을 입력해 주세요") 25 | else { 26 | mainViewModel.manNameResult = binding.editTxt.text.toString() 27 | binding.progressBar.visibility = View.VISIBLE 28 | mainViewModel.checkLoveCalculator("love-calculator.p.rapidapi.com","6f15755a03msh1e9952813104629p1ae459jsn2facf2b12aaa",mainViewModel.manNameResult,mainViewModel.womanNameResult) 29 | } 30 | } 31 | 32 | private fun observeViewModel(){ 33 | mainViewModel.apiCallEvent.observe(this,{ 34 | binding.progressBar.visibility = View.INVISIBLE 35 | when(it){ 36 | ScreenState.LOADING -> this.findNavController().navigate(R.id.action_manNameFragment_to_resultFragment) 37 | ScreenState.ERROR -> toastErrorMsg() 38 | else -> shortShowToast("알수없는 오류가 발생했습니다") 39 | } 40 | }) 41 | } 42 | 43 | private fun toastErrorMsg(){ 44 | when(mainViewModel.apiErrorType){ 45 | ErrorType.NETWORK ->longShowToast("네트워크 오류가 발생했습니다") 46 | ErrorType.SESSION_EXPIRED ->longShowToast("세션이 만료되었습니다") 47 | ErrorType.TIMEOUT ->longShowToast("서버 호출 시간이 초과되었습니다") 48 | ErrorType.UNKNOWN ->longShowToast("예기치 못한 오류가 발생했습니다") 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("kotlin-android") 4 | id("kotlin-kapt") 5 | id("dagger.hilt.android.plugin") 6 | id("com.google.gms.google-services") 7 | } 8 | 9 | android { 10 | compileSdk = SdkVersions.compileSdk 11 | 12 | defaultConfig { 13 | applicationId = "com.pss.check_percentage" 14 | minSdk = SdkVersions.minSdk 15 | targetSdk = SdkVersions.targetSdk 16 | versionCode = AppVersions.androidVersionCode 17 | versionName = AppVersions.androidVersionName 18 | 19 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 20 | } 21 | 22 | buildTypes { 23 | release { 24 | isMinifyEnabled = false 25 | isTestCoverageEnabled = false 26 | proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") 27 | } 28 | } 29 | 30 | compileOptions { 31 | sourceCompatibility = JavaVersion.VERSION_1_8 32 | targetCompatibility = JavaVersion.VERSION_1_8 33 | } 34 | 35 | kotlinOptions { 36 | jvmTarget = "1.8" 37 | } 38 | 39 | buildFeatures { 40 | dataBinding = true 41 | } 42 | } 43 | 44 | dependencies { 45 | implementation (project (":data")) 46 | implementation (project (":domain")) 47 | implementation (project (":presentation")) 48 | 49 | implementation (KTX.CORE) 50 | implementation (AndroidX.APP_COMPAT) 51 | implementation (Google.MATERIAL) 52 | implementation (AndroidX.CONSTRAINT_LAYOUT) 53 | implementation (Firebase.FIREBASE_DATABASE_KTX) 54 | implementation (Firebase.FIREBASE_FIRESTORE_KTX) 55 | testImplementation (TestTool.JUNIT) 56 | androidTestImplementation (TestTool.ANDROID_X_JUNIT) 57 | androidTestImplementation (TestTool.ANDROID_X_ESPRESSO) 58 | 59 | // dager hilt 60 | implementation (DaggerHilt.DAGGER_HILT) 61 | kapt (DaggerHilt.DAGGER_HILT_COMPILER) 62 | kapt (DaggerHilt.DAGGER_HILT_ANDROIDX_COMPILER) 63 | 64 | // Retrofit 65 | implementation (Retrofit.RETROFIT) 66 | implementation (Retrofit.CONVERTER_GSON) 67 | implementation (Retrofit.CONVERTER_JAXB) 68 | 69 | //okHttp 70 | implementation (OkHttp.OKHTTP) 71 | implementation (OkHttp.LOGGING_INTERCEPTOR) 72 | 73 | //datastore 74 | implementation (AndroidX.DATASTORE) 75 | } -------------------------------------------------------------------------------- /data/src/main/java/com/pss/data/repository/remote/datasourceimpl/MainDataSourceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.pss.data.repository.remote.datasourceimpl 2 | 3 | import com.google.firebase.database.FirebaseDatabase 4 | import com.google.firebase.firestore.FirebaseFirestore 5 | import com.google.firebase.firestore.Query 6 | import com.pss.data.mapper.FirebaseMapper.toResultString 7 | import com.pss.data.remote.api.LoveCalculatorApi 8 | import com.pss.data.remote.model.DataLoveResponse 9 | import com.pss.data.remote.model.DataScore 10 | import com.pss.data.repository.remote.datasource.MainDataSource 11 | import com.pss.data.utils.base.BaseDataSource 12 | import com.pss.domain.model.DomainScore 13 | import com.pss.domain.model.GetFirebaseResponse 14 | import com.pss.domain.model.SetFirebaseResponse 15 | import com.pss.domain.utils.RemoteErrorEmitter 16 | import javax.inject.Inject 17 | 18 | class MainDataSourceImpl @Inject constructor( 19 | private val loveCalculatorApi: LoveCalculatorApi, 20 | private val firebaseRtdb: FirebaseDatabase, 21 | private val fireStore: FirebaseFirestore 22 | ) : BaseDataSource(), MainDataSource { 23 | 24 | override suspend fun checkLoveCalculator(remoteErrorEmitter: RemoteErrorEmitter, host : String, key : String, mName : String, wName : String): DataLoveResponse? { 25 | return safeApiCall(remoteErrorEmitter){ 26 | loveCalculatorApi.getPercentage(host = host, key = key, fName = wName, sName = mName) 27 | }?.body() 28 | } 29 | 30 | override suspend fun getStatistics(): GetFirebaseResponse { 31 | return safeGetFirebaseRTDBCall{ 32 | firebaseRtdb.reference.child("statistics").get() 33 | }.toResultString() 34 | } 35 | 36 | override suspend fun setStatistics(plusResult: Int): SetFirebaseResponse { 37 | return safeSetFirebaseRTDBCall{ 38 | firebaseRtdb.reference.child("statistics").setValue(plusResult) 39 | } 40 | } 41 | 42 | override suspend fun setScore(score: DataScore): SetFirebaseResponse { 43 | return safeSetFirebaseRTDBCall{ 44 | fireStore.collection("score").document().set(score) 45 | } 46 | } 47 | 48 | override suspend fun getScore(): GetFirebaseResponse> { 49 | return safeSetFireStoreCall { 50 | fireStore.collection("score").orderBy("date", Query.Direction.DESCENDING).get() 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /presentation/src/main/res/layout/fragment_woman_name.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 10 | 11 | 12 | 18 | 19 | 27 | 28 | 37 | 45 | 46 | 47 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/pss/presentation/view/ResultFragment.kt: -------------------------------------------------------------------------------- 1 | package com.pss.presentation.view 2 | 3 | import android.view.View 4 | import androidx.fragment.app.activityViewModels 5 | import androidx.lifecycle.lifecycleScope 6 | import androidx.navigation.fragment.findNavController 7 | import com.pss.domain.utils.FirebaseState 8 | import com.pss.presentation.R 9 | import com.pss.presentation.base.BaseFragment 10 | import com.pss.presentation.databinding.FragmentResultBinding 11 | import com.pss.presentation.viewmodel.MainViewModel 12 | import kotlinx.coroutines.launch 13 | import java.text.SimpleDateFormat 14 | import java.util.* 15 | 16 | class ResultFragment : BaseFragment(R.layout.fragment_result) { 17 | private val mainViewModel by activityViewModels() 18 | 19 | 20 | override fun init() { 21 | binding.fragment = this 22 | initResult() 23 | saveScore() 24 | } 25 | 26 | private fun saveScore() = with(mainViewModel.apiCallResult){ 27 | mainViewModel.setScore(this.sname, this.fname, this.percentage, nowTime()) 28 | } 29 | 30 | private fun nowTime(): String = SimpleDateFormat("yyyy년 MM월 dd일 HH시 mm분", Locale("ko", "KR")).format( 31 | Date(System.currentTimeMillis()) 32 | ) 33 | 34 | private fun initResult() { 35 | binding.percentage.text = mainViewModel.apiCallResult.percentage.toString() 36 | when (mainViewModel.apiCallResult.percentage) { 37 | in 0..20 -> setLoveMsgTxt("조금 힘들어보여요") 38 | in 21..40 -> setLoveMsgTxt("노력해 보세요") 39 | in 41..70 -> setLoveMsgTxt("기대해도 좋겠는데요?") 40 | in 71..90 -> setLoveMsgTxt("일단 축하드려요") 41 | in 91..99 -> setLoveMsgTxt("와우.. 눈을 의심하고 있어요") 42 | 100 -> { 43 | setLoveMsgTxt("완벽하네요, 축하드려요") 44 | saveStatistics() 45 | } 46 | else -> setLoveMsgTxt("알수없는 힘!!") 47 | } 48 | binding.backMainBtn.isEnabled = true 49 | } 50 | 51 | private fun saveStatistics() = lifecycleScope.launch { 52 | with(mainViewModel.getStatistics()){ 53 | when(this.state){ 54 | FirebaseState.SUCCESS -> if (mainViewModel.setStatistics(this.result.toString().toInt() + 1).state == FirebaseState.FAILURE) error() 55 | FirebaseState.FAILURE -> error() 56 | } 57 | } 58 | } 59 | 60 | private fun error() = shortShowToast("통계를 저장하는데 오류가 발생했습니다") 61 | 62 | private fun setLoveMsgTxt(msg: String) = binding.loveTxt.setText(msg) 63 | 64 | fun clickBackMainBtn(view: View){ 65 | this.findNavController().navigate(R.id.action_resultFragment_to_mainFragment) 66 | } 67 | } -------------------------------------------------------------------------------- /presentation/src/main/res/navigation/main_nav_graph.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 20 | 21 | 26 | 33 | 34 | 39 | 46 | 47 | 52 | 59 | 60 | -------------------------------------------------------------------------------- /presentation/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id ("com.android.library") 3 | id ("kotlin-android") 4 | id ("kotlin-kapt") 5 | id ("dagger.hilt.android.plugin") 6 | } 7 | 8 | android { 9 | compileSdk = SdkVersions.compileSdk 10 | 11 | defaultConfig { 12 | minSdk = SdkVersions.minSdk 13 | targetSdk = SdkVersions.targetSdk 14 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 15 | consumerProguardFiles("consumer-rules.pro") 16 | } 17 | 18 | buildTypes { 19 | release { 20 | isMinifyEnabled = false 21 | proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") 22 | } 23 | } 24 | 25 | compileOptions { 26 | sourceCompatibility = JavaVersion.VERSION_1_8 27 | targetCompatibility = JavaVersion.VERSION_1_8 28 | } 29 | 30 | kotlinOptions { 31 | jvmTarget = "1.8" 32 | } 33 | 34 | buildFeatures { 35 | dataBinding = true 36 | } 37 | } 38 | 39 | dependencies { 40 | implementation (project (":domain")) 41 | implementation (project (":data")) 42 | 43 | implementation (KTX.CORE) 44 | implementation (AndroidX.APP_COMPAT) 45 | implementation (Google.MATERIAL) 46 | implementation (AndroidX.CONSTRAINT_LAYOUT) 47 | implementation (AndroidX.LEGACY) 48 | implementation (Firebase.FIREBASE_DATABASE_KTX) 49 | implementation (Firebase.FIREBASE_FIRESTORE_KTX) 50 | testImplementation (TestTool.JUNIT) 51 | androidTestImplementation (TestTool.ANDROID_X_JUNIT) 52 | androidTestImplementation (TestTool.ANDROID_X_ESPRESSO) 53 | 54 | // dagger hilt 55 | implementation (DaggerHilt.DAGGER_HILT) 56 | kapt (DaggerHilt.DAGGER_HILT_COMPILER) 57 | kapt (DaggerHilt.DAGGER_HILT_ANDROIDX_COMPILER) 58 | 59 | // ViewModel 60 | implementation (AndroidX.LIFECYCLE_VIEW_MODEL) 61 | 62 | // LiveData 63 | implementation (AndroidX.LIFECYCLE_LIVEDATA) 64 | 65 | // Retrofit 66 | implementation (Retrofit.RETROFIT) 67 | implementation (Retrofit.CONVERTER_GSON) 68 | implementation (Retrofit.CONVERTER_JAXB) 69 | 70 | //okHttp 71 | implementation (OkHttp.OKHTTP) 72 | implementation (OkHttp.LOGGING_INTERCEPTOR) 73 | 74 | //coroutines 75 | implementation (Coroutines.COROUTINES) 76 | 77 | //by viewModel 78 | implementation (AndroidX.ACTIVITY) 79 | implementation (AndroidX.FRAGMENT) 80 | 81 | //nav component 82 | implementation (NavComponent.NAVIGATION_FRAGMENT) 83 | implementation (NavComponent.NAVIGATION_UI) 84 | implementation (NavComponent.NAVIGATION_DYNAMIC_FEATURES_FRAGMENT) 85 | androidTestImplementation (NavComponent.NAVIGATION_TESTING) 86 | implementation (NavComponent.NAVIGATION_COMPOSE) 87 | 88 | //datastore 89 | implementation (AndroidX.DATASTORE) 90 | 91 | implementation (Library.NOTIFICATION_BAR_CUSTOM) 92 | implementation (Library.COUNT_NUMBER_EVENT) 93 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Check_Percentage 이름 궁합 2 | 3 | ## 🎯 Use stack & skill 4 | - Kotlin 100% 5 | - Kotlin DSL 6 | - Firebase RTDB 7 | - Firestore 8 | - MVVM 9 | - Clean Architecture 10 | - ACC 11 | - Dagger Hilt 12 | - DataBinding 13 | - BindingAdapter 14 | 15 |
16 | 17 | ## 👀 App tructure 18 | #### App module(Di) -> Presentation module(View, ViewModel) -> Domain module(Repository, Usecase) -> Data module(Repository, Mapper, Remote) 19 | 20 | ###
21 | 22 | ``` 23 | |── app 24 | | └── di 25 | | ├── App.kt 26 | | ├── DataSourceModule.kt 27 | | ├── FirebaseModule.kt 28 | | ├── NetworkModule.kt 29 | | └── RepositoryModule.kt 30 | | 31 | │── presentation 32 | │ ├── adapter 33 | │ │ ├── ScoreBindingAdapter.kt 34 | │ │ └── ScoreRecyclerViewAdapter.kt 35 | │ ├── base 36 | │ │ ├── BaseActivity.kt 37 | │ │ └── BaseFragment.kt 38 | │ ├── view 39 | │ │ ├── MainActivity.kt 40 | │ │ ├── MainFragment.kt 41 | │ │ ├── ManNameFragment.kt 42 | │ │ ├── ResultFragment.kt 43 | │ │ ├── SplashActivity.kt 44 | │ │ └── WomanNameFragment.kt 45 | │ ├── viewmodel 46 | │ │ ├── MainViewModel.kt 47 | │ │ └── SplashViewModel.kt 48 | │ └── widget 49 | │ ├── extension 50 | │ │ ├── ActivityExtension.kt 51 | │ │ └── FragmentExtension.kt 52 | │ └── utils 53 | │ └── SingleLiveEvent.kt 54 | │ 55 | ├── domain 56 | │ ├── model 57 | │ │ ├── DomainLoveResponse.kt 58 | │ │ └── DomainScore.kt 59 | │ ├── repository 60 | │ │ ├── MainRepository.kt 61 | │ │ └── SplashRepository.kt 62 | │ ├── usecase 63 | │ │ ├── CheckAppVersionUseCase.kt 64 | │ │ ├── CheckLoveCalculatorUseCase.kt 65 | │ │ ├── GetScoreUseCase.kt 66 | │ │ ├── GetStatisticsUseCase.kt 67 | │ │ ├── SetScoreUseCase.kt 68 | │ │ └── SetStatisticsUseCase.kt 69 | │ └── utils 70 | │ ├── ErrorType.kt 71 | │ ├── RemoteErrorEmitter.kt 72 | │ └── ScreenState.kt 73 | │ 74 | ├── data 75 | │ ├── mapper 76 | │ │ └── MainMapper.kt 77 | │ ├── remote 78 | │ │ ├── api 79 | │ │ │ └── LoveCalculatorApi.kt 80 | │ │ └── model 81 | │ │ ├── DataLoveResponse.kt 82 | │ │ └── DataScore.kt 83 | │ └── repository 84 | │ ├── remote 85 | │ │ ├── datasource 86 | │ │ │ ├── MainDataSource.kt 87 | │ │ │ └── SplashDataSource.kt 88 | │ │ └── datasourceimpl 89 | │ │ ├── MainDataSourceImpl.kt 90 | │ │ └── SplashDataSourceImpl.kt 91 | │ ├── MainRepositoryImpl.kt 92 | │ └── SplashRepositoryImpl.kt 93 | │ 94 | └── buildSrc 95 | ├── Versions.kt 96 | └── Dependency.kt 97 | ``` 98 |
99 | 100 | ## 😃 Contributors 101 | #### ParkSangSun1 102 | 103 |
104 | 105 | ## 🎞️ Demonstration 106 | ![궁합](https://user-images.githubusercontent.com/67040465/152307727-13eb4426-a60f-4a58-8e45-e4d04cf2687e.gif) 107 | 108 | 109 | ## 🎨 Other than that 110 | #### 오류나 버그가 발견되면 이슈 넣어주시면 감사하겠습니다 111 | #### If you find an error or bug, I would appreciate it if you could put in an issue 112 | -------------------------------------------------------------------------------- /presentation/src/main/res/layout/fragment_man_name.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 10 | 11 | 12 | 17 | 18 | 19 | 27 | 28 | 37 | 45 | 46 | 47 | 61 | 62 | 63 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /presentation/src/main/res/layout/fragment_result.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 10 | 11 | 12 | 16 | 17 | 25 | 26 | 31 | 40 | 51 | 52 | 63 | 64 | 65 | 81 | 82 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/pss/presentation/viewmodel/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.pss.presentation.viewmodel 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.viewModelScope 6 | import com.pss.domain.model.DomainLoveResponse 7 | import com.pss.domain.model.DomainScore 8 | import com.pss.domain.usecase.* 9 | import com.pss.domain.utils.ErrorType 10 | import com.pss.domain.utils.FirebaseState 11 | import com.pss.domain.utils.RemoteErrorEmitter 12 | import com.pss.domain.utils.ScreenState 13 | import com.pss.presentation.widget.utils.SingleLiveEvent 14 | import dagger.hilt.android.lifecycle.HiltViewModel 15 | import kotlinx.coroutines.launch 16 | import javax.inject.Inject 17 | 18 | @HiltViewModel 19 | class MainViewModel @Inject constructor( 20 | private val checkLoveCalculatorUseCase: CheckLoveCalculatorUseCase, 21 | private val getStatisticsUseCase: GetStatisticsUseCase, 22 | private val setStatisticsUseCase: SetStatisticsUseCase, 23 | private val setScoreUseCase: SetScoreUseCase, 24 | private val getScoreUseCase: GetScoreUseCase 25 | ) : ViewModel(), RemoteErrorEmitter { 26 | 27 | val apiCallEvent: LiveData get() = _apiCallEvent 28 | private var _apiCallEvent = SingleLiveEvent() 29 | 30 | val getStatisticsDisplayEvent: LiveData get() = _getStatisticsDisplayEvent 31 | private var _getStatisticsDisplayEvent = SingleLiveEvent() 32 | 33 | val getScoreEvent: LiveData get() = _getScoreEvent 34 | private var _getScoreEvent = SingleLiveEvent() 35 | 36 | var apiCallResult = DomainLoveResponse("", "", 0, "") 37 | var apiErrorType = ErrorType.UNKNOWN 38 | var errorMessage = "none" 39 | var manNameResult = "manEx" 40 | var womanNameResult = "womanEx" 41 | var scoreList = arrayListOf() 42 | 43 | 44 | fun checkLoveCalculator(host: String, key: String, mName: String, wName: String) = 45 | viewModelScope.launch { 46 | checkLoveCalculatorUseCase.execute(this@MainViewModel, host, key, mName, wName) 47 | .let { response -> 48 | if (response != null) { 49 | apiCallResult = response 50 | _apiCallEvent.postValue(ScreenState.LOADING) 51 | } else _apiCallEvent.postValue(ScreenState.ERROR) 52 | } 53 | } 54 | 55 | suspend fun getStatistics() = getStatisticsUseCase.execute() 56 | 57 | suspend fun setStatistics(plusResult: Int) = setStatisticsUseCase.execute(plusResult) 58 | 59 | fun getStatisticsDisplay() = viewModelScope.launch { 60 | with(getStatisticsUseCase.execute()){ 61 | if(this.state == FirebaseState.SUCCESS){ 62 | _getStatisticsDisplayEvent.value = this.result.toString().toInt() 63 | } 64 | } 65 | } 66 | 67 | fun getScore() = viewModelScope.launch { 68 | with(getScoreUseCase.execute()){ 69 | if (this.state == FirebaseState.SUCCESS){ 70 | scoreList.clear() 71 | for (item in this.result!!) { 72 | scoreList.add(item) 73 | } 74 | _getScoreEvent.call() 75 | } 76 | } 77 | } 78 | 79 | fun setScore(man: String, woman: String, percentage: Int, date: String) = viewModelScope.launch { 80 | setScoreUseCase.execute(DomainScore(man, woman, percentage, date)) 81 | } 82 | 83 | override fun onError(msg: String) { 84 | errorMessage = msg 85 | } 86 | 87 | override fun onError(errorType: ErrorType) { 88 | apiErrorType = errorType 89 | } 90 | } -------------------------------------------------------------------------------- /presentation/src/main/res/layout/score_recycler_view_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 10 | 11 | 12 | 19 | 20 | 30 | 31 | 38 | 39 | 46 | 47 | 48 | 58 | 59 | 66 | 67 | 74 | 75 | 76 | 77 | 86 | 87 | 94 | 95 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /presentation/src/main/res/layout/fragment_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 10 | 11 | 12 | 18 | 19 | 30 | 31 | 37 | 38 | 53 | 54 | 65 | 66 | 73 | 74 | 80 | 81 | 89 | 90 | 98 | 99 | 100 | 101 | 102 | 111 | 112 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /data/src/main/java/com/pss/data/utils/base/BaseDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.pss.data.utils.base 2 | 3 | import android.util.Log 4 | import com.google.android.gms.tasks.Task 5 | import com.google.firebase.firestore.QuerySnapshot 6 | import com.pss.domain.model.DomainScore 7 | import com.pss.domain.model.GetFirebaseResponse 8 | import com.pss.domain.model.SetFirebaseResponse 9 | import com.pss.domain.utils.ErrorType 10 | import com.pss.domain.utils.FirebaseState 11 | import com.pss.domain.utils.RemoteErrorEmitter 12 | import kotlinx.coroutines.Dispatchers 13 | import kotlinx.coroutines.tasks.await 14 | import kotlinx.coroutines.withContext 15 | import okhttp3.ResponseBody 16 | import org.json.JSONObject 17 | import retrofit2.HttpException 18 | import java.io.IOException 19 | import java.net.SocketTimeoutException 20 | 21 | abstract class BaseDataSource { 22 | 23 | companion object { 24 | private const val TAG = "BaseRemoteRepository" 25 | private const val MESSAGE_KEY = "message" 26 | private const val ERROR_KEY = "error" 27 | } 28 | 29 | /** 30 | * Function that executes the given function on Dispatchers.IO context and switch to Dispatchers.Main context when an error occurs 31 | * @param callFunction is the function that is returning the wanted object. It must be a suspend function. Eg: 32 | * override suspend fun loginUser(body: LoginUserBody, emitter: RemoteErrorEmitter): LoginUserResponse? = safeApiCall( { authApi.loginUser(body)} , emitter) 33 | * @param emitter is the interface that handles the error messages. The error messages must be displayed on the MainThread, or else they would throw an Exception. 34 | */ 35 | suspend inline fun safeApiCall(emitter: RemoteErrorEmitter, crossinline callFunction: suspend () -> T): T? { 36 | return try{ 37 | val myObject = withContext(Dispatchers.IO){ callFunction.invoke() } 38 | myObject 39 | }catch (e: Exception){ 40 | withContext(Dispatchers.Main){ 41 | e.printStackTrace() 42 | Log.e("BaseRemoteRepo", "Call error: ${e.localizedMessage}", e.cause) 43 | when(e){ 44 | is HttpException -> { 45 | if(e.code() == 401) emitter.onError(ErrorType.SESSION_EXPIRED) 46 | else { 47 | val body = e.response()?.errorBody() 48 | emitter.onError(getErrorMessage(body)) 49 | } 50 | } 51 | is SocketTimeoutException -> emitter.onError(ErrorType.TIMEOUT) 52 | is IOException -> emitter.onError(ErrorType.NETWORK) 53 | else -> emitter.onError(ErrorType.UNKNOWN) 54 | } 55 | } 56 | null 57 | } 58 | } 59 | 60 | /** 61 | * Function that executes the given function in whichever thread is given. Be aware, this is not friendly with Dispatchers.IO, 62 | * since [RemoteErrorEmitter] is intended to display messages to the user about error from the server/DB. 63 | * @param callFunction is the function that is returning the wanted object. Eg: 64 | * override suspend fun loginUser(body: LoginUserBody, emitter: RemoteErrorEmitter): LoginUserResponse? = safeApiCall( { authApi.loginUser(body)} , emitter) 65 | * @param emitter is the interface that handles the error messages. The error messages must be displayed on the MainThread, or else they would throw an Exception. 66 | */ 67 | inline fun safeApiCallNoContext(emitter: RemoteErrorEmitter, callFunction: () -> T): T? { 68 | return try{ 69 | val myObject = callFunction.invoke() 70 | myObject 71 | }catch (e: Exception){ 72 | e.printStackTrace() 73 | Log.e("BaseRemoteRepo", "Call error: ${e.localizedMessage}", e.cause) 74 | when(e){ 75 | is HttpException -> { 76 | if(e.code() == 401) emitter.onError(ErrorType.SESSION_EXPIRED) 77 | else { 78 | val body = e.response()?.errorBody() 79 | emitter.onError(getErrorMessage(body)) 80 | } 81 | } 82 | is SocketTimeoutException -> emitter.onError(ErrorType.TIMEOUT) 83 | is IOException -> emitter.onError(ErrorType.NETWORK) 84 | else -> emitter.onError(ErrorType.UNKNOWN) 85 | } 86 | null 87 | } 88 | } 89 | 90 | fun getErrorMessage(responseBody: ResponseBody?): String { 91 | return try { 92 | val jsonObject = JSONObject(responseBody!!.string()) 93 | when { 94 | jsonObject.has(MESSAGE_KEY) -> jsonObject.getString(MESSAGE_KEY) 95 | jsonObject.has(ERROR_KEY) -> jsonObject.getString(ERROR_KEY) 96 | else -> "Something wrong happened" 97 | } 98 | } catch (e: Exception) { 99 | "Something wrong happened" 100 | } 101 | } 102 | 103 | suspend inline fun safeGetFirebaseRTDBCall(crossinline callFunction: () -> Task) : GetFirebaseResponse { 104 | var state = FirebaseState.FAILURE 105 | var result : T? = null 106 | callFunction.invoke() 107 | .addOnSuccessListener { 108 | result = it 109 | state = FirebaseState.SUCCESS 110 | } 111 | .addOnFailureListener { 112 | state = FirebaseState.FAILURE 113 | }.await() 114 | 115 | return GetFirebaseResponse(state = state, result = result) 116 | } 117 | 118 | suspend inline fun safeSetFirebaseRTDBCall(crossinline callFunction: () -> Task) : SetFirebaseResponse { 119 | var state = FirebaseState.FAILURE 120 | callFunction.invoke() 121 | .addOnSuccessListener { 122 | state = FirebaseState.SUCCESS 123 | } 124 | .addOnFailureListener { 125 | state = FirebaseState.FAILURE 126 | }.await() 127 | 128 | return SetFirebaseResponse(state = state) 129 | } 130 | 131 | suspend inline fun safeSetFireStoreCall(crossinline callFunction: () -> Task) : GetFirebaseResponse> { 132 | var state = FirebaseState.FAILURE 133 | val result = arrayListOf() 134 | 135 | callFunction.invoke() 136 | .addOnSuccessListener { 137 | result.clear() 138 | for (item in it.documents) { 139 | item.toObject(DomainScore::class.java).let { 140 | result.add(it!!) 141 | } 142 | } 143 | state = FirebaseState.SUCCESS 144 | } 145 | .addOnFailureListener { 146 | state = FirebaseState.FAILURE 147 | }.await() 148 | 149 | return GetFirebaseResponse(state = state, result = result) 150 | } 151 | } -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/ic_result_love.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 18 | 21 | 24 | 27 | 30 | 33 | 36 | 39 | 42 | 45 | 48 | 53 | 56 | 59 | 64 | 67 | 70 | 73 | 76 | 79 | 82 | 85 | 88 | 91 | 94 | 97 | 100 | 103 | 108 | 111 | 116 | 119 | 122 | 125 | 128 | 131 | 134 | 137 | 140 | 143 | 146 | 149 | 152 | 157 | 160 | 163 | 168 | 171 | 174 | 179 | 182 | 185 | 190 | 193 | 194 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/ic_love_main.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 18 | 21 | 24 | 27 | 30 | 33 | 36 | 39 | 42 | 45 | 48 | 51 | 54 | 57 | 60 | 63 | 66 | 69 | 72 | 75 | 78 | 81 | 84 | 87 | 88 | --------------------------------------------------------------------------------