├── .github └── workflows │ ├── android_tests.yml │ └── android_ui_tests.yml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── github │ │ └── johnnysc │ │ └── ecp │ │ ├── core │ │ ├── BaseViewsActions.kt │ │ ├── LazyActivityScenarioRule.kt │ │ ├── RecyclerViewMatcher.kt │ │ └── ViewsIds.kt │ │ └── presentation │ │ ├── main │ │ └── MainActivityWeatherTest.kt │ │ └── messages │ │ ├── Messages.kt │ │ ├── MessagesWeatherTest.kt │ │ └── MessagesWithNoInternetWeatherTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── johnnysc │ │ │ └── ecp │ │ │ ├── core │ │ │ ├── ConvertRawResourceToPoJo.kt │ │ │ ├── InternetConnection.kt │ │ │ ├── ReadRawResource.kt │ │ │ └── StringToObject.kt │ │ │ ├── data │ │ │ ├── cloud │ │ │ │ └── HandleBaseException.kt │ │ │ └── weather │ │ │ │ ├── BaseWeatherRepository.kt │ │ │ │ ├── CityData.kt │ │ │ │ ├── WeatherData.kt │ │ │ │ ├── cache │ │ │ │ ├── CityCacheDataSource.kt │ │ │ │ └── CityPreferenceDataStore.kt │ │ │ │ ├── cloud │ │ │ │ ├── CurrentWeather.kt │ │ │ │ ├── HandleWeatherExceptions.kt │ │ │ │ ├── RemoteWeather.kt │ │ │ │ ├── Weather.kt │ │ │ │ ├── WeatherCloudDataSource.kt │ │ │ │ └── WeatherService.kt │ │ │ │ └── exceptions │ │ │ │ ├── ThereIsNoCityWithSuchTitleException.kt │ │ │ │ └── ThereIsNoDefaultCityException.kt │ │ │ ├── domain │ │ │ ├── AbstractInteractor.kt │ │ │ ├── DomainException.kt │ │ │ ├── ExceptionChain.kt │ │ │ └── weather │ │ │ │ ├── CityDomain.kt │ │ │ │ ├── DefaultCityUseCase.kt │ │ │ │ ├── WeatherDefaultCityUseCase.kt │ │ │ │ ├── WeatherDomain.kt │ │ │ │ ├── WeatherInCityUseCase.kt │ │ │ │ ├── WeatherInteractor.kt │ │ │ │ └── WeatherRepository.kt │ │ │ ├── presentation │ │ │ ├── commands │ │ │ │ ├── Command.kt │ │ │ │ ├── HandleUseCase.kt │ │ │ │ └── Parser.kt │ │ │ ├── main │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── MainViewModel.kt │ │ │ │ └── nav_screen │ │ │ │ │ ├── BaseFragmentFactory.kt │ │ │ │ │ └── MainNavScreen.kt │ │ │ ├── messages │ │ │ │ ├── FeatureChain.kt │ │ │ │ ├── HandleInput.kt │ │ │ │ ├── MessageEditText.kt │ │ │ │ ├── MessageUI.kt │ │ │ │ ├── MessagesArrayList.kt │ │ │ │ ├── MessagesCommunication.kt │ │ │ │ ├── MessagesFragment.kt │ │ │ │ ├── MessagesRecyclerView.kt │ │ │ │ ├── MessagesViewModel.kt │ │ │ │ ├── UnknownMessageViewModelChain.kt │ │ │ │ ├── ViewModelChain.kt │ │ │ │ └── adapter │ │ │ │ │ ├── AbstractMessageViewHolder.kt │ │ │ │ │ ├── CorrectAiMessageViewHolder.kt │ │ │ │ │ ├── CorrectAiMessageViewHolderFactoryChain.kt │ │ │ │ │ ├── ErrorAiMessageViewHolder.kt │ │ │ │ │ ├── ErrorAiMessageViewHolderFactoryChain.kt │ │ │ │ │ ├── MessageAdapter.kt │ │ │ │ │ ├── UserMessageViewHolder.kt │ │ │ │ │ └── UserMessageViewHolderFactoryChain.kt │ │ │ └── weather │ │ │ │ ├── WeatherChain.kt │ │ │ │ ├── WeatherViewModelChain.kt │ │ │ │ └── commands │ │ │ │ ├── setdefault │ │ │ │ ├── ParseCity.kt │ │ │ │ ├── SetDefaultCity.kt │ │ │ │ └── WeatherSetCityCommand.kt │ │ │ │ ├── weatherdefault │ │ │ │ ├── ParseDefaultWeather.kt │ │ │ │ ├── WeatherDefaultCommand.kt │ │ │ │ └── WeatherInCityNotMentioned.kt │ │ │ │ └── weatherincity │ │ │ │ ├── ParseWeatherInCity.kt │ │ │ │ ├── WeatherInCity.kt │ │ │ │ └── WeatherInCityCommand.kt │ │ │ └── sl │ │ │ ├── MainApplication.kt │ │ │ ├── ProvideConvertRawResourceToPojoAdapter.kt │ │ │ ├── ProvideExceptionChain.kt │ │ │ ├── ProvideIDontUnderstandYouViewModelChain.kt │ │ │ ├── ProvideSharedPreferences.kt │ │ │ ├── ProvideViewModelChain.kt │ │ │ ├── main │ │ │ ├── MainDependencyContainer.kt │ │ │ └── MainModule.kt │ │ │ ├── message │ │ │ ├── MessagesDependencyContainer.kt │ │ │ └── MessagesModule.kt │ │ │ └── weather │ │ │ ├── ProvideCacheDataSource.kt │ │ │ ├── ProvideCityPreferenceDataStore.kt │ │ │ ├── ProvideCitySharedPref.kt │ │ │ ├── ProvideWeatherCloudDataSource.kt │ │ │ ├── ProvideWeatherConverterRawToPojo.kt │ │ │ ├── ProvideWeatherExceptionChain.kt │ │ │ ├── ProvideWeatherInteractor.kt │ │ │ ├── ProvideWeatherRepository.kt │ │ │ ├── ProvideWeatherService.kt │ │ │ └── ProvideWeatherViewModelChain.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── edit_text_cursor.xml │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── ai_correct_message.xml │ │ ├── ai_incorrect_message.xml │ │ ├── fragment_main.xml │ │ └── user_message.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.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 │ │ ├── raw │ │ ├── weather_invalid_loc_response │ │ ├── weather_succesfull_responce_for_almaty │ │ └── wether_succesfull_responce_for_ekibastuz │ │ ├── values-night │ │ └── themes.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ ├── styles.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── github │ └── johnnysc │ └── ecp │ ├── data │ └── weather │ │ └── BaseWeatherRepositoryTest.kt │ ├── domain │ ├── ExceptionChainTest.kt │ └── weather │ │ └── WeatherInteractorTest.kt │ ├── presentation │ ├── messages │ │ ├── MessagesArrayListTest.kt │ │ └── MessagesViewModelTest.kt │ └── weather │ │ └── WeatherParsersTest.kt │ └── sl │ └── ProvideViewModelChainTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── libs └── com │ └── github │ └── johnnysc │ └── coremvvm │ ├── 1.0 │ ├── coremvvm-1.0-javadoc.jar │ ├── coremvvm-1.0-sources.jar │ ├── coremvvm-1.0.aar │ ├── coremvvm-1.0.module │ └── coremvvm-1.0.pom │ └── maven-metadata-local.xml └── settings.gradle /.github/workflows/android_tests.yml: -------------------------------------------------------------------------------- 1 | name: Android Unit Tests 2 | 3 | on: pull_request 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v3 11 | 12 | - name: Set up JDK 13 | uses: actions/setup-java@v3 14 | with: 15 | distribution: 'temurin' 16 | java-version: '11' 17 | 18 | - name: Run test 19 | run: ./gradlew test 20 | -------------------------------------------------------------------------------- /.github/workflows/android_ui_tests.yml: -------------------------------------------------------------------------------- 1 | name: Android UI Tests 2 | 3 | on: pull_request 4 | 5 | jobs: 6 | build: 7 | runs-on: macOS-10.15 8 | 9 | steps: 10 | - uses: actions/checkout@v3 11 | 12 | - name: Set up JDK 13 | uses: actions/setup-java@v3 14 | with: 15 | distribution: 'temurin' 16 | java-version: '11' 17 | 18 | - uses: malinskiy/action-android/install-sdk@release/0.1.3 19 | 20 | - run: sdkmanager platform-tools 21 | 22 | - run: adb start-server 23 | 24 | - uses: malinskiy/action-android/emulator-run-cmd@release/0.1.3 25 | with: 26 | cmd: ./gradlew connectedUitestsAndroidTest 27 | api: 21 28 | tag: default 29 | abi: x86 30 | 31 | - name: Save logcat output 32 | uses: actions/upload-artifact@master 33 | if: failure() 34 | with: 35 | name: logcat 36 | path: artifacts/logcat.log 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Last update:** 07.07.2022 2 | 3 | --- 4 | # ECP v2.0 5 | Проект представляет собой чат-бота, выполняющего различные команды. 6 | Вторая итерация проекта [ECP](https://youtube.com/playlist?list=PLQRyeBV1rkk0Umdr05YRvf7LxTSsX6pch) (первая находится в ветке `olddev`). 7 | ## Features: 8 | * Прогноз погоды (WIP) 9 | ## Tech stack: 10 | * [CoreMVVM](https://github.com/JohnnySC/CoreMVVM) 11 | * Retrofit2 12 | * GSON 13 | * Coroutines 14 | * ViewModel 15 | * LiveData 16 | * [Generic RecyclerView Adapter](https://www.youtube.com/watch?v=tAsqq5fIyFU) 17 | * [Weather API](https://www.visualcrossing.com/) 18 | ## Principles, patterns and paradigms: 19 | * [OOP](https://www.youtube.com/watch?v=vXk4JeBGd4A) 20 | * Service locator 21 | * [Chain-of-responsibility](https://www.youtube.com/watch?v=-TUsonlZfeM) 22 | * [TDD](https://youtube.com/playlist?list=PLQRyeBV1rkk2pJeI_6q9VodvPaF2y1aMD) 23 | * SOLID 24 | * [DRY](https://www.youtube.com/watch?v=iprTSb5UzQE), KISS, YAGNI 25 | * Clean Code & [Clean Architecture](https://youtube.com/playlist?list=PLQRyeBV1rkk3t0AqGY-V0I0dIt0M9WtRy) 26 | ## Additional: 27 | * Интро проекта: https://www.youtube.com/watch?v=Iiduz4YOGWo -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | } 5 | 6 | android { 7 | compileSdk 32 8 | testBuildType "uitests" 9 | defaultConfig { 10 | applicationId "com.github.johnnysc.ecp" 11 | minSdk 21 12 | targetSdk 32 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" 23 | 24 | } 25 | uitests{ 26 | initWith debug 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 | viewBinding = true 41 | } 42 | } 43 | 44 | dependencies { 45 | implementation "com.github.johnnysc:coremvvm:1.0" 46 | implementation "androidx.constraintlayout:constraintlayout:2.1.4" 47 | testImplementation "junit:junit:4.13.2" 48 | androidTestImplementation "androidx.test.ext:junit:1.1.3" 49 | androidTestImplementation "androidx.test.espresso:espresso-core:3.4.0" 50 | testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.3" 51 | } -------------------------------------------------------------------------------- /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. 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/src/androidTest/java/com/github/johnnysc/ecp/core/BaseViewsActions.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.core 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import androidx.test.core.app.ApplicationProvider 6 | import androidx.test.espresso.Espresso.onView 7 | import androidx.test.espresso.action.ViewActions 8 | import androidx.test.espresso.assertion.ViewAssertions 9 | import androidx.test.espresso.matcher.ViewMatchers 10 | import androidx.test.espresso.matcher.ViewMatchers.withId 11 | import com.github.johnnysc.coremvvm.core.ManageResources 12 | import com.github.johnnysc.ecp.R 13 | import com.github.johnnysc.ecp.presentation.main.MainActivity 14 | import org.junit.Before 15 | import org.junit.Rule 16 | 17 | open class BaseViewsActions { 18 | 19 | @get:Rule 20 | val activityTestRule = lazyActivityScenarioRule(launchActivity = false) 21 | protected lateinit var resources: ManageResources 22 | protected lateinit var appContext: Context 23 | 24 | @Before 25 | fun before() { 26 | appContext = ApplicationProvider.getApplicationContext() 27 | resources = ManageResources.Base(appContext) 28 | activityTestRule.launch(Intent(appContext, MainActivity::class.java)) 29 | } 30 | 31 | protected fun Int.typeTextToEditText(input: String) { 32 | onView(withId(this)).perform(ViewActions.typeText(input)) 33 | } 34 | 35 | protected fun Int.handleClick() { 36 | onView(withId(this)).perform(ViewActions.click()) 37 | } 38 | 39 | protected fun checkItemText(position: Int, text: String) { 40 | onView(RecyclerViewMatcher(R.id.messagesRecyclerView).atPosition(position)).check( 41 | ViewAssertions.matches(ViewMatchers.withText(text)) 42 | ) 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/github/johnnysc/ecp/core/LazyActivityScenarioRule.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.core 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import androidx.test.core.app.ActivityScenario 6 | import org.junit.rules.ExternalResource 7 | 8 | class LazyActivityScenarioRule : ExternalResource { 9 | 10 | constructor(launchActivity: Boolean, startActivityIntent: Intent) { 11 | this.launchActivity = launchActivity 12 | scenarioSupplier = { ActivityScenario.launch(startActivityIntent) } 13 | } 14 | 15 | constructor(launchActivity: Boolean, startActivityClass: Class) { 16 | this.launchActivity = launchActivity 17 | scenarioSupplier = { ActivityScenario.launch(startActivityClass) } 18 | } 19 | 20 | private var launchActivity: Boolean 21 | 22 | private var scenarioSupplier: () -> ActivityScenario 23 | 24 | private var scenario: ActivityScenario? = null 25 | 26 | private var scenarioLaunched: Boolean = false 27 | 28 | override fun before() { 29 | if (launchActivity) { 30 | launch() 31 | } 32 | } 33 | 34 | override fun after() { 35 | scenario?.close() 36 | } 37 | 38 | fun launch(newIntent: Intent? = null) { 39 | if (scenarioLaunched) 40 | throw IllegalStateException("Scenario has already been launched!") 41 | 42 | newIntent?.let { scenarioSupplier = { ActivityScenario.launch(it) } } 43 | 44 | scenario = scenarioSupplier() 45 | scenarioLaunched = true 46 | } 47 | } 48 | 49 | inline fun lazyActivityScenarioRule( 50 | launchActivity: Boolean = true, 51 | intent: Intent? = null 52 | ): LazyActivityScenarioRule = if (intent == null) { 53 | LazyActivityScenarioRule(launchActivity, A::class.java) 54 | } else { 55 | LazyActivityScenarioRule(launchActivity, intent) 56 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/github/johnnysc/ecp/core/RecyclerViewMatcher.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.core 2 | 3 | import android.content.res.Resources 4 | import android.view.View 5 | import androidx.recyclerview.widget.RecyclerView 6 | import com.github.johnnysc.ecp.R 7 | import org.hamcrest.Description 8 | import org.hamcrest.TypeSafeMatcher 9 | 10 | class RecyclerViewMatcher(private val recyclerViewId: Int) { 11 | 12 | fun atPosition(position: Int, targetViewId: Int = -1) = atPositionOnView(position, targetViewId) 13 | 14 | private fun atPositionOnView(position: Int, targetViewId: Int) = 15 | object : TypeSafeMatcher() { 16 | var resources: Resources? = null 17 | var childView: View? = null 18 | 19 | override fun describeTo(description: Description) { 20 | var idDescription = recyclerViewId.toString() 21 | if (this.resources != null) { 22 | idDescription = try { 23 | this.resources!!.getResourceName(recyclerViewId) 24 | } catch (e: Resources.NotFoundException) { 25 | String.format("%s (resource name not found)", recyclerViewId) 26 | } 27 | } 28 | 29 | description.appendText("RecyclerView with id: $idDescription at position: $position") 30 | } 31 | 32 | override fun matchesSafely(view: View): Boolean { 33 | 34 | this.resources = view.resources 35 | 36 | if (childView == null) { 37 | val recyclerView = view.rootView.findViewById(recyclerViewId) as RecyclerView 38 | if (recyclerView.id == recyclerViewId) { 39 | val viewHolder = recyclerView.findViewHolderForAdapterPosition(position) 40 | if (viewHolder != null) { 41 | childView = viewHolder.itemView.findViewById(R.id.messageTextView) 42 | } 43 | } else { 44 | return false 45 | } 46 | } 47 | 48 | return if (targetViewId == -1) { 49 | view === childView 50 | } else { 51 | val targetView = childView!!.findViewById(targetViewId) 52 | view === targetView 53 | } 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/github/johnnysc/ecp/core/ViewsIds.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.core 2 | 3 | import com.github.johnnysc.ecp.R 4 | 5 | abstract class ViewsIds { 6 | val inputTextId = R.id.messageEditText 7 | val sendMessage = R.id.sendMessageButton 8 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/github/johnnysc/ecp/presentation/main/MainActivityWeatherTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.main 2 | 3 | import com.github.johnnysc.coremvvm.sl.CoreModule 4 | import com.github.johnnysc.ecp.core.BaseViewsActions 5 | import com.github.johnnysc.ecp.core.InternetConnection 6 | import com.github.johnnysc.ecp.sl.ProvideSharedPreferences 7 | import com.github.johnnysc.ecp.sl.weather.ProvideCitySharedPref 8 | import org.junit.Before 9 | 10 | abstract class MainActivityWeatherTest : BaseViewsActions() { 11 | 12 | protected lateinit var internetConnection: InternetConnection.Write 13 | 14 | @Before 15 | fun setUp() { 16 | val coreModule = CoreModule.Base(appContext) 17 | ProvideCitySharedPref(coreModule).provideSharedPreferences().edit().clear() 18 | .apply() 19 | internetConnection = InternetConnection.Base( 20 | ProvideSharedPreferences.ProvideTestSettingsSharedPref(coreModule) 21 | .provideSharedPreferences() 22 | ) 23 | } 24 | 25 | protected fun Int.createRequestForDefaultCitySet(cityName: String) = 26 | "${resources.string(this)} $cityName" 27 | 28 | protected fun Int.createRequestForWeatherInCity(cityName: String) = 29 | "${resources.string(this)} $cityName" 30 | 31 | protected fun Int.createStringFromId() = 32 | resources.string(this) 33 | 34 | protected fun Int.createSuccessResponseForSetDefaultCity(cityName: String) = 35 | resources.string(this).format(cityName) 36 | 37 | protected fun Int.createSuccessResponseForTemperatureInCity(temperature: Float) = 38 | resources.string(this).format(temperature) 39 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/github/johnnysc/ecp/presentation/messages/Messages.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.messages 2 | 3 | import com.github.johnnysc.ecp.R 4 | import com.github.johnnysc.ecp.core.ViewsIds 5 | 6 | object Messages : ViewsIds() { 7 | const val inputStringForDefCityOneID: Int = R.string.what_is_weather_like 8 | const val setDefaultCityCommandID = R.string.set_weather_command_start 9 | const val defaultCitySetResultMessageId = R.string.set_weather_command_success 10 | const val currentTemperatureMessageId = R.string.weather_response 11 | const val inputStringForNotDefCityID = R.string.what_weather_command_start 12 | const val thereIsNoCityWithSuchTitle = R.string.there_is_no_city_with_such_title 13 | const val thereIsnoDefaultCitySet = R.string.weather_no_default_city 14 | const val incorrectMessage = "blablabal" 15 | const val iCanUnderstandYou = R.string.i_dont_understand 16 | const val theresIsNoConnectionId = R.string.there_is_no_connection 17 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/github/johnnysc/ecp/presentation/messages/MessagesWeatherTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.messages 2 | 3 | 4 | import com.github.johnnysc.ecp.presentation.main.MainActivityWeatherTest 5 | import com.github.johnnysc.ecp.presentation.messages.Messages.currentTemperatureMessageId 6 | import com.github.johnnysc.ecp.presentation.messages.Messages.inputStringForNotDefCityID 7 | import com.github.johnnysc.ecp.presentation.messages.Messages.inputTextId 8 | import com.github.johnnysc.ecp.presentation.messages.Messages.sendMessage 9 | import com.github.johnnysc.ecp.presentation.messages.Messages.setDefaultCityCommandID 10 | import com.github.johnnysc.ecp.presentation.messages.Messages.thereIsNoCityWithSuchTitle 11 | import kotlinx.coroutines.delay 12 | import kotlinx.coroutines.runBlocking 13 | import org.junit.Test 14 | 15 | class MessagesWeatherTest : MainActivityWeatherTest() { 16 | 17 | private val defaultCity = "Ekibastuz" 18 | private val cityName = "Almaty" 19 | private val noneExistedCity = "Rotrstan" 20 | 21 | @Test 22 | fun requestForTemperatureInDefaultCityWithoutDefaultCitySetAndSetDefCityAndRequireAgain(): Unit = 23 | runBlocking { 24 | internetConnection.turnOnInternet() 25 | Messages.apply { 26 | var input = inputStringForDefCityOneID.createStringFromId() 27 | 28 | inputTextId.typeTextToEditText(input) 29 | sendMessage.handleClick() 30 | 31 | checkItemText(0, input) 32 | checkItemText(1, thereIsnoDefaultCitySet.createStringFromId()) 33 | 34 | internetConnection.turnOnInternet() 35 | input = setDefaultCityCommandID.createRequestForDefaultCitySet(defaultCity) 36 | 37 | inputTextId.typeTextToEditText(input) 38 | sendMessage.handleClick() 39 | 40 | checkItemText(2, input) 41 | checkItemText( 42 | 3, 43 | defaultCitySetResultMessageId.createSuccessResponseForSetDefaultCity(defaultCity) 44 | ) 45 | 46 | input = inputStringForDefCityOneID.createStringFromId() 47 | 48 | inputTextId.typeTextToEditText(input) 49 | sendMessage.handleClick() 50 | 51 | checkItemText(4, input) 52 | checkItemText( 53 | 5, 54 | currentTemperatureMessageId.createSuccessResponseForTemperatureInCity(25.8F) 55 | ) 56 | } 57 | } 58 | 59 | @Test 60 | fun requestForTemperatureInCityByNameWithInternet() = runBlocking { 61 | internetConnection.turnOnInternet() 62 | val input = inputStringForNotDefCityID.createRequestForWeatherInCity(cityName) 63 | inputTextId.typeTextToEditText(input) 64 | sendMessage.handleClick() 65 | 66 | checkItemText(0, input) 67 | checkItemText( 68 | 1, 69 | currentTemperatureMessageId.createSuccessResponseForTemperatureInCity(34.0F) 70 | ) 71 | } 72 | 73 | @Test 74 | fun requestForDefCitySetWithNoneExistedCity(): Unit = runBlocking { 75 | internetConnection.turnOnInternet() 76 | val input = setDefaultCityCommandID.createRequestForDefaultCitySet(noneExistedCity) 77 | inputTextId.typeTextToEditText(input) 78 | sendMessage.handleClick() 79 | 80 | checkItemText(0, input) 81 | checkItemText(1, thereIsNoCityWithSuchTitle.createStringFromId()) 82 | } 83 | 84 | @Test 85 | fun requestForTemperatureInCityByNameWithNoneExistedCity(): Unit = runBlocking { 86 | internetConnection.turnOnInternet() 87 | val input = inputStringForNotDefCityID.createRequestForWeatherInCity(noneExistedCity) 88 | inputTextId.typeTextToEditText(input) 89 | sendMessage.handleClick() 90 | checkItemText(0, input) 91 | checkItemText(1, thereIsNoCityWithSuchTitle.createStringFromId()) 92 | } 93 | 94 | @Test 95 | fun requestIncomprehensibleMessage(): Unit = runBlocking { 96 | internetConnection.turnOnInternet() 97 | Messages.apply { 98 | inputTextId.typeTextToEditText(incorrectMessage) 99 | sendMessage.handleClick() 100 | checkItemText(0, incorrectMessage) 101 | checkItemText(1, iCanUnderstandYou.createStringFromId()) 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/github/johnnysc/ecp/presentation/messages/MessagesWithNoInternetWeatherTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.messages 2 | 3 | 4 | import com.github.johnnysc.ecp.presentation.main.MainActivityWeatherTest 5 | import com.github.johnnysc.ecp.presentation.messages.Messages.iCanUnderstandYou 6 | import com.github.johnnysc.ecp.presentation.messages.Messages.incorrectMessage 7 | import com.github.johnnysc.ecp.presentation.messages.Messages.inputStringForNotDefCityID 8 | import com.github.johnnysc.ecp.presentation.messages.Messages.inputTextId 9 | import com.github.johnnysc.ecp.presentation.messages.Messages.sendMessage 10 | import com.github.johnnysc.ecp.presentation.messages.Messages.setDefaultCityCommandID 11 | import com.github.johnnysc.ecp.presentation.messages.Messages.theresIsNoConnectionId 12 | import kotlinx.coroutines.runBlocking 13 | import org.junit.Test 14 | 15 | class MessagesWithNoInternetWeatherTest : MainActivityWeatherTest() { 16 | 17 | private val defaultCity = "Ekibastuz" 18 | private val cityName = "Almaty" 19 | private val noneExistedCity = "Rotrstan" 20 | 21 | @Test 22 | fun setDefaultCityWhenNoInternetMustNoConnectionMessage(): Unit = runBlocking { 23 | internetConnection.turnOffInternet() 24 | val input = setDefaultCityCommandID.createRequestForDefaultCitySet(defaultCity) 25 | inputTextId.typeTextToEditText(input) 26 | sendMessage.handleClick() 27 | checkItemText(0, input) 28 | checkItemText(1, theresIsNoConnectionId.createStringFromId()) 29 | } 30 | 31 | @Test 32 | fun setDefaultCityWhenNoInternetAndCityNotExistMustNoConnectionMessage(): Unit = runBlocking { 33 | internetConnection.turnOffInternet() 34 | val input = setDefaultCityCommandID.createRequestForDefaultCitySet(noneExistedCity) 35 | inputTextId.typeTextToEditText(input) 36 | sendMessage.handleClick() 37 | checkItemText(0, input) 38 | checkItemText(1, theresIsNoConnectionId.createStringFromId()) 39 | } 40 | 41 | @Test 42 | fun weatherInCityWhenNoInternetMustNoConnectionMessage(): Unit = runBlocking { 43 | internetConnection.turnOffInternet() 44 | val input = inputStringForNotDefCityID.createRequestForWeatherInCity(cityName) 45 | inputTextId.typeTextToEditText(input) 46 | sendMessage.handleClick() 47 | checkItemText(0, input) 48 | checkItemText(1, theresIsNoConnectionId.createStringFromId()) 49 | } 50 | 51 | @Test 52 | fun weatherInCityWhenNoInternetAndCityNotExistMustNoConnectionMessage(): Unit = runBlocking { 53 | internetConnection.turnOffInternet() 54 | val input = inputStringForNotDefCityID.createRequestForWeatherInCity(noneExistedCity) 55 | inputTextId.typeTextToEditText(input) 56 | sendMessage.handleClick() 57 | checkItemText(0, input) 58 | checkItemText(1, theresIsNoConnectionId.createStringFromId()) 59 | } 60 | 61 | @Test 62 | fun incorrectMessageWhenNoInternetMustNoConnectionMessage(): Unit = runBlocking { 63 | internetConnection.turnOffInternet() 64 | inputTextId.typeTextToEditText(incorrectMessage) 65 | sendMessage.handleClick() 66 | checkItemText(0, incorrectMessage) 67 | checkItemText(1, iCanUnderstandYou.createStringFromId()) 68 | } 69 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/core/ConvertRawResourceToPoJo.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.core 2 | 3 | import androidx.annotation.RawRes 4 | import com.github.johnnysc.coremvvm.core.Mapper 5 | 6 | abstract class ConvertRawResourceToPoJo( 7 | private val readRawResource: ReadRawResource, 8 | private val stringToObject: StringToObject 9 | ) : Mapper { 10 | 11 | override fun map(@RawRes data: Int): V { 12 | val resource = readRawResource.readText(data) 13 | return wrapResult(stringToObject.map(resource)) 14 | } 15 | 16 | protected abstract fun wrapResult(result: T): V 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/core/InternetConnection.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.core 2 | 3 | import android.content.SharedPreferences 4 | 5 | interface InternetConnection { 6 | 7 | interface Write : InternetConnection { 8 | 9 | fun turnOnInternet() 10 | fun turnOffInternet() 11 | } 12 | 13 | interface Read : InternetConnection { 14 | 15 | fun isInternetAvailable(): Boolean 16 | } 17 | 18 | interface Mutable : Write, Read 19 | 20 | class Base(private val sharedPreferences: SharedPreferences) : Mutable { 21 | 22 | companion object { 23 | private const val IS_INTERNET_AVAILABLE = "IS_INTERNET_AVAILABLE_KEY" 24 | } 25 | 26 | override fun turnOnInternet() { 27 | sharedPreferences.edit().putBoolean(IS_INTERNET_AVAILABLE, true).apply() 28 | } 29 | 30 | override fun turnOffInternet() { 31 | sharedPreferences.edit().putBoolean(IS_INTERNET_AVAILABLE, false).apply() 32 | } 33 | 34 | override fun isInternetAvailable() = 35 | sharedPreferences.getBoolean(IS_INTERNET_AVAILABLE, true) 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/core/ReadRawResource.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.core 2 | 3 | import android.content.Context 4 | import androidx.annotation.RawRes 5 | 6 | interface ReadRawResource { 7 | 8 | fun readText(@RawRes id: Int): String 9 | 10 | class Mock(private val context: Context) : ReadRawResource { 11 | override fun readText(id: Int) = 12 | context.resources.openRawResource(id).bufferedReader().readText() 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/core/StringToObject.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.core 2 | 3 | import com.github.johnnysc.coremvvm.core.Mapper 4 | import com.google.gson.Gson 5 | import com.google.gson.reflect.TypeToken 6 | 7 | interface StringToObject : Mapper { 8 | 9 | class Base(private val typeToken: TypeToken, private val gson: Gson) : StringToObject { 10 | 11 | override fun map(data: String): T = gson.fromJson(data, typeToken.type) 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/data/cloud/HandleBaseException.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.data.cloud 2 | 3 | import com.github.johnnysc.coremvvm.data.HandleError 4 | import com.github.johnnysc.coremvvm.domain.NoInternetConnectionException 5 | import com.github.johnnysc.coremvvm.domain.ServiceUnavailableException 6 | import java.net.SocketTimeoutException 7 | import java.net.UnknownHostException 8 | 9 | abstract class HandleBaseException : HandleError { 10 | override fun handle(error: Exception) = when (error) { 11 | is UnknownHostException, is SocketTimeoutException -> NoInternetConnectionException() 12 | else -> ServiceUnavailableException() 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/data/weather/BaseWeatherRepository.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.data.weather 2 | 3 | import com.github.johnnysc.ecp.data.weather.cache.CityCacheDataSource 4 | import com.github.johnnysc.ecp.data.weather.cloud.RemoteWeather 5 | import com.github.johnnysc.ecp.data.weather.cloud.WeatherCloudDataSource 6 | import com.github.johnnysc.ecp.domain.weather.WeatherDomain 7 | import com.github.johnnysc.ecp.domain.weather.WeatherRepository 8 | 9 | class BaseWeatherRepository( 10 | private val weatherCloudDataSource: WeatherCloudDataSource, 11 | private val cityCacheDataSource: CityCacheDataSource, 12 | private val weatherRemoteToWeatherDomainMapper: RemoteWeather.Mapper, 13 | private val cityDataToTitleMapper: CityData.Mapper 14 | ) : WeatherRepository { 15 | 16 | override suspend fun getWeatherInCity(city: String): WeatherDomain = 17 | weatherCloudDataSource.getWeather(city).map(weatherRemoteToWeatherDomainMapper) 18 | 19 | override suspend fun getWeatherInDefaultCity(): WeatherDomain = 20 | getWeatherInCity(cityCacheDataSource.getDefaultCity().map(cityDataToTitleMapper)) 21 | 22 | override suspend fun saveDefaultCity(newCity: String) { 23 | weatherCloudDataSource.getWeather(newCity) 24 | cityCacheDataSource.saveDefaultCity(newCity) 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/data/weather/CityData.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.data.weather 2 | 3 | 4 | interface CityData { 5 | 6 | fun map(mapper: Mapper): T 7 | 8 | class Base(private val title: String) : CityData { 9 | 10 | override fun map(mapper: Mapper) = mapper.map(title) 11 | } 12 | 13 | interface Mapper { 14 | 15 | fun map(title: String): T 16 | 17 | class Base : Mapper { 18 | 19 | override fun map(title: String): String = title 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/data/weather/WeatherData.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.data.weather 2 | 3 | interface WeatherData { 4 | 5 | fun map(mapper: Mapper): T 6 | 7 | class Base(private val temperature: Float) : WeatherData { 8 | 9 | override fun map(mapper: Mapper) = mapper.map(temperature) 10 | } 11 | 12 | interface Mapper { 13 | fun map(temperature: Float): T 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/data/weather/cache/CityCacheDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.data.weather.cache 2 | 3 | import com.github.johnnysc.ecp.data.weather.CityData 4 | import com.github.johnnysc.ecp.data.weather.exceptions.ThereIsNoDefaultCityException 5 | 6 | interface CityCacheDataSource { 7 | 8 | fun getDefaultCity(): CityData 9 | 10 | fun saveDefaultCity(newDefaultCity: String) 11 | 12 | class Base(private val preferenceDataStore: CityPreferenceDataStore) : CityCacheDataSource { 13 | 14 | override fun getDefaultCity(): CityData { 15 | val cityName = preferenceDataStore.read(DEFAULT_CITY_KEY) 16 | if (cityName.isEmpty()) { 17 | throw ThereIsNoDefaultCityException() 18 | } 19 | return CityData.Base(cityName) 20 | } 21 | 22 | override fun saveDefaultCity(newDefaultCity: String) { 23 | preferenceDataStore.save(DEFAULT_CITY_KEY, newDefaultCity) 24 | } 25 | 26 | companion object { 27 | private const val DEFAULT_CITY_KEY = "default city key" 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/data/weather/cache/CityPreferenceDataStore.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.data.weather.cache 2 | 3 | import android.content.SharedPreferences 4 | 5 | interface CityPreferenceDataStore { 6 | 7 | fun save(key: String, data: String) 8 | 9 | fun read(key: String): String 10 | 11 | class Base(private val sharedPreferences: SharedPreferences) : CityPreferenceDataStore { 12 | 13 | override fun save(key: String, data: String) = sharedPreferences.edit().putString(key, data).apply() 14 | 15 | override fun read(key: String): String = sharedPreferences.getString(key, null) ?: "" 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/data/weather/cloud/CurrentWeather.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.data.weather.cloud 2 | 3 | import com.github.johnnysc.coremvvm.core.Read 4 | import com.google.gson.annotations.SerializedName 5 | 6 | interface CurrentWeather : Read { 7 | 8 | data class Base( 9 | @SerializedName("temp") val temp: Float? 10 | ) : CurrentWeather { 11 | 12 | override fun read(): Float = temp!! 13 | } 14 | } 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/data/weather/cloud/HandleWeatherExceptions.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.data.weather.cloud 2 | 3 | import com.github.johnnysc.ecp.data.cloud.HandleBaseException 4 | import com.github.johnnysc.ecp.data.weather.exceptions.ThereIsNoCityWithSuchTitleException 5 | import retrofit2.HttpException 6 | 7 | 8 | class HandleWeatherExceptions : HandleBaseException() { 9 | companion object { 10 | private const val LOCATION_INCORRECT_ERROR_PREFIX = 11 | "Invalid location found. Please check your location parameter:" 12 | } 13 | 14 | override fun handle(error: Exception) = 15 | if (error is HttpException && error.response()?.errorBody()?.string()?.startsWith( 16 | LOCATION_INCORRECT_ERROR_PREFIX 17 | ) ?: false 18 | ) { 19 | ThereIsNoCityWithSuchTitleException() 20 | } else { 21 | super.handle(error) 22 | } 23 | 24 | } 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/data/weather/cloud/RemoteWeather.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.data.weather.cloud 2 | 3 | import com.github.johnnysc.ecp.domain.weather.WeatherDomain 4 | 5 | interface RemoteWeather { 6 | 7 | fun map(mapper: Mapper): T 8 | 9 | class Base(private val weather: Weather) : RemoteWeather { 10 | 11 | override fun map(mapper: Mapper): T = mapper.map(weather.read()) 12 | } 13 | 14 | interface Mapper { 15 | 16 | fun map(temperature: Float): T 17 | 18 | class Base : Mapper { 19 | 20 | override fun map(temperature: Float): WeatherDomain = WeatherDomain.Base(temperature) 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/data/weather/cloud/Weather.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.data.weather.cloud 2 | 3 | import com.github.johnnysc.coremvvm.core.Read 4 | import com.google.gson.annotations.SerializedName 5 | 6 | interface Weather : Read { 7 | 8 | data class Base( 9 | @SerializedName("currentConditions") private val currentWeather: CurrentWeather.Base 10 | ) : Weather { 11 | 12 | override fun read(): Float = currentWeather.read() 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/data/weather/cloud/WeatherCloudDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.data.weather.cloud 2 | 3 | import com.github.johnnysc.coremvvm.data.CloudDataSource 4 | import com.github.johnnysc.coremvvm.data.HandleError 5 | import com.github.johnnysc.ecp.R 6 | import com.github.johnnysc.ecp.core.StringToObject 7 | import com.github.johnnysc.ecp.core.ConvertRawResourceToPoJo 8 | import com.github.johnnysc.ecp.core.InternetConnection 9 | import com.github.johnnysc.ecp.core.ReadRawResource 10 | import com.google.gson.reflect.TypeToken 11 | import okhttp3.MediaType.Companion.toMediaTypeOrNull 12 | import okhttp3.ResponseBody 13 | import okhttp3.ResponseBody.Companion.toResponseBody 14 | import retrofit2.HttpException 15 | import retrofit2.Response 16 | import java.net.UnknownHostException 17 | 18 | interface WeatherCloudDataSource { 19 | 20 | suspend fun getWeather(cityName: String): RemoteWeather 21 | 22 | class Base( 23 | handleError: HandleError, 24 | private val weatherCloud: WeatherService 25 | ) : WeatherCloudDataSource, CloudDataSource.Abstract(handleError) { 26 | 27 | override suspend fun getWeather(cityName: String): RemoteWeather = handle { 28 | RemoteWeather.Base(weatherCloud.getWeather(cityName, WEATHER_API_KEY, UNITS, INCLUDE)) 29 | } 30 | 31 | companion object { 32 | private const val WEATHER_API_KEY = "Api key her" 33 | 34 | private const val UNITS = "metric" 35 | 36 | private const val INCLUDE = "current" 37 | 38 | } 39 | } 40 | 41 | class Mock( 42 | private val fetchWeather: ConvertRawResourceToPoJo, 43 | private val internetConnection: InternetConnection.Read, 44 | handleError: HandleError 45 | ) : WeatherCloudDataSource, CloudDataSource.Abstract(handleError) { 46 | 47 | override suspend fun getWeather(cityName: String) = handle { 48 | if (internetConnection.isInternetAvailable()) 49 | when (cityName) { 50 | "Ekibastuz" -> { 51 | fetchWeather.map(R.raw.wether_succesfull_responce_for_ekibastuz) 52 | } 53 | "Almaty" -> { 54 | fetchWeather.map(R.raw.weather_succesfull_responce_for_almaty) 55 | } 56 | else -> { 57 | throw HttpException( 58 | Response.error( 59 | 400, 60 | "Invalid location found. Please check your location parameter:".toResponseBody( 61 | "plain/text".toMediaTypeOrNull() 62 | ) 63 | ) 64 | ) 65 | } 66 | } 67 | else 68 | throw UnknownHostException() 69 | } 70 | 71 | class FetchWeather( 72 | stringToObject: StringToObject, 73 | readRawResource: ReadRawResource 74 | ) : ConvertRawResourceToPoJo(readRawResource, stringToObject) { 75 | override fun wrapResult(result: Weather.Base) = RemoteWeather.Base(result) 76 | } 77 | 78 | class WeatherResponseToken : TypeToken() 79 | } 80 | 81 | 82 | } 83 | 84 | 85 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/data/weather/cloud/WeatherService.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.data.weather.cloud 2 | 3 | import retrofit2.http.GET 4 | import retrofit2.http.Path 5 | import retrofit2.http.Query 6 | 7 | interface WeatherService { 8 | 9 | @GET("timeline/{location}") 10 | suspend fun getWeather( 11 | @Path("location") location: String, 12 | @Query("key") key: String, 13 | @Query("unitGroup") units: String, 14 | @Query("include") include: String 15 | ): Weather.Base 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/data/weather/exceptions/ThereIsNoCityWithSuchTitleException.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.data.weather.exceptions 2 | 3 | class ThereIsNoCityWithSuchTitleException : Exception("There is no city with such name") -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/data/weather/exceptions/ThereIsNoDefaultCityException.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.data.weather.exceptions 2 | 3 | class ThereIsNoDefaultCityException : Exception("There is no default city") -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/domain/AbstractInteractor.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.domain 2 | 3 | import com.github.johnnysc.ecp.presentation.messages.MessageUI 4 | 5 | abstract class AbstractInteractor( 6 | private val handleException: ExceptionChain, 7 | private val domainExceptionToMessageUIMapper: DomainException.Mapper 8 | ) { 9 | 10 | protected suspend fun handle(executeRequest: suspend () -> MessageUI) = try { 11 | executeRequest.invoke() 12 | } catch (exception: Exception) { 13 | handleException.handle(exception).map( 14 | domainExceptionToMessageUIMapper 15 | ) 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/domain/DomainException.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.domain 2 | 3 | import com.github.johnnysc.coremvvm.core.ManageResources 4 | import com.github.johnnysc.ecp.R 5 | import com.github.johnnysc.ecp.presentation.messages.MessageUI 6 | 7 | interface DomainException { 8 | fun map(mapper: Mapper): T 9 | 10 | abstract class AbstractDomainException(private val messageId: Int) : 11 | DomainException { 12 | override fun map(mapper: Mapper) = mapper.map(messageId) 13 | 14 | } 15 | 16 | class Unknown : 17 | AbstractDomainException(R.string.unknown_exception) 18 | 19 | class ThereIsNoCityWithSuchTitle : 20 | AbstractDomainException(R.string.there_is_no_city_with_such_title) 21 | 22 | class ThereIsNoConnection : AbstractDomainException(R.string.there_is_no_connection) 23 | 24 | class ThereIsNoDefaultCity : AbstractDomainException(R.string.weather_no_default_city) 25 | 26 | interface Mapper { 27 | fun map(messageId: Int): T 28 | 29 | class Base(private val resources: ManageResources) : Mapper { 30 | override fun map(messageId: Int) = MessageUI.AiError(text = resources.string(messageId)) 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/domain/ExceptionChain.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.domain 2 | 3 | import com.github.johnnysc.coremvvm.domain.NoInternetConnectionException 4 | import com.github.johnnysc.ecp.data.weather.exceptions.ThereIsNoCityWithSuchTitleException 5 | import com.github.johnnysc.ecp.data.weather.exceptions.ThereIsNoDefaultCityException 6 | 7 | interface ExceptionChain { 8 | fun handle(error: Exception): DomainException 9 | 10 | abstract class Base(private val nextExceptionChain: ExceptionChain) : ExceptionChain { 11 | 12 | protected abstract val exceptionClass: Class 13 | 14 | override fun handle(error: Exception) = if (error.javaClass == exceptionClass) 15 | createDomainException() 16 | else 17 | nextExceptionChain.handle(error) 18 | 19 | protected abstract fun createDomainException(): DomainException 20 | } 21 | 22 | class ThereIsNoSuchCityChain(exceptionChain: ExceptionChain) : Base(exceptionChain) { 23 | 24 | override val exceptionClass = ThereIsNoCityWithSuchTitleException::class.java 25 | 26 | override fun createDomainException() = DomainException.ThereIsNoCityWithSuchTitle() 27 | } 28 | 29 | class ThereIsNoConnectionChain(exceptionChain: ExceptionChain) : Base(exceptionChain) { 30 | 31 | override val exceptionClass = NoInternetConnectionException::class.java 32 | 33 | override fun createDomainException() = DomainException.ThereIsNoConnection() 34 | 35 | } 36 | 37 | class ThereIsNoDefaultCityChain(exceptionChain: ExceptionChain) : Base(exceptionChain) { 38 | 39 | override val exceptionClass = ThereIsNoDefaultCityException::class.java 40 | 41 | override fun createDomainException() = DomainException.ThereIsNoDefaultCity() 42 | } 43 | 44 | class DefaultExceptionChain : ExceptionChain { 45 | override fun handle(error: Exception) = DomainException.Unknown() 46 | } 47 | 48 | 49 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/domain/weather/CityDomain.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.domain.weather 2 | 3 | import com.github.johnnysc.coremvvm.core.ManageResources 4 | import com.github.johnnysc.ecp.R 5 | import com.github.johnnysc.ecp.presentation.messages.MessageUI 6 | 7 | interface CityDomain { 8 | fun map(mapper: Mapper): T 9 | 10 | data class Base(private val cityName: String) : CityDomain { 11 | override fun map(mapper: Mapper) = mapper.map(cityName) 12 | } 13 | 14 | interface Mapper { 15 | fun map(cityName: String): T 16 | 17 | class Base(private val manageResources: ManageResources) : Mapper { 18 | override fun map(cityName: String) = 19 | MessageUI.Ai(manageResources.string(R.string.set_weather_command_success).format(cityName)) 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/domain/weather/DefaultCityUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.domain.weather 2 | 3 | import com.github.johnnysc.ecp.presentation.messages.MessageUI 4 | 5 | interface DefaultCityUseCase { 6 | 7 | suspend fun setDefault(newCity: String): MessageUI 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/domain/weather/WeatherDefaultCityUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.domain.weather 2 | 3 | import com.github.johnnysc.ecp.presentation.messages.MessageUI 4 | 5 | interface WeatherDefaultCityUseCase { 6 | 7 | suspend fun getWeather(): MessageUI 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/domain/weather/WeatherDomain.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.domain.weather 2 | 3 | import com.github.johnnysc.coremvvm.core.ManageResources 4 | import com.github.johnnysc.ecp.R 5 | import com.github.johnnysc.ecp.presentation.messages.MessageUI 6 | 7 | interface WeatherDomain { 8 | fun map(mapper: Mapper): T 9 | 10 | data class Base(private val temperature: Float) : WeatherDomain { 11 | override fun map(mapper: Mapper) = mapper.map(temperature) 12 | } 13 | 14 | interface Mapper { 15 | fun map(temperature: Float): T 16 | 17 | class BaseToMessage(private val manageResources: ManageResources) : Mapper { 18 | override fun map(temperature: Float) = 19 | MessageUI.Ai(manageResources.string(R.string.weather_response).format(temperature)) 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/domain/weather/WeatherInCityUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.domain.weather 2 | 3 | import com.github.johnnysc.ecp.presentation.messages.MessageUI 4 | 5 | interface WeatherInCityUseCase { 6 | 7 | suspend fun getWeather(city: String): MessageUI 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/domain/weather/WeatherInteractor.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.domain.weather 2 | 3 | import com.github.johnnysc.ecp.domain.AbstractInteractor 4 | import com.github.johnnysc.ecp.domain.DomainException 5 | import com.github.johnnysc.ecp.domain.ExceptionChain 6 | import com.github.johnnysc.ecp.presentation.messages.MessageUI 7 | 8 | interface WeatherInteractor : WeatherInCityUseCase, DefaultCityUseCase, WeatherDefaultCityUseCase { 9 | 10 | class Base( 11 | private val weatherRepository: WeatherRepository, 12 | private val weatherDomainToMessageUIMapper: WeatherDomain.Mapper, 13 | private val cityDomainToMessageUIMapper: CityDomain.Mapper, 14 | handleException: ExceptionChain, 15 | domainExceptionToMessageUIMapper: DomainException.Mapper 16 | ) : AbstractInteractor(handleException, domainExceptionToMessageUIMapper), 17 | WeatherInteractor { 18 | 19 | override suspend fun getWeather(city: String) = handle { 20 | weatherRepository.getWeatherInCity(city).map(weatherDomainToMessageUIMapper) 21 | } 22 | 23 | override suspend fun getWeather() = handle { 24 | weatherRepository.getWeatherInDefaultCity().map(weatherDomainToMessageUIMapper) 25 | } 26 | 27 | override suspend fun setDefault(newCity: String) = handle { 28 | weatherRepository.saveDefaultCity(newCity) 29 | CityDomain.Base(newCity).map(cityDomainToMessageUIMapper) 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/domain/weather/WeatherRepository.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.domain.weather 2 | 3 | interface WeatherRepository { 4 | 5 | suspend fun getWeatherInCity(city: String): WeatherDomain 6 | 7 | suspend fun getWeatherInDefaultCity(): WeatherDomain 8 | 9 | suspend fun saveDefaultCity(newCity: String) 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/commands/Command.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.commands 2 | 3 | import com.github.johnnysc.ecp.presentation.messages.FeatureChain 4 | import com.github.johnnysc.ecp.presentation.messages.MessageUI 5 | import com.github.johnnysc.ecp.presentation.weather.commands.weatherincity.IsEmptyHandleUseCase 6 | 7 | /** 8 | * @param T Interactor 9 | */ 10 | interface Command : FeatureChain.Check, HandleUseCase { 11 | 12 | /** 13 | * @param T interactor 14 | * @param U useCase 15 | */ 16 | abstract class Abstract(private val parser: Parser) : Command { 17 | private var handler: IsEmptyHandleUseCase = IsEmptyHandleUseCase.Empty() 18 | 19 | override suspend fun handle(useCase: T): MessageUI = handler.handle(useCase) 20 | 21 | override fun canHandle(message: String): Boolean { 22 | handler = parser.map(message) 23 | return !handler.isEmpty() 24 | } 25 | } 26 | 27 | class Empty : Command { 28 | override suspend fun handle(useCase: T): MessageUI = MessageUI.Empty() 29 | 30 | override fun canHandle(message: String): Boolean = false 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/commands/HandleUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.commands 2 | 3 | import com.github.johnnysc.ecp.presentation.messages.MessageUI 4 | 5 | interface HandleUseCase { 6 | suspend fun handle(useCase: T): MessageUI 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/commands/Parser.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.commands 2 | 3 | import com.github.johnnysc.coremvvm.core.Mapper 4 | import com.github.johnnysc.ecp.presentation.weather.commands.weatherincity.IsEmptyHandleUseCase 5 | 6 | interface Parser : Mapper> -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/main/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.main 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.lifecycle.ViewModelStoreOwner 6 | import com.github.johnnysc.coremvvm.presentation.FragmentFactory 7 | import com.github.johnnysc.coremvvm.sl.ProvideViewModel 8 | import com.github.johnnysc.ecp.R 9 | import com.github.johnnysc.ecp.presentation.main.nav_screen.BaseFragmentFactory 10 | 11 | class MainActivity : AppCompatActivity(), ProvideViewModel { 12 | 13 | private lateinit var fragmentFactory: FragmentFactory 14 | private lateinit var viewModel: MainViewModel 15 | 16 | override fun onCreate(savedInstanceState: Bundle?) { 17 | super.onCreate(savedInstanceState) 18 | setContentView(R.layout.activity_main) 19 | fragmentFactory = BaseFragmentFactory(R.id.container, supportFragmentManager) 20 | viewModel = provideViewModel(MainViewModel::class.java, this) 21 | viewModel.observe(this) { navScreen -> 22 | fragmentFactory.fragment(navigationScreen = navScreen) 23 | } 24 | } 25 | 26 | override fun provideViewModel( 27 | clazz: Class, 28 | owner: ViewModelStoreOwner 29 | ) = (application as ProvideViewModel).provideViewModel(clazz, this) 30 | 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/main/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.main 2 | 3 | import androidx.lifecycle.LifecycleOwner 4 | import androidx.lifecycle.Observer 5 | import androidx.lifecycle.ViewModel 6 | import com.github.johnnysc.coremvvm.presentation.Communication 7 | import com.github.johnnysc.coremvvm.presentation.NavigationCommunication 8 | import com.github.johnnysc.coremvvm.presentation.NavigationScreen 9 | import com.github.johnnysc.ecp.presentation.main.nav_screen.MainNavScreen 10 | 11 | 12 | class MainViewModel( 13 | private val communication: NavigationCommunication.Base, 14 | ) : ViewModel(), Communication.Observe { 15 | 16 | init { 17 | communication.map(MainNavScreen()) 18 | } 19 | 20 | override fun observe(owner: LifecycleOwner, observer: Observer) { 21 | communication.observe(owner, observer) 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/main/nav_screen/BaseFragmentFactory.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.main.nav_screen 2 | 3 | import androidx.fragment.app.FragmentManager 4 | import com.github.johnnysc.coremvvm.presentation.FragmentFactory 5 | import com.github.johnnysc.coremvvm.presentation.NavigationScreen 6 | 7 | class BaseFragmentFactory( 8 | containerId: Int, 9 | fragmentManager: FragmentManager, 10 | ) : FragmentFactory.Abstract( 11 | containerId, 12 | fragmentManager, 13 | ) { 14 | 15 | override val screens: List = listOf(MainNavScreen()) 16 | 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/main/nav_screen/MainNavScreen.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.main.nav_screen 2 | 3 | import com.github.johnnysc.coremvvm.presentation.NavigationScreen 4 | import com.github.johnnysc.coremvvm.presentation.ShowStrategy 5 | import com.github.johnnysc.ecp.presentation.messages.MessagesFragment 6 | 7 | class MainNavScreen(showStrategy: ShowStrategy = ShowStrategy.REPLACE) : NavigationScreen( 8 | "mainFragment", 9 | MessagesFragment::class.java, showStrategy 10 | ) { 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/messages/FeatureChain.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.messages 2 | 3 | import com.github.johnnysc.coremvvm.core.ManageResources 4 | import com.github.johnnysc.ecp.R 5 | 6 | interface FeatureChain { 7 | interface Check : FeatureChain { 8 | fun canHandle(message: String): Boolean 9 | } 10 | 11 | interface Handle : FeatureChain { 12 | suspend fun handle(message: String): MessageUI 13 | } 14 | 15 | interface CheckAndHandle : Check, Handle 16 | 17 | class UnknownMessageChain(private val manageResources: ManageResources) : CheckAndHandle { 18 | private val errorMessageId = "-1" 19 | 20 | override fun canHandle(message: String) = true 21 | 22 | override suspend fun handle(message: String) = MessageUI.AiError( 23 | manageResources.string(R.string.i_dont_understand) 24 | ) 25 | } 26 | 27 | class Empty : Handle { 28 | override suspend fun handle(message: String) = MessageUI.Empty() 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/messages/HandleInput.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.messages 2 | 3 | interface HandleInput { 4 | fun handleInput(message: String) 5 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/messages/MessageEditText.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.messages 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | 6 | class MessageEditText : androidx.appcompat.widget.AppCompatEditText { 7 | 8 | constructor(context: Context) : super(context) 9 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) 10 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( 11 | context, 12 | attrs, 13 | defStyleAttr 14 | ) 15 | 16 | fun handleInput(handleInput: HandleInput) { 17 | var formattedText = text ?: "" 18 | formattedText = formattedText.trim() 19 | if (formattedText.isNotEmpty()) 20 | handleInput.handleInput(formattedText.toString()) 21 | setText("") 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/messages/MessageUI.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.messages 2 | 3 | import com.github.johnnysc.coremvvm.presentation.adapter.ItemUi 4 | import com.github.johnnysc.coremvvm.presentation.adapter.MyView 5 | 6 | interface MessageUI : ItemUi { 7 | 8 | fun copyWithId(id: String): MessageUI 9 | 10 | abstract class Message(private val text: String, private val id: String) : MessageUI { 11 | override fun content() = text 12 | 13 | override fun id() = id 14 | 15 | override fun show(vararg views: MyView) { 16 | views[0].show(text) 17 | } 18 | } 19 | 20 | data class User(private val text: String, private val id: String = "") : Message(text, id) { 21 | override fun type() = 1 22 | 23 | override fun copyWithId(id: String) = copy(id = id) 24 | } 25 | 26 | data class Ai(private val text: String, private val id: String = "") : Message(text, id) { 27 | override fun type() = 2 28 | 29 | override fun copyWithId(id: String) = copy(id = id) 30 | } 31 | 32 | data class AiError(private val text: String, private val id: String = "") : Message(text, id) { 33 | override fun type() = 3 34 | 35 | override fun copyWithId(id: String) = copy(id = id) 36 | } 37 | 38 | data class Empty( 39 | private val text: String = "", 40 | private val id: String = "" 41 | ) : Message(text, id) { 42 | 43 | override fun type() = Int.MIN_VALUE 44 | 45 | override fun copyWithId(id: String) = copy(id = id) 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/messages/MessagesArrayList.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.messages 2 | 3 | class MessagesArrayList : ArrayList() { 4 | 5 | override fun add(element: MessageUI): Boolean { 6 | val id = this.size.toString() 7 | return super.add(element.copyWithId(id)) 8 | } 9 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/messages/MessagesCommunication.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.messages 2 | 3 | import androidx.lifecycle.LifecycleOwner 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.Observer 6 | import com.github.johnnysc.coremvvm.presentation.Communication 7 | 8 | interface MessagesCommunication { 9 | 10 | interface Observe : Communication.Observe> 11 | interface Update : Communication.Update 12 | 13 | interface Mutable : Observe, Update 14 | 15 | class Base( 16 | private val mutableLiveData: MutableLiveData = MutableLiveData(), 17 | ) : Mutable { 18 | 19 | override fun observe(owner: LifecycleOwner, observer: Observer>) = 20 | mutableLiveData.observe(owner, observer) 21 | 22 | override fun map(data: MessageUI) { 23 | val list = mutableLiveData.value ?: MessagesArrayList() 24 | list.add(data) 25 | mutableLiveData.value = list 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/messages/MessagesFragment.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.messages 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.recyclerview.widget.RecyclerView 6 | import com.github.johnnysc.coremvvm.presentation.BaseFragment 7 | import com.github.johnnysc.ecp.R 8 | import com.github.johnnysc.ecp.presentation.messages.adapter.MessageAdapter 9 | import com.google.android.material.floatingactionbutton.FloatingActionButton 10 | 11 | 12 | class MessagesFragment : BaseFragment() { 13 | 14 | override val layoutResId = R.layout.fragment_main 15 | 16 | override fun viewModelClass(): Class = MessagesViewModel::class.java 17 | 18 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 19 | super.onViewCreated(view, savedInstanceState) 20 | val messagesRecyclerView = view.findViewById(R.id.messagesRecyclerView) 21 | val messageAdapter = MessageAdapter() 22 | messagesRecyclerView.adapter = messageAdapter 23 | val messageInput = view.findViewById(R.id.messageEditText) 24 | val sendMessageButton = view.findViewById(R.id.sendMessageButton) 25 | sendMessageButton.setOnClickListener { 26 | messageInput.handleInput(viewModel) 27 | } 28 | viewModel.observe(this) { 29 | messageAdapter.map(it) 30 | messagesRecyclerView.scrollDown() 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/messages/MessagesRecyclerView.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.messages 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import androidx.recyclerview.widget.RecyclerView 6 | 7 | class MessagesRecyclerView : RecyclerView { 8 | 9 | constructor(context: Context) : super(context) 10 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) 11 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( 12 | context, 13 | attrs, 14 | defStyleAttr 15 | ) 16 | 17 | fun scrollDown() { 18 | adapter?.let { scrollToPosition(it.itemCount - 1) } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/messages/MessagesViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.messages 2 | 3 | import androidx.lifecycle.LifecycleOwner 4 | import androidx.lifecycle.Observer 5 | import androidx.lifecycle.ViewModel 6 | import androidx.lifecycle.viewModelScope 7 | import com.github.johnnysc.coremvvm.core.Dispatchers 8 | 9 | class MessagesViewModel( 10 | private val dispatchers: Dispatchers, 11 | private val communication: MessagesCommunication.Mutable, 12 | private val viewModelChain: FeatureChain.Handle 13 | ) : ViewModel(), MessagesCommunication.Observe, HandleInput { 14 | 15 | override fun handleInput(message: String) { 16 | communication.map(MessageUI.User(message)) 17 | dispatchers.launchBackground(viewModelScope) { 18 | val messageUI = viewModelChain.handle(message) 19 | dispatchers.changeToUI { communication.map(messageUI) } 20 | } 21 | } 22 | 23 | override fun observe(owner: LifecycleOwner, observer: Observer>) = 24 | communication.observe(owner, observer) 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/messages/UnknownMessageViewModelChain.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.messages 2 | 3 | class UnknownMessageViewModelChain(featureChain: FeatureChain.UnknownMessageChain) : ViewModelChain(featureChain) -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/messages/ViewModelChain.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.messages 2 | 3 | abstract class ViewModelChain( 4 | private val featureChain: FeatureChain.CheckAndHandle, 5 | ) : FeatureChain.Handle { 6 | 7 | protected var nextFeatureChain: FeatureChain.Handle = FeatureChain.Empty() 8 | 9 | override suspend fun handle(message: String) = 10 | if (featureChain.canHandle(message)) 11 | featureChain.handle(message) 12 | else 13 | nextFeatureChain.handle(message) 14 | 15 | fun nextFeatureChain(featureChain: FeatureChain.Handle) { 16 | nextFeatureChain = featureChain 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/messages/adapter/AbstractMessageViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.messages.adapter 2 | 3 | import android.view.View 4 | import com.github.johnnysc.coremvvm.presentation.adapter.GenericViewHolder 5 | import com.github.johnnysc.ecp.R 6 | import com.github.johnnysc.ecp.presentation.messages.MessageUI 7 | 8 | abstract class AbstractMessageViewHolder(view: View) : GenericViewHolder(view) { 9 | 10 | override fun bind(item: MessageUI) { 11 | item.show(itemView.findViewById(R.id.messageTextView)) 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/messages/adapter/CorrectAiMessageViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.messages.adapter 2 | 3 | import android.view.View 4 | 5 | class CorrectAiMessageViewHolder(view: View) : AbstractMessageViewHolder(view) -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/messages/adapter/CorrectAiMessageViewHolderFactoryChain.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.messages.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import com.github.johnnysc.coremvvm.presentation.adapter.ViewHolderFactoryChain 6 | import com.github.johnnysc.ecp.R 7 | import com.github.johnnysc.ecp.presentation.messages.MessageUI 8 | 9 | class CorrectAiMessageViewHolderFactoryChain(private val viewHolderFactoryChain: ViewHolderFactoryChain) : 10 | ViewHolderFactoryChain { 11 | 12 | override fun viewHolder(parent: ViewGroup, viewType: Int) = 13 | if (viewType == 2) { 14 | CorrectAiMessageViewHolder( 15 | LayoutInflater.from(parent.context) 16 | .inflate(R.layout.ai_correct_message, parent, false) 17 | ) 18 | } else viewHolderFactoryChain.viewHolder(parent, viewType) 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/messages/adapter/ErrorAiMessageViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.messages.adapter 2 | 3 | import android.view.View 4 | 5 | class ErrorAiMessageViewHolder(view: View) : AbstractMessageViewHolder(view) -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/messages/adapter/ErrorAiMessageViewHolderFactoryChain.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.messages.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import com.github.johnnysc.coremvvm.presentation.adapter.ViewHolderFactoryChain 6 | import com.github.johnnysc.ecp.R 7 | import com.github.johnnysc.ecp.presentation.messages.MessageUI 8 | 9 | class ErrorAiMessageViewHolderFactoryChain(private val viewHolderFactoryChain: ViewHolderFactoryChain) : 10 | ViewHolderFactoryChain { 11 | 12 | override fun viewHolder(parent: ViewGroup, viewType: Int) = 13 | if (viewType == 3) { 14 | ErrorAiMessageViewHolder( 15 | LayoutInflater.from(parent.context) 16 | .inflate(R.layout.ai_incorrect_message, parent, false) 17 | ) 18 | } else viewHolderFactoryChain.viewHolder(parent, viewType) 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/messages/adapter/MessageAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.messages.adapter 2 | 3 | import com.github.johnnysc.coremvvm.presentation.adapter.GenericAdapter 4 | import com.github.johnnysc.coremvvm.presentation.adapter.ViewHolderFactoryChain 5 | import com.github.johnnysc.ecp.presentation.messages.MessageUI 6 | 7 | class MessageAdapter : GenericAdapter( 8 | CorrectAiMessageViewHolderFactoryChain( 9 | ErrorAiMessageViewHolderFactoryChain( 10 | UserMessageViewHolderFactoryChain( 11 | ViewHolderFactoryChain.Exception() 12 | ) 13 | ) 14 | ) 15 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/messages/adapter/UserMessageViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.messages.adapter 2 | 3 | import android.view.View 4 | 5 | class UserMessageViewHolder(view: View) : AbstractMessageViewHolder(view) -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/messages/adapter/UserMessageViewHolderFactoryChain.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.messages.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import com.github.johnnysc.coremvvm.presentation.adapter.ViewHolderFactoryChain 6 | import com.github.johnnysc.ecp.R 7 | import com.github.johnnysc.ecp.presentation.messages.MessageUI 8 | 9 | class UserMessageViewHolderFactoryChain(private val viewHolderFactoryChain: ViewHolderFactoryChain) : 10 | ViewHolderFactoryChain { 11 | 12 | override fun viewHolder(parent: ViewGroup, viewType: Int) = 13 | if (viewType == 1) { 14 | UserMessageViewHolder( 15 | LayoutInflater.from(parent.context) 16 | .inflate(R.layout.user_message, parent, false) 17 | ) 18 | } else viewHolderFactoryChain.viewHolder(parent, viewType) 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/weather/WeatherChain.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.weather 2 | 3 | import com.github.johnnysc.coremvvm.core.ManageResources 4 | import com.github.johnnysc.ecp.domain.weather.WeatherInteractor 5 | import com.github.johnnysc.ecp.presentation.commands.Command 6 | import com.github.johnnysc.ecp.presentation.messages.FeatureChain 7 | import com.github.johnnysc.ecp.presentation.messages.MessageUI 8 | import com.github.johnnysc.ecp.presentation.weather.commands.setdefault.ParseCity 9 | import com.github.johnnysc.ecp.presentation.weather.commands.setdefault.WeatherSetCityCommand 10 | import com.github.johnnysc.ecp.presentation.weather.commands.weatherdefault.ParseDefaultWeather 11 | import com.github.johnnysc.ecp.presentation.weather.commands.weatherdefault.WeatherDefaultCommand 12 | import com.github.johnnysc.ecp.presentation.weather.commands.weatherincity.ParseWeatherInCity 13 | import com.github.johnnysc.ecp.presentation.weather.commands.weatherincity.WeatherInCityCommand 14 | 15 | class WeatherChain( 16 | private val interactor: WeatherInteractor, 17 | private val manageResources: ManageResources, 18 | private val commands: List> 19 | = listOf( 20 | WeatherInCityCommand(ParseWeatherInCity(manageResources)), 21 | WeatherDefaultCommand(ParseDefaultWeather(manageResources)), 22 | WeatherSetCityCommand(ParseCity(manageResources)) 23 | ) 24 | ) : FeatureChain.CheckAndHandle { 25 | 26 | private var currentCommand: Command = Command.Empty() 27 | 28 | override fun canHandle(message: String): Boolean { 29 | val find = commands.find { 30 | it.canHandle(message) 31 | } 32 | find?.let { 33 | currentCommand = it 34 | } 35 | return find != null 36 | } 37 | 38 | override suspend fun handle(message: String): MessageUI { 39 | val result = currentCommand.handle(interactor) 40 | currentCommand = Command.Empty() 41 | return result 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/weather/WeatherViewModelChain.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.weather 2 | 3 | import com.github.johnnysc.ecp.presentation.messages.ViewModelChain 4 | 5 | class WeatherViewModelChain(weatherChain: WeatherChain) : ViewModelChain(weatherChain) -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/weather/commands/setdefault/ParseCity.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.weather.commands.setdefault 2 | 3 | import com.github.johnnysc.coremvvm.core.ManageResources 4 | import com.github.johnnysc.ecp.R 5 | import com.github.johnnysc.ecp.domain.weather.DefaultCityUseCase 6 | import com.github.johnnysc.ecp.presentation.commands.Parser 7 | import com.github.johnnysc.ecp.presentation.weather.commands.weatherincity.IsEmptyHandleUseCase 8 | 9 | class ParseCity(private val manageResources: ManageResources) : Parser { 10 | 11 | override fun map(data: String): IsEmptyHandleUseCase { 12 | val commandStart = manageResources.string(R.string.set_weather_command_start) 13 | if (data.startsWith(commandStart, true)) { 14 | val city = data.substring(commandStart.length).trim() 15 | if (city.isNotEmpty()) { 16 | return SetDefaultCity(city) 17 | } 18 | } 19 | return IsEmptyHandleUseCase.Empty() 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/weather/commands/setdefault/SetDefaultCity.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.weather.commands.setdefault 2 | 3 | import com.github.johnnysc.ecp.domain.weather.DefaultCityUseCase 4 | import com.github.johnnysc.ecp.presentation.messages.MessageUI 5 | import com.github.johnnysc.ecp.presentation.weather.commands.weatherincity.IsEmptyHandleUseCase 6 | 7 | data class SetDefaultCity(private val city: String) : IsEmptyHandleUseCase { 8 | 9 | override suspend fun handle(useCase: DefaultCityUseCase): MessageUI = useCase.setDefault(city) 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/weather/commands/setdefault/WeatherSetCityCommand.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.weather.commands.setdefault 2 | 3 | import com.github.johnnysc.ecp.domain.weather.DefaultCityUseCase 4 | import com.github.johnnysc.ecp.domain.weather.WeatherInteractor 5 | import com.github.johnnysc.ecp.presentation.commands.Command 6 | 7 | class WeatherSetCityCommand(parser: ParseCity) : 8 | Command.Abstract(parser) -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/weather/commands/weatherdefault/ParseDefaultWeather.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.weather.commands.weatherdefault 2 | 3 | import com.github.johnnysc.coremvvm.core.ManageResources 4 | import com.github.johnnysc.ecp.R 5 | import com.github.johnnysc.ecp.domain.weather.WeatherDefaultCityUseCase 6 | import com.github.johnnysc.ecp.presentation.commands.Parser 7 | import com.github.johnnysc.ecp.presentation.weather.commands.weatherincity.IsEmptyHandleUseCase 8 | 9 | class ParseDefaultWeather( 10 | private val manageResources: ManageResources 11 | ) : Parser { 12 | 13 | override fun map(data: String): IsEmptyHandleUseCase { 14 | if (manageResources.string(R.string.what_is_weather_like).equals( 15 | data, 16 | true 17 | ) || manageResources.string(R.string.whats_weather_like).equals( 18 | data, 19 | true 20 | ) 21 | ) { 22 | return WeatherInCityNotMentioned 23 | } 24 | return IsEmptyHandleUseCase.Empty() 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/weather/commands/weatherdefault/WeatherDefaultCommand.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.weather.commands.weatherdefault 2 | 3 | import com.github.johnnysc.ecp.domain.weather.WeatherDefaultCityUseCase 4 | import com.github.johnnysc.ecp.domain.weather.WeatherInteractor 5 | import com.github.johnnysc.ecp.presentation.commands.Command 6 | 7 | class WeatherDefaultCommand(parser: ParseDefaultWeather) : 8 | Command.Abstract(parser) -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/weather/commands/weatherdefault/WeatherInCityNotMentioned.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.weather.commands.weatherdefault 2 | 3 | import com.github.johnnysc.ecp.domain.weather.WeatherDefaultCityUseCase 4 | import com.github.johnnysc.ecp.presentation.messages.MessageUI 5 | import com.github.johnnysc.ecp.presentation.weather.commands.weatherincity.IsEmptyHandleUseCase 6 | 7 | object WeatherInCityNotMentioned : IsEmptyHandleUseCase { 8 | 9 | override suspend fun handle(useCase: WeatherDefaultCityUseCase): MessageUI = useCase.getWeather() 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/weather/commands/weatherincity/ParseWeatherInCity.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.weather.commands.weatherincity 2 | 3 | import com.github.johnnysc.coremvvm.core.ManageResources 4 | import com.github.johnnysc.ecp.R 5 | import com.github.johnnysc.ecp.domain.weather.WeatherInCityUseCase 6 | import com.github.johnnysc.ecp.presentation.commands.HandleUseCase 7 | import com.github.johnnysc.ecp.presentation.commands.Parser 8 | 9 | class ParseWeatherInCity(private val manageResources: ManageResources) : Parser { 10 | 11 | override fun map(data: String): IsEmptyHandleUseCase { 12 | val commandStart = manageResources.string(R.string.what_weather_command_start) 13 | if (data.startsWith(commandStart, true)) { 14 | val city = data.substring(commandStart.length).trim() 15 | if (city.isNotEmpty()) { 16 | return WeatherInCity(city) 17 | } 18 | } 19 | return IsEmptyHandleUseCase.Empty() 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/weather/commands/weatherincity/WeatherInCity.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.weather.commands.weatherincity 2 | 3 | import com.github.johnnysc.ecp.domain.weather.WeatherInCityUseCase 4 | import com.github.johnnysc.ecp.presentation.commands.HandleUseCase 5 | import com.github.johnnysc.ecp.presentation.messages.MessageUI 6 | 7 | data class WeatherInCity(private val city: String) : IsEmptyHandleUseCase { 8 | 9 | override suspend fun handle(useCase: WeatherInCityUseCase): MessageUI = useCase.getWeather(city) 10 | } 11 | 12 | interface IsEmptyHandleUseCase : IsEmpty, HandleUseCase { 13 | 14 | class Empty : IsEmptyHandleUseCase { 15 | override suspend fun handle(useCase: T) = MessageUI.Empty() 16 | override fun isEmpty() = true 17 | } 18 | } 19 | 20 | interface IsEmpty {//todo move to core 21 | fun isEmpty(): Boolean = false 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/presentation/weather/commands/weatherincity/WeatherInCityCommand.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.weather.commands.weatherincity 2 | 3 | import com.github.johnnysc.ecp.domain.weather.WeatherInCityUseCase 4 | import com.github.johnnysc.ecp.domain.weather.WeatherInteractor 5 | import com.github.johnnysc.ecp.presentation.commands.Command 6 | 7 | class WeatherInCityCommand(parser: ParseWeatherInCity) : 8 | Command.Abstract(parser) -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/sl/MainApplication.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.sl 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.ViewModelProvider 6 | import androidx.lifecycle.ViewModelStoreOwner 7 | import com.github.johnnysc.coremvvm.core.ManageResources 8 | import com.github.johnnysc.coremvvm.sl.CoreModule 9 | import com.github.johnnysc.coremvvm.sl.DependencyContainer 10 | import com.github.johnnysc.coremvvm.sl.ProvideViewModel 11 | import com.github.johnnysc.coremvvm.sl.ViewModelsFactory 12 | import com.github.johnnysc.ecp.sl.main.MainDependencyContainer 13 | import com.github.johnnysc.ecp.sl.message.MessagesDependencyContainer 14 | import com.github.johnnysc.ecp.sl.weather.ProvideWeatherViewModelChain 15 | 16 | class MainApplication : Application(), ProvideViewModel { 17 | 18 | private lateinit var viewModelsFactory: ViewModelsFactory 19 | 20 | override fun onCreate() { 21 | super.onCreate() 22 | val coreModule = CoreModule.Base(this) 23 | val main = MainDependencyContainer( 24 | DependencyContainer.Error() 25 | ) 26 | val manageResources: ManageResources = ManageResources.Base(this) 27 | val provideIDontUnderstandYouViewModelChain = 28 | ProvideIDontUnderstandYouViewModelChain(manageResources) 29 | val provideWeatherViewModelChain = ProvideWeatherViewModelChain(coreModule, this) 30 | val provideViewModelChain = 31 | ProvideViewModelChain.Base( 32 | provideWeatherViewModelChain, 33 | provideIDontUnderstandYouViewModelChain 34 | ) 35 | val messagesDependencyContainer = 36 | MessagesDependencyContainer(main, coreModule, provideViewModelChain.viewModelChain()) 37 | viewModelsFactory = ViewModelsFactory(messagesDependencyContainer) 38 | } 39 | 40 | override fun provideViewModel(clazz: Class, owner: ViewModelStoreOwner) = 41 | ViewModelProvider(owner, viewModelsFactory)[clazz] 42 | 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/sl/ProvideConvertRawResourceToPojoAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.sl 2 | 3 | import com.github.johnnysc.ecp.core.StringToObject 4 | import com.github.johnnysc.ecp.core.ConvertRawResourceToPoJo 5 | import com.github.johnnysc.ecp.core.ReadRawResource 6 | 7 | abstract class ProvideConvertRawResourceToPojoAdapter( 8 | protected val readRawResource: ReadRawResource, 9 | protected val stringToObject: StringToObject 10 | ) { 11 | abstract fun provideConvertRawResourceToPojoAdapter(): ConvertRawResourceToPoJo 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/sl/ProvideExceptionChain.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.sl 2 | 3 | import com.github.johnnysc.ecp.domain.ExceptionChain 4 | 5 | interface ProvideExceptionChain { 6 | 7 | fun provideExceptionChain(): ExceptionChain 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/sl/ProvideIDontUnderstandYouViewModelChain.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.sl 2 | 3 | import com.github.johnnysc.coremvvm.core.ManageResources 4 | import com.github.johnnysc.ecp.presentation.messages.FeatureChain 5 | import com.github.johnnysc.ecp.presentation.messages.UnknownMessageViewModelChain 6 | 7 | class ProvideIDontUnderstandYouViewModelChain(private val manageResources: ManageResources) : 8 | ProvideViewModelChain { 9 | 10 | override fun viewModelChain() = UnknownMessageViewModelChain(FeatureChain.UnknownMessageChain(manageResources)) 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/sl/ProvideSharedPreferences.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.sl 2 | 3 | import android.content.SharedPreferences 4 | import com.github.johnnysc.coremvvm.sl.CoreModule 5 | 6 | interface ProvideSharedPreferences { 7 | 8 | fun provideSharedPreferences(): SharedPreferences 9 | 10 | abstract class AbstractProvideSharedPreferences(private val coreModule: CoreModule) : ProvideSharedPreferences { 11 | protected abstract val sharedPreferencesName: String 12 | 13 | override fun provideSharedPreferences(): SharedPreferences { 14 | return coreModule.sharedPreferences(sharedPreferencesName) 15 | } 16 | } 17 | 18 | class ProvideTestSettingsSharedPref(coreModule: CoreModule) : AbstractProvideSharedPreferences(coreModule) { 19 | override val sharedPreferencesName = "testSettingsSharedPreferences" 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/sl/ProvideViewModelChain.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.sl 2 | 3 | import com.github.johnnysc.ecp.presentation.messages.ViewModelChain 4 | 5 | interface ProvideViewModelChain { 6 | 7 | fun viewModelChain(): T 8 | 9 | class Base(private vararg val provideViewModelChain: ProvideViewModelChain) : 10 | ProvideViewModelChain { 11 | override fun viewModelChain(): ViewModelChain { 12 | val listOfViewModelChain = provideViewModelChain.map { provideViewModelChain -> 13 | provideViewModelChain.viewModelChain() 14 | } 15 | 16 | val finalChain = listOfViewModelChain.first() 17 | 18 | listOfViewModelChain.forEachIndexed { index, viewModelChain -> 19 | if (index < listOfViewModelChain.lastIndex) 20 | viewModelChain.nextFeatureChain(listOfViewModelChain[index + 1]) 21 | 22 | } 23 | return finalChain 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/sl/main/MainDependencyContainer.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.sl.main 2 | 3 | import androidx.lifecycle.ViewModel 4 | import com.github.johnnysc.coremvvm.presentation.NavigationCommunication 5 | import com.github.johnnysc.coremvvm.sl.DependencyContainer 6 | import com.github.johnnysc.ecp.presentation.main.MainViewModel 7 | 8 | class MainDependencyContainer( 9 | private val dependencyContainer: DependencyContainer 10 | ) : DependencyContainer { 11 | override fun module(clazz: Class) = 12 | if (clazz == MainViewModel::class.java) 13 | MainModule( 14 | NavigationCommunication.Base() 15 | ) 16 | else 17 | dependencyContainer.module(clazz) 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/sl/main/MainModule.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.sl.main 2 | 3 | import com.github.johnnysc.coremvvm.presentation.NavigationCommunication 4 | import com.github.johnnysc.coremvvm.sl.Module 5 | import com.github.johnnysc.ecp.presentation.main.MainViewModel 6 | 7 | class MainModule( 8 | private val navigationCommunication: NavigationCommunication.Base 9 | ) : Module { 10 | override fun viewModel(): MainViewModel = 11 | MainViewModel(navigationCommunication) 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/sl/message/MessagesDependencyContainer.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.sl.message 2 | 3 | import androidx.lifecycle.ViewModel 4 | import com.github.johnnysc.coremvvm.sl.CoreModule 5 | import com.github.johnnysc.coremvvm.sl.DependencyContainer 6 | import com.github.johnnysc.ecp.presentation.messages.MessagesViewModel 7 | import com.github.johnnysc.ecp.presentation.messages.ViewModelChain 8 | 9 | class MessagesDependencyContainer( 10 | private val dependencyContainer: DependencyContainer, 11 | private val coreModule: CoreModule, 12 | private val viewModelChain: ViewModelChain, 13 | ) : DependencyContainer { 14 | 15 | override fun module(clazz: Class) = if (clazz == MessagesViewModel::class.java) { 16 | MessagesModule(coreModule, viewModelChain) 17 | } else { 18 | dependencyContainer.module(clazz) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/sl/message/MessagesModule.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.sl.message 2 | 3 | import com.github.johnnysc.coremvvm.sl.CoreModule 4 | import com.github.johnnysc.coremvvm.sl.Module 5 | import com.github.johnnysc.ecp.presentation.messages.MessagesCommunication 6 | import com.github.johnnysc.ecp.presentation.messages.MessagesViewModel 7 | import com.github.johnnysc.ecp.presentation.messages.ViewModelChain 8 | 9 | class MessagesModule( 10 | private val coreModule: CoreModule, 11 | private val viewModelChain: ViewModelChain 12 | ) : Module { 13 | 14 | override fun viewModel() = 15 | MessagesViewModel(coreModule.dispatchers(), MessagesCommunication.Base(), viewModelChain) 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/sl/weather/ProvideCacheDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.sl.weather 2 | 3 | import com.github.johnnysc.ecp.data.weather.cache.CityCacheDataSource 4 | 5 | interface ProvideCacheDataSource { 6 | fun provideCacheDataSource(): CityCacheDataSource 7 | 8 | class Base(private val cityPreferenceDataStore: ProvideCityPreferenceDataStore) : 9 | ProvideCacheDataSource { 10 | override fun provideCacheDataSource() = 11 | CityCacheDataSource.Base(cityPreferenceDataStore.provideCityPreferenceDataStore()) 12 | 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/sl/weather/ProvideCityPreferenceDataStore.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.sl.weather 2 | 3 | import com.github.johnnysc.coremvvm.sl.CoreModule 4 | import com.github.johnnysc.ecp.data.weather.cache.CityPreferenceDataStore 5 | 6 | interface ProvideCityPreferenceDataStore { 7 | 8 | fun provideCityPreferenceDataStore(): CityPreferenceDataStore 9 | 10 | class Base(private val coreModule: CoreModule) : ProvideCityPreferenceDataStore { 11 | override fun provideCityPreferenceDataStore() = 12 | CityPreferenceDataStore.Base(ProvideCitySharedPref(coreModule).provideSharedPreferences()) 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/sl/weather/ProvideCitySharedPref.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.sl.weather 2 | 3 | import com.github.johnnysc.coremvvm.sl.CoreModule 4 | import com.github.johnnysc.ecp.sl.ProvideSharedPreferences 5 | 6 | class ProvideCitySharedPref(coreModule: CoreModule) : 7 | ProvideSharedPreferences.AbstractProvideSharedPreferences(coreModule) { 8 | override val sharedPreferencesName = "citySharedPref" 9 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/sl/weather/ProvideWeatherCloudDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.sl.weather 2 | 3 | import com.github.johnnysc.ecp.core.InternetConnection 4 | import com.github.johnnysc.ecp.data.weather.cloud.HandleWeatherExceptions 5 | import com.github.johnnysc.ecp.data.weather.cloud.RemoteWeather 6 | import com.github.johnnysc.ecp.data.weather.cloud.Weather 7 | import com.github.johnnysc.ecp.data.weather.cloud.WeatherCloudDataSource 8 | import com.github.johnnysc.ecp.sl.ProvideConvertRawResourceToPojoAdapter 9 | import com.github.johnnysc.ecp.sl.ProvideSharedPreferences 10 | 11 | interface ProvideWeatherCloudDataSource { 12 | 13 | fun provideCloudDataSource(): WeatherCloudDataSource 14 | 15 | class Base(private val provideWeatherCloud: ProvideWeatherService) : 16 | ProvideWeatherCloudDataSource { 17 | 18 | override fun provideCloudDataSource() = WeatherCloudDataSource.Base( 19 | HandleWeatherExceptions(), 20 | provideWeatherCloud.provideWeatherService() 21 | ) 22 | 23 | } 24 | 25 | class Mock( 26 | private val provideConvertRawResourceToPojoAdapter: ProvideConvertRawResourceToPojoAdapter, 27 | private val provideSharedPreferences: ProvideSharedPreferences 28 | ) : 29 | ProvideWeatherCloudDataSource { 30 | 31 | override fun provideCloudDataSource() = WeatherCloudDataSource.Mock( 32 | provideConvertRawResourceToPojoAdapter.provideConvertRawResourceToPojoAdapter(), 33 | InternetConnection.Base(provideSharedPreferences.provideSharedPreferences()), 34 | HandleWeatherExceptions() 35 | ) 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/sl/weather/ProvideWeatherConverterRawToPojo.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.sl.weather 2 | 3 | import com.github.johnnysc.ecp.core.StringToObject 4 | import com.github.johnnysc.ecp.core.ConvertRawResourceToPoJo 5 | import com.github.johnnysc.ecp.core.ReadRawResource 6 | import com.github.johnnysc.ecp.data.weather.cloud.RemoteWeather 7 | import com.github.johnnysc.ecp.data.weather.cloud.Weather 8 | import com.github.johnnysc.ecp.data.weather.cloud.WeatherCloudDataSource 9 | import com.github.johnnysc.ecp.sl.ProvideConvertRawResourceToPojoAdapter 10 | 11 | class ProvideWeatherConverterRawToPojo( 12 | stringToObject: StringToObject, 13 | readRawResource: ReadRawResource 14 | ) : ProvideConvertRawResourceToPojoAdapter( 15 | readRawResource, 16 | stringToObject 17 | ) { 18 | override fun provideConvertRawResourceToPojoAdapter(): ConvertRawResourceToPoJo { 19 | return WeatherCloudDataSource.Mock.FetchWeather(stringToObject, readRawResource) 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/sl/weather/ProvideWeatherExceptionChain.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.sl.weather 2 | 3 | import com.github.johnnysc.ecp.domain.ExceptionChain 4 | import com.github.johnnysc.ecp.sl.ProvideExceptionChain 5 | 6 | class ProvideWeatherExceptionChain : ProvideExceptionChain { 7 | 8 | override fun provideExceptionChain(): ExceptionChain { 9 | return ExceptionChain.ThereIsNoConnectionChain( 10 | ExceptionChain.ThereIsNoSuchCityChain( 11 | ExceptionChain.ThereIsNoDefaultCityChain( 12 | ExceptionChain.DefaultExceptionChain() 13 | ) 14 | ) 15 | ) 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/sl/weather/ProvideWeatherInteractor.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.sl.weather 2 | 3 | import com.github.johnnysc.coremvvm.core.ManageResources 4 | import com.github.johnnysc.ecp.domain.DomainException 5 | import com.github.johnnysc.ecp.domain.weather.CityDomain 6 | import com.github.johnnysc.ecp.domain.weather.WeatherDomain 7 | import com.github.johnnysc.ecp.domain.weather.WeatherInteractor 8 | import com.github.johnnysc.ecp.sl.ProvideExceptionChain 9 | 10 | interface ProvideWeatherInteractor { 11 | fun provideWeatherInteractor(): WeatherInteractor 12 | 13 | class Base( 14 | private val provideWeatherRepository: ProvideWeatherRepository, 15 | private val manageResources: ManageResources, 16 | private val provideExceptionChain: ProvideExceptionChain 17 | ) : ProvideWeatherInteractor { 18 | override fun provideWeatherInteractor() = WeatherInteractor.Base( 19 | provideWeatherRepository.provideWeatherRepository(), 20 | WeatherDomain.Mapper.BaseToMessage(manageResources), 21 | CityDomain.Mapper.Base(manageResources), 22 | provideExceptionChain.provideExceptionChain(), 23 | DomainException.Mapper.Base(manageResources) 24 | ) 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/sl/weather/ProvideWeatherRepository.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.sl.weather 2 | 3 | import com.github.johnnysc.ecp.data.weather.BaseWeatherRepository 4 | import com.github.johnnysc.ecp.data.weather.CityData 5 | import com.github.johnnysc.ecp.data.weather.cloud.RemoteWeather 6 | import com.github.johnnysc.ecp.domain.weather.WeatherRepository 7 | 8 | interface ProvideWeatherRepository { 9 | fun provideWeatherRepository(): WeatherRepository 10 | 11 | class Base( 12 | private val provideWeatherCloud: ProvideWeatherCloudDataSource, 13 | private val provideCityPreferenceDataStore: ProvideCacheDataSource 14 | ) : ProvideWeatherRepository { 15 | override fun provideWeatherRepository() = BaseWeatherRepository( 16 | provideWeatherCloud.provideCloudDataSource(), 17 | provideCityPreferenceDataStore.provideCacheDataSource(), 18 | RemoteWeather.Mapper.Base(), 19 | CityData.Mapper.Base() 20 | ) 21 | } 22 | } 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/sl/weather/ProvideWeatherService.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.sl.weather 2 | 3 | import com.github.johnnysc.coremvvm.data.MakeService 4 | import com.github.johnnysc.coremvvm.data.ProvideRetrofitBuilder 5 | import com.github.johnnysc.ecp.data.weather.cloud.WeatherService 6 | 7 | interface ProvideWeatherService { 8 | 9 | fun provideWeatherService(): WeatherService 10 | 11 | class Base( 12 | retrofitBuilder: ProvideRetrofitBuilder 13 | ) : MakeService.Abstract( 14 | retrofitBuilder 15 | ), ProvideWeatherService { 16 | 17 | override fun provideWeatherService(): WeatherService = service(WeatherService::class.java) 18 | 19 | override fun baseUrl(): String = "https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/" 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/johnnysc/ecp/sl/weather/ProvideWeatherViewModelChain.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.sl.weather 2 | 3 | import android.content.Context 4 | import com.github.johnnysc.coremvvm.core.ManageResources 5 | import com.github.johnnysc.coremvvm.sl.CoreModule 6 | import com.github.johnnysc.ecp.BuildConfig.BUILD_TYPE 7 | import com.github.johnnysc.ecp.core.StringToObject 8 | import com.github.johnnysc.ecp.core.ReadRawResource 9 | import com.github.johnnysc.ecp.data.weather.cloud.WeatherCloudDataSource 10 | import com.github.johnnysc.ecp.presentation.weather.WeatherChain 11 | import com.github.johnnysc.ecp.presentation.weather.WeatherViewModelChain 12 | import com.github.johnnysc.ecp.sl.ProvideSharedPreferences 13 | import com.github.johnnysc.ecp.sl.ProvideViewModelChain 14 | import com.google.gson.Gson 15 | 16 | class ProvideWeatherViewModelChain( 17 | private val coreModule: CoreModule, 18 | private val context: Context 19 | ) : ProvideViewModelChain { 20 | companion object { 21 | private const val uiTestVariant = "uitests" 22 | } 23 | 24 | override fun viewModelChain(): WeatherViewModelChain { 25 | val manageResources = ManageResources.Base(context) 26 | val provideWeatherCloudDataSource = 27 | if (BUILD_TYPE == uiTestVariant) { 28 | val provideConvertRawResourceToPojoAdapter = ProvideWeatherConverterRawToPojo( 29 | StringToObject.Base( 30 | WeatherCloudDataSource.Mock.WeatherResponseToken(), Gson() 31 | ), ReadRawResource.Mock(context) 32 | ) 33 | ProvideWeatherCloudDataSource.Mock( 34 | provideConvertRawResourceToPojoAdapter, 35 | ProvideSharedPreferences.ProvideTestSettingsSharedPref(coreModule) 36 | ) 37 | } else { 38 | val provideWeatherCloud = ProvideWeatherService.Base(coreModule) 39 | ProvideWeatherCloudDataSource.Base(provideWeatherCloud) 40 | } 41 | val provideCityPreferenceDataStore = 42 | ProvideCityPreferenceDataStore.Base(coreModule) 43 | val provideCacheDataSource = ProvideCacheDataSource.Base(provideCityPreferenceDataStore) 44 | val provideWeatherRepository = 45 | ProvideWeatherRepository.Base(provideWeatherCloudDataSource, provideCacheDataSource) 46 | val provideWeatherExceptionChain = ProvideWeatherExceptionChain() 47 | val provideWeatherInteractor = ProvideWeatherInteractor.Base( 48 | provideWeatherRepository, 49 | manageResources, 50 | provideWeatherExceptionChain 51 | ) 52 | return WeatherViewModelChain( 53 | WeatherChain( 54 | provideWeatherInteractor.provideWeatherInteractor(), 55 | manageResources 56 | ) 57 | ) 58 | } 59 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/edit_text_cursor.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/ai_correct_message.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/ai_incorrect_message.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 20 | 21 | 31 | 32 | 45 | -------------------------------------------------------------------------------- /app/src/main/res/layout/user_message.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 11 | 12 | 18 | 19 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnnySC/ECP/3a7bc0b607b7807c47e61b0c9ac0fc8170b020ba/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnnySC/ECP/3a7bc0b607b7807c47e61b0c9ac0fc8170b020ba/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnnySC/ECP/3a7bc0b607b7807c47e61b0c9ac0fc8170b020ba/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnnySC/ECP/3a7bc0b607b7807c47e61b0c9ac0fc8170b020ba/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnnySC/ECP/3a7bc0b607b7807c47e61b0c9ac0fc8170b020ba/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnnySC/ECP/3a7bc0b607b7807c47e61b0c9ac0fc8170b020ba/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnnySC/ECP/3a7bc0b607b7807c47e61b0c9ac0fc8170b020ba/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnnySC/ECP/3a7bc0b607b7807c47e61b0c9ac0fc8170b020ba/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnnySC/ECP/3a7bc0b607b7807c47e61b0c9ac0fc8170b020ba/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnnySC/ECP/3a7bc0b607b7807c47e61b0c9ac0fc8170b020ba/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/raw/weather_invalid_loc_response: -------------------------------------------------------------------------------- 1 | Invalid location found. Please check your location parameter:Ротрстан -------------------------------------------------------------------------------- /app/src/main/res/raw/weather_succesfull_responce_for_almaty: -------------------------------------------------------------------------------- 1 | { 2 | "currentConditions": { 3 | "temp": 34.0 4 | } 5 | } -------------------------------------------------------------------------------- /app/src/main/res/raw/wether_succesfull_responce_for_ekibastuz: -------------------------------------------------------------------------------- 1 | { 2 | "currentConditions": { 3 | "temp": 25.8 4 | } 5 | } -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | #4CAF50 11 | #E91E63 12 | #00BCD4 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8dp 4 | 8dp 5 | 32dp 6 | 20sp 7 | 16dp 8 | 0dp 9 | 10dp 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ECP 3 | Enter Message 4 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 5 | small 6 | I don\'t understand you! 7 | Error! Set default city using this command: My city is X, where X is the name of the city 8 | My city is 9 | Current temperature: %1$s\u2103. 10 | what is the weather like 11 | what\'s the weather like 12 | What the weather is like in 13 | Default city set to %1$s 14 | Unknown exception 15 | There is no city with such title 16 | There is no connection 17 | Send message 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 19 | 20 | 23 | 24 | 27 | 28 | 31 | 32 | 39 | 40 | 47 | 48 | 52 | 53 | 57 | 58 | 63 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/test/java/com/github/johnnysc/ecp/data/weather/BaseWeatherRepositoryTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.data.weather 2 | 3 | import com.github.johnnysc.ecp.data.weather.exceptions.ThereIsNoCityWithSuchTitleException 4 | import com.github.johnnysc.ecp.data.weather.exceptions.ThereIsNoDefaultCityException 5 | import com.github.johnnysc.ecp.data.weather.cache.CityCacheDataSource 6 | import com.github.johnnysc.ecp.data.weather.cloud.RemoteWeather 7 | import com.github.johnnysc.ecp.data.weather.cloud.WeatherCloudDataSource 8 | import com.github.johnnysc.ecp.data.weather.cloud.CurrentWeather 9 | import com.github.johnnysc.ecp.data.weather.cloud.Weather 10 | import com.github.johnnysc.ecp.domain.weather.WeatherDomain 11 | import kotlinx.coroutines.runBlocking 12 | import org.junit.Assert 13 | import org.junit.Test 14 | 15 | class BaseWeatherRepositoryTest { 16 | 17 | @Test 18 | fun `test success get weather in city`() = runBlocking { 19 | val repository = BaseWeatherRepository( 20 | TestCloudDataSource(), 21 | TestCacheDataSource(true), 22 | RemoteWeather.Mapper.Base(), 23 | CityData.Mapper.Base() 24 | ) 25 | Assert.assertEquals(WeatherDomain.Base(30F), repository.getWeatherInCity("Saint Paul")) 26 | } 27 | 28 | @Test 29 | fun `test success get weather in default city`() = runBlocking { 30 | val repository = BaseWeatherRepository( 31 | TestCloudDataSource(), 32 | TestCacheDataSource(true), 33 | RemoteWeather.Mapper.Base(), 34 | CityData.Mapper.Base() 35 | ) 36 | Assert.assertEquals(WeatherDomain.Base(20F), repository.getWeatherInDefaultCity()) 37 | } 38 | 39 | @Test 40 | fun `test success set default city`() = runBlocking { 41 | val cache = TestCacheDataSource(true) 42 | val repository = BaseWeatherRepository( 43 | TestCloudDataSource(), 44 | cache, 45 | RemoteWeather.Mapper.Base(), 46 | CityData.Mapper.Base() 47 | ) 48 | repository.saveDefaultCity("Saint Paul") 49 | Assert.assertEquals(cache.savedCity, "Saint Paul") 50 | } 51 | 52 | @Test 53 | fun `test failed get weather in city`(): Unit = runBlocking { 54 | val repository = BaseWeatherRepository( 55 | TestCloudDataSource(), 56 | TestCacheDataSource(false), 57 | RemoteWeather.Mapper.Base(), 58 | CityData.Mapper.Base() 59 | ) 60 | Assert.assertThrows(ThereIsNoCityWithSuchTitleException::class.java) { 61 | runBlocking { repository.getWeatherInCity("Innsmouth") } 62 | } 63 | } 64 | 65 | @Test 66 | fun `test failed get weather in default city`(): Unit = runBlocking { 67 | val repository = BaseWeatherRepository( 68 | TestCloudDataSource(), 69 | TestCacheDataSource(false), 70 | RemoteWeather.Mapper.Base(), 71 | CityData.Mapper.Base() 72 | ) 73 | Assert.assertThrows(ThereIsNoDefaultCityException::class.java) { 74 | runBlocking { repository.getWeatherInDefaultCity() } 75 | } 76 | } 77 | 78 | @Test 79 | fun `test failed set default city`(): Unit = runBlocking { 80 | val repository = BaseWeatherRepository( 81 | TestCloudDataSource(), 82 | TestCacheDataSource(false), 83 | RemoteWeather.Mapper.Base(), 84 | CityData.Mapper.Base() 85 | ) 86 | Assert.assertThrows(ThereIsNoCityWithSuchTitleException::class.java) { 87 | runBlocking { repository.saveDefaultCity("Innsmouth") } 88 | } 89 | } 90 | 91 | class TestCloudDataSource : WeatherCloudDataSource { 92 | 93 | override suspend fun getWeather(cityName: String): RemoteWeather = 94 | RemoteWeather.Base(Weather.Base(CurrentWeather.Base(if (cityName == "Hawkins") 20F else if (cityName == "Saint Paul") 30F else throw ThereIsNoCityWithSuchTitleException()))) 95 | } 96 | 97 | class TestCacheDataSource(private val isDefaultSet: Boolean) : CityCacheDataSource { 98 | 99 | var savedCity = "Hawkins" 100 | 101 | override fun getDefaultCity(): CityData { 102 | if (!isDefaultSet) 103 | throw ThereIsNoDefaultCityException() 104 | return CityData.Base("Hawkins") 105 | } 106 | 107 | override fun saveDefaultCity(newDefaultCity: String) { 108 | savedCity = newDefaultCity 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /app/src/test/java/com/github/johnnysc/ecp/domain/ExceptionChainTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.domain 2 | 3 | import com.github.johnnysc.coremvvm.core.ManageResources 4 | import com.github.johnnysc.coremvvm.domain.NoInternetConnectionException 5 | import com.github.johnnysc.ecp.R 6 | import com.github.johnnysc.ecp.data.weather.exceptions.ThereIsNoCityWithSuchTitleException 7 | import com.github.johnnysc.ecp.data.weather.exceptions.ThereIsNoDefaultCityException 8 | import com.github.johnnysc.ecp.presentation.messages.MessageUI 9 | import org.junit.Assert.assertEquals 10 | import org.junit.Test 11 | 12 | class ExceptionChainTest { 13 | 14 | private val chain = ExceptionChain.ThereIsNoSuchCityChain( 15 | ExceptionChain.ThereIsNoDefaultCityChain( 16 | ExceptionChain.ThereIsNoConnectionChain( 17 | ExceptionChain.DefaultExceptionChain() 18 | ) 19 | ) 20 | ) 21 | private val mapper = DomainException.Mapper.Base(TestManageResource()) 22 | 23 | @Test 24 | fun `test exception chain with standard exceptions from data layer of weather feature`() { 25 | val expectedOne = MessageUI.AiError("There is no connection") 26 | val expectedTwo = MessageUI.AiError("There is no city with such title") 27 | val expectedThree = 28 | MessageUI.AiError("Error! Set default city using this command: My city is X, where X is the name of the city") 29 | 30 | val inputOne = NoInternetConnectionException() 31 | val inputTwo = ThereIsNoCityWithSuchTitleException() 32 | val inputThree = ThereIsNoDefaultCityException() 33 | 34 | val actualOne = chain.handle(inputOne).map(mapper) 35 | assertEquals(expectedOne, actualOne) 36 | val actualTwo = chain.handle(inputTwo).map(mapper) 37 | assertEquals(expectedTwo, actualTwo) 38 | val actualThree = chain.handle(inputThree).map(mapper) 39 | assertEquals(expectedThree, actualThree) 40 | 41 | } 42 | 43 | @Test 44 | fun `test exception chain with unknown exception`() { 45 | val expected = MessageUI.AiError("Unknown exception") 46 | val input = IndexOutOfBoundsException() 47 | val actual = chain.handle(input).map(mapper) 48 | assertEquals(expected, actual) 49 | } 50 | 51 | class TestManageResource : ManageResources { 52 | override fun string(id: Int): String { 53 | return when (id) { 54 | R.string.unknown_exception -> "Unknown exception" 55 | R.string.there_is_no_city_with_such_title -> "There is no city with such title" 56 | R.string.there_is_no_connection -> "There is no connection" 57 | R.string.weather_no_default_city -> "Error! Set default city using this command: My city is X, where X is the name of the city" 58 | else -> "Something went wrong" 59 | } 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /app/src/test/java/com/github/johnnysc/ecp/domain/weather/WeatherInteractorTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.domain.weather 2 | 3 | import com.github.johnnysc.coremvvm.core.ManageResources 4 | import com.github.johnnysc.coremvvm.domain.NoInternetConnectionException 5 | import com.github.johnnysc.ecp.R 6 | import com.github.johnnysc.ecp.data.weather.exceptions.ThereIsNoCityWithSuchTitleException 7 | import com.github.johnnysc.ecp.data.weather.exceptions.ThereIsNoDefaultCityException 8 | import com.github.johnnysc.ecp.domain.DomainException 9 | import com.github.johnnysc.ecp.domain.ExceptionChain 10 | import com.github.johnnysc.ecp.presentation.messages.MessageUI 11 | import kotlinx.coroutines.runBlocking 12 | import org.junit.Assert 13 | import org.junit.Test 14 | 15 | class WeatherInteractorTest { 16 | 17 | private val chain = ExceptionChain.ThereIsNoSuchCityChain( 18 | ExceptionChain.ThereIsNoDefaultCityChain( 19 | ExceptionChain.ThereIsNoConnectionChain( 20 | ExceptionChain.DefaultExceptionChain() 21 | ) 22 | ) 23 | ) 24 | private val testManageResource = TestManageResource() 25 | private val weatherDomainToMessageUIMapper = 26 | WeatherDomain.Mapper.BaseToMessage(testManageResource) 27 | private val cityDomainToMessageUIMapper = CityDomain.Mapper.Base(testManageResource) 28 | private val domainExceptionToUIMapper = DomainException.Mapper.Base(testManageResource) 29 | private val city = "Астана" 30 | 31 | @Test 32 | fun `all methods without internet get MessageUi AiError`() = runBlocking { 33 | val weatherRepository = 34 | TestWeatherRepository(isInternetAvailable = false, isDefaultCitySet = true) 35 | val weatherInteractor = WeatherInteractor.Base( 36 | weatherRepository, 37 | weatherDomainToMessageUIMapper, 38 | cityDomainToMessageUIMapper, chain, domainExceptionToUIMapper 39 | ) 40 | val expected = MessageUI.AiError(testManageResource.string(R.string.there_is_no_connection)) 41 | 42 | val actualOne = weatherInteractor.getWeather() 43 | Assert.assertEquals(expected, actualOne) 44 | val actualTwo = weatherInteractor.getWeather(city) 45 | Assert.assertEquals(expected, actualTwo) 46 | val actualThree = weatherInteractor.setDefault(city) 47 | Assert.assertEquals(expected, actualThree) 48 | } 49 | 50 | @Test 51 | fun `get weather in default city without default city get MessageUI AiError`() = runBlocking { 52 | val weatherRepository = 53 | TestWeatherRepository(isInternetAvailable = true, isDefaultCitySet = false) 54 | val weatherInteractor = WeatherInteractor.Base( 55 | weatherRepository, 56 | weatherDomainToMessageUIMapper, 57 | cityDomainToMessageUIMapper, chain, domainExceptionToUIMapper 58 | ) 59 | 60 | val expected = 61 | MessageUI.AiError(testManageResource.string(R.string.weather_no_default_city)) 62 | 63 | val actual = weatherInteractor.getWeather() 64 | 65 | Assert.assertEquals(expected, actual) 66 | } 67 | 68 | @Test 69 | fun `get weather in default city with default city get MessageUi Ai with temperature`() = runBlocking { 70 | val weatherRepository = 71 | TestWeatherRepository(isInternetAvailable = true, isDefaultCitySet = true) 72 | val weatherInteractor = WeatherInteractor.Base( 73 | weatherRepository, 74 | weatherDomainToMessageUIMapper, 75 | cityDomainToMessageUIMapper, chain, domainExceptionToUIMapper 76 | ) 77 | 78 | val expected = 79 | MessageUI.Ai(testManageResource.string(R.string.weather_response).format(23F)) 80 | 81 | val actual = weatherInteractor.getWeather() 82 | 83 | Assert.assertEquals(expected, actual) 84 | } 85 | 86 | @Test 87 | fun `get weather with existed city get MessageUi Ai with temperature in city`() = runBlocking { 88 | val weatherRepository = 89 | TestWeatherRepository(isInternetAvailable = true, isDefaultCitySet = true) 90 | val weatherInteractor = WeatherInteractor.Base( 91 | weatherRepository, 92 | weatherDomainToMessageUIMapper, 93 | cityDomainToMessageUIMapper, chain, domainExceptionToUIMapper 94 | ) 95 | 96 | val expected = 97 | MessageUI.Ai(testManageResource.string(R.string.weather_response).format(21F)) 98 | 99 | val actual = weatherInteractor.getWeather(city) 100 | 101 | Assert.assertEquals(expected, actual) 102 | } 103 | 104 | @Test 105 | fun `get temperature with none existed city get MessageUi AiError with ThereIsNoCityWithSuchTitle exception`() = runBlocking { 106 | val noneExistedCity = "Ротрстан" 107 | val weatherRepository = 108 | TestWeatherRepository(isInternetAvailable = true, isDefaultCitySet = true) 109 | val weatherInteractor = WeatherInteractor.Base( 110 | weatherRepository, 111 | weatherDomainToMessageUIMapper, 112 | cityDomainToMessageUIMapper, chain, domainExceptionToUIMapper 113 | ) 114 | 115 | val expected = 116 | MessageUI.AiError(testManageResource.string(R.string.there_is_no_city_with_such_title)) 117 | 118 | val actual = weatherInteractor.getWeather(noneExistedCity) 119 | 120 | Assert.assertEquals(expected, actual) 121 | } 122 | 123 | @Test 124 | fun `setDefaultCity with existed city get MessageUi Ai with confirmation`() = runBlocking { 125 | val weatherRepository = 126 | TestWeatherRepository(isInternetAvailable = true, isDefaultCitySet = true) 127 | val weatherInteractor = WeatherInteractor.Base( 128 | weatherRepository, 129 | weatherDomainToMessageUIMapper, 130 | cityDomainToMessageUIMapper, chain, domainExceptionToUIMapper 131 | ) 132 | 133 | val expected = 134 | MessageUI.Ai(testManageResource.string(R.string.set_weather_command_success).format(city)) 135 | println(expected) 136 | val actual = weatherInteractor.setDefault(city) 137 | 138 | Assert.assertEquals(expected, actual) 139 | } 140 | 141 | @Test 142 | fun `setDefaultCity with none existed city get MessageUI AiError with ThereIsNoCityWithSuchTitle message `() = runBlocking { 143 | val noneExistedCity = "Ротрстан" 144 | val weatherRepository = 145 | TestWeatherRepository(isInternetAvailable = true, isDefaultCitySet = true) 146 | val weatherInteractor = WeatherInteractor.Base( 147 | weatherRepository, 148 | weatherDomainToMessageUIMapper, 149 | cityDomainToMessageUIMapper, chain, domainExceptionToUIMapper 150 | ) 151 | 152 | val expected = 153 | MessageUI.AiError(testManageResource.string(R.string.there_is_no_city_with_such_title)) 154 | 155 | val actual = weatherInteractor.setDefault(noneExistedCity) 156 | 157 | Assert.assertEquals(expected, actual) 158 | } 159 | 160 | 161 | class TestWeatherRepository( 162 | private val isInternetAvailable: Boolean, 163 | private val isDefaultCitySet: Boolean 164 | ) : WeatherRepository { 165 | private var defaultCity: String = if (isDefaultCitySet) 166 | "Экибастуз" 167 | else 168 | "" 169 | private val weatherDomainMap = mutableMapOf() 170 | 171 | init { 172 | weatherDomainMap.apply { 173 | put("Экибастуз", WeatherDomain.Base(23F)) 174 | put("Астана", WeatherDomain.Base(21F)) 175 | put("Алмата", WeatherDomain.Base(35F)) 176 | put("Павлодар", WeatherDomain.Base(21F)) 177 | put("Семипалатинск", WeatherDomain.Base(19F)) 178 | } 179 | } 180 | 181 | override suspend fun getWeatherInCity(city: String): WeatherDomain { 182 | checkInternetConnection() 183 | if (weatherDomainMap.contains(city)) 184 | return weatherDomainMap[city]!! 185 | throw ThereIsNoCityWithSuchTitleException() 186 | } 187 | 188 | override suspend fun getWeatherInDefaultCity(): WeatherDomain { 189 | if (isDefaultCitySet) { 190 | checkInternetConnection() 191 | return weatherDomainMap[defaultCity]!! 192 | } 193 | throw ThereIsNoDefaultCityException() 194 | } 195 | 196 | override suspend fun saveDefaultCity(newCity: String) { 197 | val cities = weatherDomainMap.keys 198 | checkInternetConnection() 199 | if (!cities.contains(newCity)) 200 | throw ThereIsNoCityWithSuchTitleException() 201 | } 202 | 203 | private fun checkInternetConnection() { 204 | if (isInternetAvailable) 205 | return 206 | throw NoInternetConnectionException() 207 | } 208 | } 209 | 210 | class TestManageResource : ManageResources { 211 | override fun string(id: Int): String { 212 | return when (id) { 213 | R.string.unknown_exception -> "Unknown exception" 214 | R.string.there_is_no_city_with_such_title -> "There is no city with such title" 215 | R.string.there_is_no_connection -> "There is no connection" 216 | R.string.weather_no_default_city -> "Error! Set default city using this command: My city is X, where X is the name of the city" 217 | R.string.weather_response -> "Current temperature: %1\$s\u2103 " 218 | R.string.set_weather_command_success -> "Default city set to %1\$s!" 219 | else -> "Something went wrong" 220 | } 221 | } 222 | } 223 | } -------------------------------------------------------------------------------- /app/src/test/java/com/github/johnnysc/ecp/presentation/messages/MessagesArrayListTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.messages 2 | 3 | import org.junit.Assert 4 | import org.junit.Test 5 | 6 | internal class MessagesArrayListTest { 7 | 8 | @Test 9 | fun `test list`() { 10 | val list = MessagesArrayList() 11 | val messageUser = MessageUI.User("message user") 12 | val messageAi = MessageUI.Ai("message ai") 13 | 14 | list.add(messageUser) 15 | var expected = listOf(MessageUI.User("message user", "0")) 16 | Assert.assertEquals(expected, list) 17 | 18 | list.add(messageAi) 19 | expected = listOf( 20 | MessageUI.User("message user", "0"), 21 | MessageUI.Ai("message ai", "1") 22 | ) 23 | Assert.assertEquals(expected, list) 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/test/java/com/github/johnnysc/ecp/presentation/messages/MessagesViewModelTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.messages 2 | 3 | import androidx.lifecycle.LifecycleOwner 4 | import androidx.lifecycle.Observer 5 | import com.github.johnnysc.coremvvm.core.Dispatchers 6 | import kotlinx.coroutines.CoroutineDispatcher 7 | import kotlinx.coroutines.runBlocking 8 | import kotlinx.coroutines.test.TestCoroutineDispatcher 9 | import org.junit.Assert 10 | import org.junit.Test 11 | 12 | internal class MessagesViewModelTest { 13 | 14 | @Test 15 | fun `test successful response`() = runBlocking { 16 | val testChainFactory = TestChainFactory(TestChainOne()) 17 | testChainFactory.nextFeatureChain(TestChainTwo()) 18 | val communication = TestCommunication() 19 | val dispatchers = TestDispatchers() 20 | val viewModel = MessagesViewModel( 21 | dispatchers = dispatchers, 22 | communication = communication, 23 | viewModelChain = testChainFactory 24 | ) 25 | viewModel.handleInput("For first one") 26 | Assert.assertEquals(MessageUI.User("For first one", "0"), communication.messages[0]) 27 | Assert.assertEquals(MessageUI.Ai("First message", "1"), communication.messages[1]) 28 | } 29 | 30 | @Test 31 | fun `test error response`() = runBlocking { 32 | val testChainFactory = TestChainFactory(TestChainOne()) 33 | testChainFactory.nextFeatureChain(TestChainTwo()) 34 | val communication = TestCommunication() 35 | val dispatchers = TestDispatchers() 36 | val viewModel = MessagesViewModel( 37 | dispatchers = dispatchers, 38 | communication = communication, 39 | viewModelChain = testChainFactory 40 | ) 41 | viewModel.handleInput("For second one") 42 | Assert.assertEquals(MessageUI.User("For second one", "0"), communication.messages[0]) 43 | Assert.assertEquals(MessageUI.AiError("I don't understand you", "1"), communication.messages[1]) 44 | } 45 | 46 | private class TestChainFactory(feature: FeatureChain.CheckAndHandle) : ViewModelChain(feature) 47 | 48 | private class TestChainOne : FeatureChain.CheckAndHandle { 49 | override fun canHandle(message: String): Boolean = message == "For first one" 50 | 51 | override suspend fun handle(message: String): MessageUI = MessageUI.Ai("First message") 52 | } 53 | 54 | private class TestChainTwo : FeatureChain.Handle { 55 | override suspend fun handle(message: String): MessageUI = MessageUI.AiError("I don't understand you") 56 | } 57 | 58 | private class TestCommunication : MessagesCommunication.Mutable { 59 | 60 | var messages = MessagesArrayList() 61 | 62 | override fun map(data: MessageUI) { 63 | messages.add(data) 64 | } 65 | 66 | override fun observe(owner: LifecycleOwner, observer: Observer>) = Unit 67 | } 68 | 69 | private class TestDispatchers( 70 | dispatcher: CoroutineDispatcher = TestCoroutineDispatcher() 71 | ) : Dispatchers.Abstract(dispatcher, dispatcher) 72 | } -------------------------------------------------------------------------------- /app/src/test/java/com/github/johnnysc/ecp/presentation/weather/WeatherParsersTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.presentation.weather 2 | 3 | import com.github.johnnysc.coremvvm.core.ManageResources 4 | import com.github.johnnysc.ecp.domain.weather.WeatherDefaultCityUseCase 5 | import com.github.johnnysc.ecp.presentation.weather.commands.setdefault.ParseCity 6 | import com.github.johnnysc.ecp.presentation.weather.commands.setdefault.SetDefaultCity 7 | import com.github.johnnysc.ecp.presentation.weather.commands.weatherdefault.ParseDefaultWeather 8 | import com.github.johnnysc.ecp.presentation.weather.commands.weatherdefault.WeatherInCityNotMentioned 9 | import com.github.johnnysc.ecp.presentation.weather.commands.weatherincity.IsEmptyHandleUseCase 10 | import com.github.johnnysc.ecp.presentation.weather.commands.weatherincity.ParseWeatherInCity 11 | import com.github.johnnysc.ecp.presentation.weather.commands.weatherincity.WeatherInCity 12 | import kotlinx.coroutines.runBlocking 13 | import org.junit.Assert 14 | import org.junit.Test 15 | 16 | internal class WeatherParsersTest { 17 | 18 | @Test 19 | fun `success default weather`() = runBlocking { 20 | val testManageResources = TestManageResources("What's the weather like") 21 | val parser = ParseDefaultWeather(testManageResources) 22 | Assert.assertEquals(WeatherInCityNotMentioned, parser.map("what's The WeaThEr lIKE")) 23 | } 24 | 25 | @Test 26 | fun `failed default weather`() = runBlocking { 27 | val testManageResources = TestManageResources("What's the weather like") 28 | val parser = ParseDefaultWeather(testManageResources) 29 | Assert.assertEquals(true, parser.map("Alexa, tell some joke!").isEmpty()) 30 | } 31 | 32 | @Test 33 | fun `success weather in city`() = runBlocking { 34 | val testManageResources = TestManageResources("What the weather is like in") 35 | val parser = ParseWeatherInCity(testManageResources) 36 | Assert.assertEquals(WeatherInCity("Miami"), parser.map("what the WeATHer is lIKE in Miami")) 37 | } 38 | 39 | @Test 40 | fun `failed weather in city`() = runBlocking { 41 | val testManageResources = TestManageResources("What the weather is like in") 42 | val parser = ParseWeatherInCity(testManageResources) 43 | Assert.assertEquals(true, parser.map("Whot da weader is like in Miame").isEmpty()) 44 | Assert.assertEquals(true, parser.map("What the weather is like in ").isEmpty()) 45 | } 46 | 47 | @Test 48 | fun `success set city`() = runBlocking { 49 | val testManageResources = TestManageResources("My city is") 50 | val parser = ParseCity(testManageResources) 51 | Assert.assertEquals(SetDefaultCity("Miami"), parser.map("My city is Miami")) 52 | } 53 | 54 | @Test 55 | fun `failed set city`() = runBlocking { 56 | val testManageResources = TestManageResources("My city is") 57 | val parser = ParseCity(testManageResources) 58 | Assert.assertEquals(true, parser.map("Mai sity is Daytona beach").isEmpty()) 59 | Assert.assertEquals(true, parser.map("My city is ").isEmpty()) 60 | } 61 | 62 | private class TestManageResources(private val value: String) : ManageResources { 63 | override fun string(id: Int): String = value 64 | } 65 | } -------------------------------------------------------------------------------- /app/src/test/java/com/github/johnnysc/ecp/sl/ProvideViewModelChainTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.johnnysc.ecp.sl 2 | 3 | import com.github.johnnysc.ecp.presentation.messages.FeatureChain 4 | import com.github.johnnysc.ecp.presentation.messages.MessageUI 5 | import com.github.johnnysc.ecp.presentation.messages.ViewModelChain 6 | import org.junit.Assert 7 | import org.junit.Test 8 | 9 | class ProvideViewModelChainTest { 10 | 11 | @Test 12 | fun `test creation of ViewModelChain creation`() { 13 | val featureChainOne = FeatureTestChain() 14 | 15 | val viewModelChainBaseTestOne = ViewModelChainTest(featureChainOne) 16 | val viewModelChainBaseTestTwo = ViewModelChainTest(featureChainOne) 17 | val viewModelChainBaseTestThree = ViewModelChainTest(featureChainOne) 18 | 19 | val provideViewModelChainTestOne = ProvideViewModelChainTest(viewModelChainBaseTestOne) 20 | val provideViewModelChainTestTwo = ProvideViewModelChainTest(viewModelChainBaseTestTwo) 21 | val provideViewModelChainTestThree = ProvideViewModelChainTest(viewModelChainBaseTestThree) 22 | 23 | val result = ProvideViewModelChain.Base( 24 | provideViewModelChainTestOne, 25 | provideViewModelChainTestTwo, 26 | provideViewModelChainTestThree 27 | ).viewModelChain() 28 | Assert.assertEquals(viewModelChainBaseTestOne, result) 29 | 30 | var nextFeatureChain = (result as ViewModelChainTest).provideNextFeatureChain() 31 | Assert.assertEquals(viewModelChainBaseTestTwo, nextFeatureChain) 32 | 33 | nextFeatureChain = (nextFeatureChain as ViewModelChainTest).provideNextFeatureChain() 34 | Assert.assertEquals(viewModelChainBaseTestThree, nextFeatureChain) 35 | } 36 | 37 | private class ViewModelChainTest(featureChain: FeatureChain.CheckAndHandle) : 38 | ViewModelChain(featureChain) { 39 | fun provideNextFeatureChain(): FeatureChain.Handle { 40 | return nextFeatureChain 41 | } 42 | } 43 | 44 | private class ProvideViewModelChainTest(private val viewModelChain: ViewModelChainTest) : 45 | ProvideViewModelChain { 46 | override fun viewModelChain() = viewModelChain 47 | } 48 | 49 | private class FeatureTestChain : FeatureChain.CheckAndHandle { 50 | override fun canHandle(message: String): Boolean { 51 | return true 52 | } 53 | 54 | override suspend fun handle(message: String): MessageUI { 55 | return MessageUI.Empty() 56 | } 57 | } 58 | 59 | 60 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | dependencies { 8 | classpath "com.android.tools.build:gradle:7.2.1" 9 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10" 10 | } 11 | } 12 | 13 | allprojects { 14 | repositories { 15 | google() 16 | mavenCentral() 17 | maven { 18 | url "../libs" 19 | } 20 | } 21 | } 22 | 23 | task clean(type: Delete) { 24 | delete rootProject.buildDir 25 | } -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnnySC/ECP/3a7bc0b607b7807c47e61b0c9ac0fc8170b020ba/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Dec 04 11:17:50 MSK 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /libs/com/github/johnnysc/coremvvm/1.0/coremvvm-1.0-javadoc.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnnySC/ECP/3a7bc0b607b7807c47e61b0c9ac0fc8170b020ba/libs/com/github/johnnysc/coremvvm/1.0/coremvvm-1.0-javadoc.jar -------------------------------------------------------------------------------- /libs/com/github/johnnysc/coremvvm/1.0/coremvvm-1.0-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnnySC/ECP/3a7bc0b607b7807c47e61b0c9ac0fc8170b020ba/libs/com/github/johnnysc/coremvvm/1.0/coremvvm-1.0-sources.jar -------------------------------------------------------------------------------- /libs/com/github/johnnysc/coremvvm/1.0/coremvvm-1.0.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnnySC/ECP/3a7bc0b607b7807c47e61b0c9ac0fc8170b020ba/libs/com/github/johnnysc/coremvvm/1.0/coremvvm-1.0.aar -------------------------------------------------------------------------------- /libs/com/github/johnnysc/coremvvm/1.0/coremvvm-1.0.module: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": "1.1", 3 | "component": { 4 | "group": "com.github.johnnysc", 5 | "module": "coremvvm", 6 | "version": "1.0", 7 | "attributes": { 8 | "org.gradle.status": "release" 9 | } 10 | }, 11 | "createdBy": { 12 | "gradle": { 13 | "version": "7.4.2" 14 | } 15 | }, 16 | "variants": [ 17 | { 18 | "name": "releaseVariantReleaseApiPublication", 19 | "attributes": { 20 | "org.gradle.category": "library", 21 | "org.gradle.dependency.bundling": "external", 22 | "org.gradle.libraryelements": "aar", 23 | "org.gradle.usage": "java-api" 24 | }, 25 | "dependencies": [ 26 | { 27 | "group": "com.google.android.material", 28 | "module": "material", 29 | "version": { 30 | "requires": "1.6.1" 31 | } 32 | }, 33 | { 34 | "group": "androidx.lifecycle", 35 | "module": "lifecycle-viewmodel-ktx", 36 | "version": { 37 | "requires": "2.4.1" 38 | } 39 | }, 40 | { 41 | "group": "androidx.lifecycle", 42 | "module": "lifecycle-livedata-ktx", 43 | "version": { 44 | "requires": "2.4.1" 45 | } 46 | }, 47 | { 48 | "group": "androidx.core", 49 | "module": "core-ktx", 50 | "version": { 51 | "requires": "1.8.0" 52 | } 53 | }, 54 | { 55 | "group": "org.jetbrains.kotlinx", 56 | "module": "kotlinx-coroutines-android", 57 | "version": { 58 | "requires": "1.6.3" 59 | } 60 | }, 61 | { 62 | "group": "com.squareup.okhttp3", 63 | "module": "logging-interceptor", 64 | "version": { 65 | "requires": "4.9.3" 66 | } 67 | }, 68 | { 69 | "group": "com.squareup.retrofit2", 70 | "module": "retrofit", 71 | "version": { 72 | "requires": "2.9.0" 73 | } 74 | }, 75 | { 76 | "group": "com.squareup.retrofit2", 77 | "module": "converter-gson", 78 | "version": { 79 | "requires": "2.9.0" 80 | } 81 | }, 82 | { 83 | "group": "androidx.appcompat", 84 | "module": "appcompat", 85 | "version": { 86 | "requires": "1.4.2" 87 | } 88 | }, 89 | { 90 | "group": "org.jetbrains.kotlin", 91 | "module": "kotlin-stdlib-jdk8", 92 | "version": { 93 | "requires": "1.6.10" 94 | } 95 | } 96 | ], 97 | "files": [ 98 | { 99 | "name": "coremvvm-1.0.aar", 100 | "url": "coremvvm-1.0.aar", 101 | "size": 138934, 102 | "sha512": "bf6392e883221e88c01f251a543442cf8cc8a34066d332e3f48a914bedfdc86788947118480d2c2627cfc29e3714858ad239f9a61d33aefc373e73aed901f6d8", 103 | "sha256": "1476702455a732c94f726941234fe6f2a06c306255def9b7d248549df275beeb", 104 | "sha1": "af3a0b1104ad12f814e4e563c5a53e462d87b8b0", 105 | "md5": "83dbdd75726ca5b9cde421e2b0f75177" 106 | } 107 | ] 108 | }, 109 | { 110 | "name": "releaseVariantReleaseRuntimePublication", 111 | "attributes": { 112 | "org.gradle.category": "library", 113 | "org.gradle.dependency.bundling": "external", 114 | "org.gradle.libraryelements": "aar", 115 | "org.gradle.usage": "java-runtime" 116 | }, 117 | "dependencies": [ 118 | { 119 | "group": "com.google.android.material", 120 | "module": "material", 121 | "version": { 122 | "requires": "1.6.1" 123 | } 124 | }, 125 | { 126 | "group": "androidx.lifecycle", 127 | "module": "lifecycle-viewmodel-ktx", 128 | "version": { 129 | "requires": "2.4.1" 130 | } 131 | }, 132 | { 133 | "group": "androidx.lifecycle", 134 | "module": "lifecycle-livedata-ktx", 135 | "version": { 136 | "requires": "2.4.1" 137 | } 138 | }, 139 | { 140 | "group": "androidx.core", 141 | "module": "core-ktx", 142 | "version": { 143 | "requires": "1.8.0" 144 | } 145 | }, 146 | { 147 | "group": "org.jetbrains.kotlinx", 148 | "module": "kotlinx-coroutines-android", 149 | "version": { 150 | "requires": "1.6.3" 151 | } 152 | }, 153 | { 154 | "group": "com.squareup.okhttp3", 155 | "module": "logging-interceptor", 156 | "version": { 157 | "requires": "4.9.3" 158 | } 159 | }, 160 | { 161 | "group": "com.squareup.retrofit2", 162 | "module": "retrofit", 163 | "version": { 164 | "requires": "2.9.0" 165 | } 166 | }, 167 | { 168 | "group": "com.squareup.retrofit2", 169 | "module": "converter-gson", 170 | "version": { 171 | "requires": "2.9.0" 172 | } 173 | }, 174 | { 175 | "group": "androidx.appcompat", 176 | "module": "appcompat", 177 | "version": { 178 | "requires": "1.4.2" 179 | } 180 | }, 181 | { 182 | "group": "org.jetbrains.kotlin", 183 | "module": "kotlin-stdlib-jdk8", 184 | "version": { 185 | "requires": "1.6.10" 186 | } 187 | } 188 | ], 189 | "files": [ 190 | { 191 | "name": "coremvvm-1.0.aar", 192 | "url": "coremvvm-1.0.aar", 193 | "size": 138934, 194 | "sha512": "bf6392e883221e88c01f251a543442cf8cc8a34066d332e3f48a914bedfdc86788947118480d2c2627cfc29e3714858ad239f9a61d33aefc373e73aed901f6d8", 195 | "sha256": "1476702455a732c94f726941234fe6f2a06c306255def9b7d248549df275beeb", 196 | "sha1": "af3a0b1104ad12f814e4e563c5a53e462d87b8b0", 197 | "md5": "83dbdd75726ca5b9cde421e2b0f75177" 198 | } 199 | ] 200 | }, 201 | { 202 | "name": "releaseVariantReleaseSourcePublication", 203 | "attributes": { 204 | "org.gradle.category": "documentation", 205 | "org.gradle.dependency.bundling": "external", 206 | "org.gradle.docstype": "sources", 207 | "org.gradle.usage": "java-runtime" 208 | }, 209 | "files": [ 210 | { 211 | "name": "coremvvm-1.0-sources.jar", 212 | "url": "coremvvm-1.0-sources.jar", 213 | "size": 24210, 214 | "sha512": "54bea03e27d67997e314195833f102090749009d89ada5171f60d46d90827ccb2238f5ead037d43eaaf0bd24f1a00ef3fd1a9f8ace3941a1b80f30ee6e1e9600", 215 | "sha256": "53a4fe2ea4a65c8cd298161f492486780f66ebf4d477bdfd337cc0fe5371e698", 216 | "sha1": "c79e383dc234438b20b0fb0dd6ccc22b7ecb96c5", 217 | "md5": "83c34bbafe66bda44aee951fb25e736d" 218 | } 219 | ] 220 | }, 221 | { 222 | "name": "releaseVariantReleaseJavaDocPublication", 223 | "attributes": { 224 | "org.gradle.category": "documentation", 225 | "org.gradle.dependency.bundling": "external", 226 | "org.gradle.docstype": "javadoc", 227 | "org.gradle.usage": "java-runtime" 228 | }, 229 | "files": [ 230 | { 231 | "name": "coremvvm-1.0-javadoc.jar", 232 | "url": "coremvvm-1.0-javadoc.jar", 233 | "size": 743927, 234 | "sha512": "ac7c60179241a8dd114b42a1c60d74dca39871513d579aaa33e93916d3b0b8cf6bdd1020ba8f986783b040c32152c57f64efed0266319f207d251db974235042", 235 | "sha256": "45b9030c1dff297c0d27fde2f48a6ac157ec177086a25277554e0d1c8ff05a99", 236 | "sha1": "5bc471490b608f8162abac2fc4ac8394991b684c", 237 | "md5": "51f463ffb3238445877ec78a87bbdd25" 238 | } 239 | ] 240 | } 241 | ] 242 | } 243 | -------------------------------------------------------------------------------- /libs/com/github/johnnysc/coremvvm/1.0/coremvvm-1.0.pom: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 4.0.0 10 | com.github.johnnysc 11 | coremvvm 12 | 1.0 13 | aar 14 | 15 | 16 | com.google.android.material 17 | material 18 | 1.6.1 19 | compile 20 | 21 | 22 | androidx.lifecycle 23 | lifecycle-viewmodel-ktx 24 | 2.4.1 25 | compile 26 | 27 | 28 | androidx.lifecycle 29 | lifecycle-livedata-ktx 30 | 2.4.1 31 | compile 32 | 33 | 34 | androidx.core 35 | core-ktx 36 | 1.8.0 37 | compile 38 | 39 | 40 | org.jetbrains.kotlinx 41 | kotlinx-coroutines-android 42 | 1.6.3 43 | compile 44 | 45 | 46 | com.squareup.okhttp3 47 | logging-interceptor 48 | 4.9.3 49 | compile 50 | 51 | 52 | com.squareup.retrofit2 53 | retrofit 54 | 2.9.0 55 | compile 56 | 57 | 58 | com.squareup.retrofit2 59 | converter-gson 60 | 2.9.0 61 | compile 62 | 63 | 64 | androidx.appcompat 65 | appcompat 66 | 1.4.2 67 | compile 68 | 69 | 70 | org.jetbrains.kotlin 71 | kotlin-stdlib-jdk8 72 | 1.6.10 73 | compile 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /libs/com/github/johnnysc/coremvvm/maven-metadata-local.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | com.github.johnnysc 4 | coremvvm 5 | 6 | 1.0 7 | 1.0 8 | 9 | 1.0 10 | 11 | 20220711153018 12 | 13 | 14 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = "ECP" 2 | include ':app' 3 | --------------------------------------------------------------------------------