From 2a90aba88b07951f66572d2afdcf1a03c87a7bbc Mon Sep 17 00:00:00 2001 From: Melih Aksoy Date: Wed, 21 Aug 2019 17:15:01 +0200 Subject: [PATCH 01/20] Single activity approach (#30) * CircleCI config fixes * Implicit deeplinking via navController navigate * Removed jacoco.exec files. Closes # 32 --- .circleci/config.yml | 61 ++++++- .gitignore | 5 + Gemfile.lock | 159 ++++++++++++++++++ app/build.gradle | 3 + app/src/main/AndroidManifest.xml | 15 +- .../com/melih/rocketscience/MainActivity.kt | 31 ++++ .../melih/rocketscience/di/AppComponent.kt | 5 +- .../com/melih/rocketscience/di/AppModule.kt | 19 +++ .../src/main/res/layout/activity_main.xml | 15 +- app/src/main/res/navigation/nav_main.xml | 10 ++ build.gradle | 2 +- core/jacoco.exec | Bin 934 -> 0 bytes core/src/main/AndroidManifest.xml | 7 +- .../kotlin/com/melih/core/actions/Actions.kt | 16 +- .../melih/core/base/lifecycle/BaseActivity.kt | 56 ------ .../melih/core/base/lifecycle/BaseFragment.kt | 7 +- core/src/main/res/values/strings.xml | 21 +-- fastlane/Fastfile | 46 ++++- features/detail/build.gradle | 1 - features/detail/jacoco.exec | Bin 2651 -> 0 bytes features/detail/src/main/AndroidManifest.xml | 15 +- .../melih/detail/di/DetailFeatureModule.kt | 24 --- .../detail/di/modules/DetailFragmentModule.kt | 2 +- .../com/melih/detail/ui/DetailActivity.kt | 37 ---- .../src/main/res/layout/activity_detail.xml | 22 --- .../src/main/res/navigation/nav_detail.xml | 34 ++-- .../detail/src/main/res/values/strings.xml | 3 +- features/launches/jacoco.exec | Bin 2302 -> 0 bytes .../launches/src/main/AndroidManifest.xml | 15 +- .../melih/list/di/LaunchesFeatureModule.kt | 24 --- .../com/melih/list/ui/LaunchesActivity.kt | 26 --- .../com/melih/list/ui/LaunchesFragment.kt | 33 +--- .../src/main/res/navigation/nav_launches.xml | 3 +- scripts/default_android_config.gradle | 4 + scripts/feature_module.gradle | 1 + scripts/module.gradle | 2 +- 36 files changed, 402 insertions(+), 322 deletions(-) create mode 100644 Gemfile.lock create mode 100644 app/src/main/kotlin/com/melih/rocketscience/MainActivity.kt create mode 100644 app/src/main/kotlin/com/melih/rocketscience/di/AppModule.kt rename features/launches/src/main/res/layout/activity_launches.xml => app/src/main/res/layout/activity_main.xml (53%) create mode 100644 app/src/main/res/navigation/nav_main.xml delete mode 100644 core/jacoco.exec delete mode 100644 core/src/main/kotlin/com/melih/core/base/lifecycle/BaseActivity.kt delete mode 100644 features/detail/jacoco.exec delete mode 100644 features/detail/src/main/kotlin/com/melih/detail/di/DetailFeatureModule.kt delete mode 100644 features/detail/src/main/kotlin/com/melih/detail/ui/DetailActivity.kt delete mode 100644 features/detail/src/main/res/layout/activity_detail.xml delete mode 100644 features/launches/jacoco.exec delete mode 100644 features/launches/src/main/kotlin/com/melih/list/di/LaunchesFeatureModule.kt delete mode 100644 features/launches/src/main/kotlin/com/melih/list/ui/LaunchesActivity.kt diff --git a/.circleci/config.yml b/.circleci/config.yml index 4a7b433..edbe35c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -26,19 +26,66 @@ jobs: command: fastlane detekt - store_artifacts: # for display in Artifacts: https://circleci.com/docs/2.0/artifacts/ path: reports/detekt - - store_test_results: # for display in Test Summary: https://circleci.com/docs/2.0/collect-test-data/ - path: reports # Tests + ## App + - run: + name: Test App + command: | + fastlane test_app + ./gradlew app:jacocoTestReport + bash <(curl -s https://codecov.io/bash) + - store_artifacts: # for display in Artifacts: https://circleci.com/docs/2.0/artifacts/ + path: app/build/reports/tests + - store_test_results: # for display in Test Summary: https://circleci.com/docs/2.0/collect-test-data/ + path: app/build/test-results + + ## Core + - run: + name: Test Core + command: | + fastlane test_core + ./gradlew core:jacocoTestReport + bash <(curl -s https://codecov.io/bash) + - store_artifacts: # for display in Artifacts: https://circleci.com/docs/2.0/artifacts/ + path: core/build/reports/tests + - store_test_results: # for display in Test Summary: https://circleci.com/docs/2.0/collect-test-data/ + path: core/build/test-results + + ## Launches + - run: + name: Test Launches + command: | + fastlane test_launches + ./gradlew features:launches:jacocoTestReport + bash <(curl -s https://codecov.io/bash) + - store_artifacts: # for display in Artifacts: https://circleci.com/docs/2.0/artifacts/ + path: features/launches/build/reports/tests + - store_test_results: # for display in Test Summary: https://circleci.com/docs/2.0/collect-test-data/ + path: features/launches/build/test-results + + ## Detail + - run: + name: Test Detail + command: | + fastlane test_detail + ./gradlew features:detail:jacocoTestReport + bash <(curl -s https://codecov.io/bash) + - store_artifacts: # for display in Artifacts: https://circleci.com/docs/2.0/artifacts/ + path: features/detail/build/reports/tests + - store_test_results: # for display in Test Summary: https://circleci.com/docs/2.0/collect-test-data/ + path: features/detail/build/test-results + + ## Repository - run: - name: Tests + name: Test Repository command: | - fastlane test_all - ./gradlew jacocoTestReport + fastlane test_repository + ./gradlew repository:jacocoTestReport bash <(curl -s https://codecov.io/bash) - store_artifacts: # for display in Artifacts: https://circleci.com/docs/2.0/artifacts/ - path: build/reports/tests + path: repository/build/reports/tests - store_test_results: # for display in Test Summary: https://circleci.com/docs/2.0/collect-test-data/ - path: reports + path: repository/build/test-results # See https://circleci.com/docs/2.0/deployment-integrations/ for deploy examples diff --git a/.gitignore b/.gitignore index 90bb429..3a8ee31 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ /.idea .DS_Store /build +/fastlane/README.md +/fastlane/report.xml /captures .externalNativeBuild /projectFilesBackup @@ -11,3 +13,6 @@ # Project reports /reports + +#jacoco.exec +jacoco.exec diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..7eac911 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,159 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.0) + addressable (2.6.0) + public_suffix (>= 2.0.2, < 4.0) + atomos (0.1.3) + babosa (1.0.2) + claide (1.0.3) + colored (1.2) + colored2 (3.1.2) + commander-fastlane (4.4.6) + highline (~> 1.7.2) + declarative (0.0.10) + declarative-option (0.1.0) + digest-crc (0.4.1) + domain_name (0.5.20190701) + unf (>= 0.0.5, < 1.0.0) + dotenv (2.7.5) + emoji_regex (1.0.1) + excon (0.66.0) + faraday (0.15.4) + multipart-post (>= 1.2, < 3) + faraday-cookie_jar (0.0.6) + faraday (>= 0.7.4) + http-cookie (~> 1.0.0) + faraday_middleware (0.13.1) + faraday (>= 0.7.4, < 1.0) + fastimage (2.1.5) + fastlane (2.129.0) + CFPropertyList (>= 2.3, < 4.0.0) + addressable (>= 2.3, < 3.0.0) + babosa (>= 1.0.2, < 2.0.0) + bundler (>= 1.12.0, < 3.0.0) + colored + commander-fastlane (>= 4.4.6, < 5.0.0) + dotenv (>= 2.1.1, < 3.0.0) + emoji_regex (>= 0.1, < 2.0) + excon (>= 0.45.0, < 1.0.0) + faraday (~> 0.9) + faraday-cookie_jar (~> 0.0.6) + faraday_middleware (~> 0.9) + fastimage (>= 2.1.0, < 3.0.0) + gh_inspector (>= 1.1.2, < 2.0.0) + google-api-client (>= 0.21.2, < 0.24.0) + google-cloud-storage (>= 1.15.0, < 2.0.0) + highline (>= 1.7.2, < 2.0.0) + json (< 3.0.0) + jwt (~> 2.1.0) + mini_magick (>= 4.9.4, < 5.0.0) + multi_xml (~> 0.5) + multipart-post (~> 2.0.0) + plist (>= 3.1.0, < 4.0.0) + public_suffix (~> 2.0.0) + rubyzip (>= 1.2.2, < 2.0.0) + security (= 0.1.3) + simctl (~> 1.6.3) + slack-notifier (>= 2.0.0, < 3.0.0) + terminal-notifier (>= 2.0.0, < 3.0.0) + terminal-table (>= 1.4.5, < 2.0.0) + tty-screen (>= 0.6.3, < 1.0.0) + tty-spinner (>= 0.8.0, < 1.0.0) + word_wrap (~> 1.0.0) + xcodeproj (>= 1.8.1, < 2.0.0) + xcpretty (~> 0.3.0) + xcpretty-travis-formatter (>= 0.0.3) + gh_inspector (1.1.3) + google-api-client (0.23.9) + addressable (~> 2.5, >= 2.5.1) + googleauth (>= 0.5, < 0.7.0) + httpclient (>= 2.8.1, < 3.0) + mime-types (~> 3.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.0) + signet (~> 0.9) + google-cloud-core (1.3.0) + google-cloud-env (~> 1.0) + google-cloud-env (1.2.0) + faraday (~> 0.11) + google-cloud-storage (1.16.0) + digest-crc (~> 0.4) + google-api-client (~> 0.23) + google-cloud-core (~> 1.2) + googleauth (>= 0.6.2, < 0.10.0) + googleauth (0.6.7) + faraday (~> 0.12) + jwt (>= 1.4, < 3.0) + memoist (~> 0.16) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (~> 0.7) + highline (1.7.10) + http-cookie (1.0.3) + domain_name (~> 0.5) + httpclient (2.8.3) + json (2.2.0) + jwt (2.1.0) + memoist (0.16.0) + mime-types (3.2.2) + mime-types-data (~> 3.2015) + mime-types-data (3.2019.0331) + mini_magick (4.9.5) + multi_json (1.13.1) + multi_xml (0.6.0) + multipart-post (2.0.0) + nanaimo (0.2.6) + naturally (2.2.0) + os (1.0.1) + plist (3.5.0) + public_suffix (2.0.5) + representable (3.0.4) + declarative (< 0.1.0) + declarative-option (< 0.2.0) + uber (< 0.2.0) + retriable (3.1.2) + rouge (2.0.7) + rubyzip (1.2.3) + security (0.1.3) + signet (0.11.0) + addressable (~> 2.3) + faraday (~> 0.9) + jwt (>= 1.5, < 3.0) + multi_json (~> 1.10) + simctl (1.6.5) + CFPropertyList + naturally + slack-notifier (2.3.2) + terminal-notifier (2.0.0) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + tty-cursor (0.7.0) + tty-screen (0.7.0) + tty-spinner (0.9.1) + tty-cursor (~> 0.7) + uber (0.1.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.6) + unicode-display_width (1.6.0) + word_wrap (1.0.0) + xcodeproj (1.12.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.2.6) + xcpretty (0.3.0) + rouge (~> 2.0.7) + xcpretty-travis-formatter (1.0.0) + xcpretty (~> 0.2, >= 0.0.7) + +PLATFORMS + ruby + +DEPENDENCIES + fastlane + +BUNDLED WITH + 2.0.1 diff --git a/app/build.gradle b/app/build.gradle index abf4201..be9186c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -37,4 +37,7 @@ dependencies { compileOnly libraries.retrofit compileOnly libraries.room compileOnly libraries.paging + + // Need for proper renders in xml previews + compileOnly libraries.constraintLayout } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7e6e52f..d905a0d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,4 +1,5 @@ - @@ -13,5 +14,15 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme" - tools:ignore="GoogleAppIndexingWarning" /> + tools:ignore="GoogleAppIndexingWarning"> + + + + + + + + + + diff --git a/app/src/main/kotlin/com/melih/rocketscience/MainActivity.kt b/app/src/main/kotlin/com/melih/rocketscience/MainActivity.kt new file mode 100644 index 0000000..06b7c17 --- /dev/null +++ b/app/src/main/kotlin/com/melih/rocketscience/MainActivity.kt @@ -0,0 +1,31 @@ +package com.melih.rocketscience + +import android.os.Bundle +import androidx.databinding.DataBindingUtil +import androidx.navigation.NavController +import androidx.navigation.findNavController +import androidx.navigation.ui.NavigationUI +import com.melih.rocketscience.databinding.MainActivityBinding +import dagger.android.support.DaggerAppCompatActivity + +class MainActivity : DaggerAppCompatActivity() { + + private lateinit var binding: MainActivityBinding + private lateinit var navController: NavController + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = DataBindingUtil.setContentView(this, R.layout.activity_main) + + navController = findNavController(R.id.nav_host_fragment) + NavigationUI.setupWithNavController(binding.toolbar, navController) + } + + override fun onSupportNavigateUp(): Boolean { + if (!NavigationUI.navigateUp(navController, null)) { + onBackPressed() + } + + return true + } +} diff --git a/app/src/main/kotlin/com/melih/rocketscience/di/AppComponent.kt b/app/src/main/kotlin/com/melih/rocketscience/di/AppComponent.kt index ffa55c8..aa77983 100644 --- a/app/src/main/kotlin/com/melih/rocketscience/di/AppComponent.kt +++ b/app/src/main/kotlin/com/melih/rocketscience/di/AppComponent.kt @@ -1,8 +1,6 @@ package com.melih.rocketscience.di import com.melih.core.di.CoreComponent -import com.melih.detail.di.DetailFeatureModule -import com.melih.list.di.LaunchesFeatureModule import com.melih.rocketscience.App import dagger.Component import dagger.android.AndroidInjectionModule @@ -11,8 +9,7 @@ import dagger.android.AndroidInjector @AppScope @Component( modules = [AndroidInjectionModule::class, - LaunchesFeatureModule::class, - DetailFeatureModule::class], + AppModule::class], dependencies = [CoreComponent::class] ) diff --git a/app/src/main/kotlin/com/melih/rocketscience/di/AppModule.kt b/app/src/main/kotlin/com/melih/rocketscience/di/AppModule.kt new file mode 100644 index 0000000..2cc3b52 --- /dev/null +++ b/app/src/main/kotlin/com/melih/rocketscience/di/AppModule.kt @@ -0,0 +1,19 @@ +package com.melih.rocketscience.di + +import com.melih.detail.di.DetailContributor +import com.melih.list.di.LaunchesContributor +import com.melih.rocketscience.MainActivity +import dagger.Module +import dagger.android.ContributesAndroidInjector + +@Module +abstract class AppModule { + + + @ContributesAndroidInjector( + modules = [ + LaunchesContributor::class, + DetailContributor::class] + ) + abstract fun mainActivity(): MainActivity +} diff --git a/features/launches/src/main/res/layout/activity_launches.xml b/app/src/main/res/layout/activity_main.xml similarity index 53% rename from features/launches/src/main/res/layout/activity_launches.xml rename to app/src/main/res/layout/activity_main.xml index 4369e55..8950ebb 100644 --- a/features/launches/src/main/res/layout/activity_launches.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,7 +1,9 @@ - + - + - + android:layout_height="match_parent" + app:defaultNavHost="true" + app:navGraph="@navigation/nav_main" /> diff --git a/app/src/main/res/navigation/nav_main.xml b/app/src/main/res/navigation/nav_main.xml new file mode 100644 index 0000000..c390eb6 --- /dev/null +++ b/app/src/main/res/navigation/nav_main.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/build.gradle b/build.gradle index 1314a64..f1303a2 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.5.0-rc03' + classpath 'com.android.tools.build:gradle:3.5.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.dokka:dokka-android-gradle-plugin:0.9.18" classpath "de.mannodermaus.gradle.plugins:android-junit5:1.5.0.0" diff --git a/core/jacoco.exec b/core/jacoco.exec deleted file mode 100644 index 45a3fecf1b2c5f56d98a221e1182637b874b1f5a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 934 zcmZQPa6o`vfI-eTH77HpSl8DnK(D;mNUtO{S1&I|H^sm-#XL1Rg@J)FN00fq9E3j0 zB^Z2U{wL#_6&Vb=$@#hZxggE@MX3e(#hE4fMV0!Qc_pbuiOE2Av3^owajJfBX>xLE zaWNCKV1sI|14q#ZB?fIGb(SQSq%yM$=CUyf+cr2j5No4bVrEWh5lH7VM%xTt+aFB~ zI>gx+l$uzapU2E5C=%_s`SW8>0|q1FG`gqer50r-yA~DY7Xf|h!@lQ+?Vsj&;_WRi z%_#wD)c#lFaK>?l5rZ*t_6CE3NW~{VF(orEor#$dZQs=ZTFOL|Qm + - + diff --git a/core/src/main/kotlin/com/melih/core/actions/Actions.kt b/core/src/main/kotlin/com/melih/core/actions/Actions.kt index 8fbd473..ca813c9 100644 --- a/core/src/main/kotlin/com/melih/core/actions/Actions.kt +++ b/core/src/main/kotlin/com/melih/core/actions/Actions.kt @@ -1,16 +1,12 @@ package com.melih.core.actions -import android.content.Intent - -const val EXTRA_LAUNCH_ID = "extras:detail:launchid" +import android.net.Uri +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.NavHostFragment +import com.melih.core.R /** * Navigation actions for navigation between feature activities */ -object Actions { - - fun openDetailFor(id: Long) = - Intent("action.dashboard.open") - .putExtra(EXTRA_LAUNCH_ID, id) - -} +fun Fragment.openDetail(id: Long) = + NavHostFragment.findNavController(this).navigate(Uri.parse(getString(R.string.detail_uri, id))) diff --git a/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseActivity.kt b/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseActivity.kt deleted file mode 100644 index ba672c4..0000000 --- a/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseActivity.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.melih.core.base.lifecycle - -import android.os.Bundle -import androidx.annotation.IdRes -import androidx.annotation.LayoutRes -import androidx.databinding.DataBindingUtil -import androidx.databinding.ViewDataBinding -import androidx.navigation.fragment.NavHostFragment -import androidx.navigation.ui.NavigationUI -import dagger.android.support.DaggerAppCompatActivity - -const val NAV_HOST_FRAGMENT_TAG = "nav_host_fragment_tag" - -/** - * Base class of all Activity classes - */ -abstract class BaseActivity : DaggerAppCompatActivity() { - - protected lateinit var binding: T - protected lateinit var navHostFragment: NavHostFragment - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - binding = DataBindingUtil.setContentView(this, getLayoutId()) - binding.lifecycleOwner = this - - if (savedInstanceState == null) { - navHostFragment = createNavHostFragment() - - supportFragmentManager - .beginTransaction() - .add(addNavHostTo(), navHostFragment, NAV_HOST_FRAGMENT_TAG) - .commitNow() - } else { - navHostFragment = supportFragmentManager - .findFragmentByTag(NAV_HOST_FRAGMENT_TAG) as NavHostFragment - } - } - - override fun onSupportNavigateUp(): Boolean { - if (!NavigationUI.navigateUp(navHostFragment.navController, null)) { - onBackPressed() - } - - return true - } - - @LayoutRes - abstract fun getLayoutId(): Int - - abstract fun createNavHostFragment(): NavHostFragment - - @IdRes - abstract fun addNavHostTo(): Int -} diff --git a/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseFragment.kt b/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseFragment.kt index 4224e72..7f37c1c 100644 --- a/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseFragment.kt +++ b/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseFragment.kt @@ -8,9 +8,8 @@ import androidx.annotation.LayoutRes import androidx.databinding.DataBindingUtil import androidx.databinding.ViewDataBinding import androidx.fragment.app.Fragment -import androidx.navigation.NavController -import androidx.navigation.fragment.NavHostFragment import com.google.android.material.snackbar.Snackbar +import com.melih.core.R import com.melih.repository.interactors.base.Reason /** @@ -23,7 +22,6 @@ abstract class BaseFragment : Fragment() { // region Properties - protected lateinit var navController: NavController protected lateinit var binding: T // endregion @@ -34,7 +32,6 @@ abstract class BaseFragment : Fragment() { container: ViewGroup?, savedInstanceState: Bundle? ): View? { - navController = NavHostFragment.findNavController(this) binding = DataBindingUtil.inflate(inflater, getLayoutId(), container, false) binding.lifecycleOwner = this return binding.root @@ -45,7 +42,7 @@ abstract class BaseFragment : Fragment() { binding.root, resources.getString(reason.messageRes), Snackbar.LENGTH_INDEFINITE - ).setAction(com.melih.core.R.string.retry) { + ).setAction(R.string.retry) { block() }.show() } diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index 22788f1..9268d45 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -1,15 +1,12 @@ - - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor - incididunt ut labore et dolore - magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo - consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat - non proident, sunt in culpa qui officia - deserunt mollit anim id est laborum - + + +]> - Retry + + Retry - - action.detail.open + ¶mLaunchId; + &deeplinkDetailPath;/{¶mLaunchId;} + &deeplinkDetailPath;/%1d diff --git a/fastlane/Fastfile b/fastlane/Fastfile index d30e454..ce5b3e9 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -30,9 +30,29 @@ platform :android do run_detekt() end - desc "Runs all tests in all modules" - lane :test_all do - run_all_tests() + desc "Runs tests in app module" + lane :test_app do + run_app_tests() + end + + desc "Runs tests in core module" + lane :test_core do + run_core_tests() + end + + desc "Runs tests in launches module" + lane :test_launches do + run_launches_tests() + end + + desc "Runs tests in detail module" + lane :test_detail do + run_detail_tests() + end + + desc "Runs tests in repository module" + lane :test_repository do + run_repository_tests() end # ================ Gradle tasks ================ @@ -45,7 +65,23 @@ platform :android do gradle(task: "removeReports") end - def run_all_tests - gradle(task: "clean test --continue") + def run_app_tests + gradle(task: "app:test --continue") + end + + def run_core_tests + gradle(task: "core:test --continue") + end + + def run_launches_tests + gradle(task: "features:launches:test --continue") + end + + def run_detail_tests + gradle(task: "features:detail:test --continue") + end + + def run_repository_tests + gradle(task: "repository:test --continue") end end diff --git a/features/detail/build.gradle b/features/detail/build.gradle index 0b93486..cea8317 100644 --- a/features/detail/build.gradle +++ b/features/detail/build.gradle @@ -1,7 +1,6 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' -apply plugin: "androidx.navigation.safeargs" apply from: "$rootProject.projectDir/scripts/feature_module.gradle" diff --git a/features/detail/jacoco.exec b/features/detail/jacoco.exec deleted file mode 100644 index b1922d8c8e4bfe1496e0078b4c30eb6f2ffe588c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2651 zcmZQPa6o`vfI-eTH77HpSl8DnK(D;mNUtO{S1&I|*U;EB&A`GinSp^ZhpR)&8$uiU z3GO<}?o#qDuYDypq(S#AG15 zSl>Oh#3!*dFFC^{wIne!r&uK*u_!UOSg@IukrA(l&5X=UObrYS!5-a{t_-0Irw&^W zC!3iVr0F0?49 zmbR_TIk`$Bc^hTr9sWYgBE>8z#n>2JEy?k20mrZ$@41VD76(I_0x#9LQD!*>g{|O< z>?Vh)VH_Vw4RQh=PEIyAO-=)QSdaBh4=9H9SpTmdwjNHkFt@NUuuKGdlr1F)LYEKa GBn<#og|7wx diff --git a/features/detail/src/main/AndroidManifest.xml b/features/detail/src/main/AndroidManifest.xml index e7838c8..748078e 100644 --- a/features/detail/src/main/AndroidManifest.xml +++ b/features/detail/src/main/AndroidManifest.xml @@ -1,14 +1 @@ - - - - - - - - - - - + diff --git a/features/detail/src/main/kotlin/com/melih/detail/di/DetailFeatureModule.kt b/features/detail/src/main/kotlin/com/melih/detail/di/DetailFeatureModule.kt deleted file mode 100644 index 86b41bf..0000000 --- a/features/detail/src/main/kotlin/com/melih/detail/di/DetailFeatureModule.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.melih.detail.di - -import com.melih.detail.ui.DetailActivity -import com.melih.list.di.scopes.DetailScope -import dagger.Module -import dagger.android.ContributesAndroidInjector - -/** - * Contributes fragments & view models in this module - */ -@Module -abstract class DetailFeatureModule { - - // region Contributes - - @ContributesAndroidInjector( - modules = [ - DetailContributor::class - ] - ) - @DetailScope - abstract fun detailActivity(): DetailActivity - // endregion -} diff --git a/features/detail/src/main/kotlin/com/melih/detail/di/modules/DetailFragmentModule.kt b/features/detail/src/main/kotlin/com/melih/detail/di/modules/DetailFragmentModule.kt index 314e70e..f7cfc4d 100644 --- a/features/detail/src/main/kotlin/com/melih/detail/di/modules/DetailFragmentModule.kt +++ b/features/detail/src/main/kotlin/com/melih/detail/di/modules/DetailFragmentModule.kt @@ -32,7 +32,7 @@ abstract class DetailFragmentModule { @Provides @JvmStatic fun provideGetLaunchDetailParams(fragment: DetailFragment): GetLaunchDetails.Params { - val args: DetailFragmentArgs by fragment.navArgs() + val args by fragment.navArgs() return GetLaunchDetails.Params(args.launchId) } } diff --git a/features/detail/src/main/kotlin/com/melih/detail/ui/DetailActivity.kt b/features/detail/src/main/kotlin/com/melih/detail/ui/DetailActivity.kt deleted file mode 100644 index 0e7fbae..0000000 --- a/features/detail/src/main/kotlin/com/melih/detail/ui/DetailActivity.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.melih.detail.ui - -import android.os.Bundle -import androidx.navigation.fragment.NavHostFragment -import com.melih.core.actions.EXTRA_LAUNCH_ID -import com.melih.core.base.lifecycle.BaseActivity -import com.melih.detail.R -import com.melih.detail.databinding.DetailActivityBinding - -const val INVALID_LAUNCH_ID = -1L - -class DetailActivity : BaseActivity() { - - // region Functions - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setSupportActionBar(binding.toolbar) - supportActionBar?.setDisplayHomeAsUpEnabled(true) - supportActionBar?.setDisplayShowHomeEnabled(true) - } - - override fun getLayoutId(): Int = R.layout.activity_detail - - override fun createNavHostFragment() = - NavHostFragment.create( - R.navigation.nav_detail, - DetailFragmentArgs.Builder() - .setLaunchId(intent?.extras?.getLong(EXTRA_LAUNCH_ID) ?: INVALID_LAUNCH_ID) - .build() - .toBundle() - ) - - override fun addNavHostTo(): Int = R.id.container - // endregion -} diff --git a/features/detail/src/main/res/layout/activity_detail.xml b/features/detail/src/main/res/layout/activity_detail.xml deleted file mode 100644 index 89c60f2..0000000 --- a/features/detail/src/main/res/layout/activity_detail.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - diff --git a/features/detail/src/main/res/navigation/nav_detail.xml b/features/detail/src/main/res/navigation/nav_detail.xml index 2f2f844..cbab98f 100644 --- a/features/detail/src/main/res/navigation/nav_detail.xml +++ b/features/detail/src/main/res/navigation/nav_detail.xml @@ -1,18 +1,22 @@ - + - - - + + + + diff --git a/features/detail/src/main/res/values/strings.xml b/features/detail/src/main/res/values/strings.xml index c9dc1ae..f4f6713 100644 --- a/features/detail/src/main/res/values/strings.xml +++ b/features/detail/src/main/res/values/strings.xml @@ -1,3 +1,4 @@ + - Image of the rocket + Image of the rocket diff --git a/features/launches/jacoco.exec b/features/launches/jacoco.exec deleted file mode 100644 index 65d24583fde61e2f3fb3f8bbafd36c6c14f60818..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2302 zcmZQPa6o`vfI-eTH77HpSl8DnK(D;mNUtO{S1&I|*U-}3FxfQCgn@xEhpUb8DTHQ^ z5ZrZ^$$4+tzXJ@~$@#hZxggE@$@xX8`bmk!srqG^spYx(DXBU7PC#xLkmn2H2{8N- zWa`ZMTX=irVFq0iG^!YKGcz;db!nP;Qc{vZS_;^O9hN!}+KPi*m+}Zcj!MwlQnd0m zgE7LTMX3e(#hE4fMV0!Qc_pbuiOE2AvA%n1iBDo_UUEiiu}VNFtIR%c=Fg}2z?@1aIKlmYfjy=_1QlNV;1O9V}n$43$Q1z|NaG`|6UlDo;6ELG&V>vFavw?21fvd=295i z$ygS9QEHZAVwP-Z1oq>thg=Z)F~hKo*yL2RH1kALQ-~*j=0ND*+Cw{HanzB?iHWHe zX{N~_Ps;I$+CpdvbwS=aAus$q7R6Ig%osbwx)>)NNFce0Be$9*RP&@*8l;+~7#To3 lsj(13YrUhOsuK+6^x#lncekb>oK|EvIZO@X_&{nn0RXUjAT|I1 diff --git a/features/launches/src/main/AndroidManifest.xml b/features/launches/src/main/AndroidManifest.xml index 2323aa1..c857a13 100644 --- a/features/launches/src/main/AndroidManifest.xml +++ b/features/launches/src/main/AndroidManifest.xml @@ -1,14 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/features/launches/src/main/kotlin/com/melih/list/di/LaunchesFeatureModule.kt b/features/launches/src/main/kotlin/com/melih/list/di/LaunchesFeatureModule.kt deleted file mode 100644 index 68ae1be..0000000 --- a/features/launches/src/main/kotlin/com/melih/list/di/LaunchesFeatureModule.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.melih.list.di - -import com.melih.list.di.scopes.LaunchesScope -import com.melih.list.ui.LaunchesActivity -import dagger.Module -import dagger.android.ContributesAndroidInjector - -/** - * Contributes fragments & view models in this module - */ -@Module -abstract class LaunchesFeatureModule { - - // region Contributes - - @ContributesAndroidInjector( - modules = [ - LaunchesContributor::class - ] - ) - @LaunchesScope - abstract fun launchesActivity(): LaunchesActivity - // endregion -} diff --git a/features/launches/src/main/kotlin/com/melih/list/ui/LaunchesActivity.kt b/features/launches/src/main/kotlin/com/melih/list/ui/LaunchesActivity.kt deleted file mode 100644 index 529b4ae..0000000 --- a/features/launches/src/main/kotlin/com/melih/list/ui/LaunchesActivity.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.melih.list.ui - -import android.os.Bundle -import androidx.navigation.fragment.NavHostFragment -import com.melih.core.base.lifecycle.BaseActivity -import com.melih.list.R -import com.melih.list.databinding.LaunchesActivityBinding - -class LaunchesActivity : BaseActivity() { - - // region Functions - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setSupportActionBar(binding.toolbar) - } - - override fun getLayoutId(): Int = R.layout.activity_launches - - override fun createNavHostFragment() = - NavHostFragment.create(R.navigation.nav_launches) - - override fun addNavHostTo(): Int = R.id.container - // endregion -} diff --git a/features/launches/src/main/kotlin/com/melih/list/ui/LaunchesFragment.kt b/features/launches/src/main/kotlin/com/melih/list/ui/LaunchesFragment.kt index 816f40c..66a7ec6 100644 --- a/features/launches/src/main/kotlin/com/melih/list/ui/LaunchesFragment.kt +++ b/features/launches/src/main/kotlin/com/melih/list/ui/LaunchesFragment.kt @@ -3,7 +3,7 @@ package com.melih.list.ui import android.os.Bundle import android.view.View import androidx.swiperefreshlayout.widget.SwipeRefreshLayout -import com.melih.core.actions.Actions +import com.melih.core.actions.openDetail import com.melih.core.base.lifecycle.BaseDaggerFragment import com.melih.core.extensions.createFor import com.melih.core.extensions.observe @@ -36,17 +36,6 @@ class LaunchesFragment : BaseDaggerFragment(), SwipeRefreshLayout.O observeDataChanges() } - //override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - // inflater.inflate(R.menu.menu_rocket_list, menu) - // - // with(menu.findItem(R.id.search)) { - // onExpandOrCollapse(::onSearchExpand, ::onSearchCollapse) - // setSearchQueryListener(actionView as SearchView) - // } - // - // super.onCreateOptionsMenu(menu, inflater) - //} - private fun observeDataChanges() { // Observing state to show loading @@ -64,30 +53,12 @@ class LaunchesFragment : BaseDaggerFragment(), SwipeRefreshLayout.O observe(viewModel.pagedList) { launchesAdapter.submitList(it) } - - //observe(viewModel.filteredItems) { - // launchesAdapter.submitList(it) - //} } private fun onItemSelected(item: LaunchEntity) { - startActivity(Actions.openDetailFor(item.id)) + openDetail(item.id) } - //private fun onSearchExpand() { - // binding.swipeRefreshLayout.isEnabled = false - //} - - //private fun onSearchCollapse() { - // binding.swipeRefreshLayout.isEnabled = true - //} - - //private fun setSearchQueryListener(searchView: SearchView) { - // searchView.setOnQueryChangedListener { - // viewModel.filterItemListBy(it) - // } - //} - override fun onRefresh() { viewModel.refresh() } diff --git a/features/launches/src/main/res/navigation/nav_launches.xml b/features/launches/src/main/res/navigation/nav_launches.xml index 03ee663..c36b835 100644 --- a/features/launches/src/main/res/navigation/nav_launches.xml +++ b/features/launches/src/main/res/navigation/nav_launches.xml @@ -1,5 +1,6 @@ - Date: Thu, 22 Aug 2019 01:07:55 +0200 Subject: [PATCH 02/20] Update README.md (#33) Fixed CircleCI badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e700c93..dd65b02 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Rocket Science -[![codebeat badge](https://codebeat.co/badges/542fb08a-b3cc-4ff8-b1bb-35a66932f12f)](https://codebeat.co/projects/github-com-melihaksoy-rocketscience-master) [![codecov](https://codecov.io/gh/melihaksoy/RocketScience/branch/master/graph/badge.svg?token=pXPKpV5dz6)](https://codecov.io/gh/melihaksoy/RocketScience) [![CircleCI](https://circleci.com/gh/melihaksoy/RocketScience/tree/master.svg?style=svg&circle-token=705e399a0116be0a5bb10bddc72fc7ef19b568e3)](https://circleci.com/gh/melihaksoy/RocketScience/tree/master) [![Awesome Kotlin Badge](https://kotlin.link/awesome-kotlin.svg)](https://github.com/KotlinBy/awesome-kotlin) +[![codebeat badge](https://codebeat.co/badges/542fb08a-b3cc-4ff8-b1bb-35a66932f12f)](https://codebeat.co/projects/github-com-melihaksoy-rocketscience-master) [![codecov](https://codecov.io/gh/melihaksoy/RocketScience/branch/master/graph/badge.svg?token=pXPKpV5dz6)](https://codecov.io/gh/melihaksoy/RocketScience) [![CircleCI](https://circleci.com/gh/melihaksoy/Android-Kotlin-Modulerized-CleanArchitecture/tree/master.svg?style=svg)](https://circleci.com/gh/melihaksoy/Android-Kotlin-Modulerized-CleanArchitecture/tree/master) [![Awesome Kotlin Badge](https://kotlin.link/awesome-kotlin.svg)](https://github.com/KotlinBy/awesome-kotlin) RocketScience is a prototype application tries to serve an example for modularization & clean architecture in Android. From 934a50a6c753fa4b5bd2a9fb86cedbf881282fa8 Mon Sep 17 00:00:00 2001 From: Melih Aksoy Date: Wed, 28 Aug 2019 11:45:42 +0200 Subject: [PATCH 03/20] Update android.yml --- .github/workflows/android.yml | 61 +++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 .github/workflows/android.yml diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 0000000..7dc66de --- /dev/null +++ b/.github/workflows/android.yml @@ -0,0 +1,61 @@ +name: Android CI + +on: [push] + +jobs: + setup: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - name: Setup + run: | + gem install bundler + bundle install + mkdir ~/code/reports + + code_quality: + + runs-on: ubuntu-latest + needs: setup + + steps: + - name: Code Quality ( Detekt ) + run: fastlane detekt + + test: + + runs-on: ubuntu-latest + needs: setup + + steps: + - name: Test App + run: | + fastlane test_app + ./gradlew app:jacocoTestReport + bash <(curl -s https://codecov.io/bash) + + - name: Test Core + run: | + fastlane test_core + ./gradlew core:jacocoTestReport + bash <(curl -s https://codecov.io/bash) + + - name: Test Launches + run: | + fastlane test_launches + ./gradlew launches:jacocoTestReport + bash <(curl -s https://codecov.io/bash) + + - name: Test Detail + run: | + fastlane test_detail + ./gradlew detail:jacocoTestReport + bash <(curl -s https://codecov.io/bash) + + - name: Test Repository + run: | + fastlane test_repository + ./gradlew repository:jacocoTestReport + bash <(curl -s https://codecov.io/bash) From aafd76f4a1e5002c9b84fb0f4abae77b1c7b0833 Mon Sep 17 00:00:00 2001 From: Melih Aksoy Date: Wed, 28 Aug 2019 12:50:26 +0200 Subject: [PATCH 04/20] Update android.yml Updated github workflow --- .github/workflows/android.yml | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 7dc66de..1bbca1e 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -3,33 +3,34 @@ name: Android CI on: [push] jobs: - setup: - - runs-on: ubuntu-latest - + + code_quality: + + runs-on: macOS-latest + steps: - uses: actions/checkout@v1 + - name: Setup run: | gem install bundler bundle install - mkdir ~/code/reports - - code_quality: - - runs-on: ubuntu-latest - needs: setup - - steps: + - name: Code Quality ( Detekt ) run: fastlane detekt test: - runs-on: ubuntu-latest - needs: setup + runs-on: macOS-latest steps: + - uses: actions/checkout@v1 + + - name: Setup + run: | + gem install bundler + bundle install + - name: Test App run: | fastlane test_app @@ -45,13 +46,13 @@ jobs: - name: Test Launches run: | fastlane test_launches - ./gradlew launches:jacocoTestReport + ./gradlew features:launches:jacocoTestReport bash <(curl -s https://codecov.io/bash) - name: Test Detail run: | fastlane test_detail - ./gradlew detail:jacocoTestReport + ./gradlew features:detail:jacocoTestReport bash <(curl -s https://codecov.io/bash) - name: Test Repository From 9c6d71ffc733d61d9e810d5e1e9fdcf267cb4667 Mon Sep 17 00:00:00 2001 From: Melih Aksoy Date: Thu, 29 Aug 2019 15:22:45 +0200 Subject: [PATCH 05/20] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dd65b02..e978d4a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # Rocket Science -[![codebeat badge](https://codebeat.co/badges/542fb08a-b3cc-4ff8-b1bb-35a66932f12f)](https://codebeat.co/projects/github-com-melihaksoy-rocketscience-master) [![codecov](https://codecov.io/gh/melihaksoy/RocketScience/branch/master/graph/badge.svg?token=pXPKpV5dz6)](https://codecov.io/gh/melihaksoy/RocketScience) [![CircleCI](https://circleci.com/gh/melihaksoy/Android-Kotlin-Modulerized-CleanArchitecture/tree/master.svg?style=svg)](https://circleci.com/gh/melihaksoy/Android-Kotlin-Modulerized-CleanArchitecture/tree/master) [![Awesome Kotlin Badge](https://kotlin.link/awesome-kotlin.svg)](https://github.com/KotlinBy/awesome-kotlin) +[![Actions](https://github.com/melihaksoy/Android-Kotlin-Modulerized-CleanArchitecture/workflows/Android%20CI/badge.svg)](https://github.com/melihaksoy/Android-Kotlin-Modulerized-CleanArchitecture/actions) [![CircleCI](https://circleci.com/gh/melihaksoy/Android-Kotlin-Modulerized-CleanArchitecture/tree/master.svg?style=svg)](https://circleci.com/gh/melihaksoy/Android-Kotlin-Modulerized-CleanArchitecture/tree/master) [![codebeat badge](https://codebeat.co/badges/542fb08a-b3cc-4ff8-b1bb-35a66932f12f)](https://codebeat.co/projects/github-com-melihaksoy-rocketscience-master) [![codecov](https://codecov.io/gh/melihaksoy/RocketScience/branch/master/graph/badge.svg?token=pXPKpV5dz6)](https://codecov.io/gh/melihaksoy/RocketScience) [![Awesome Kotlin Badge](https://kotlin.link/awesome-kotlin.svg)](https://github.com/KotlinBy/awesome-kotlin) + +https://github.com/melihaksoy/Android-Kotlin-Modulerized-CleanArchitecture/workflows/Android%20CI/badge.svg RocketScience is a prototype application tries to serve an example for modularization & clean architecture in Android. From 5a10edce17ec3936a59c64a5be5dc5acaa897b3e Mon Sep 17 00:00:00 2001 From: Melih Aksoy Date: Thu, 29 Aug 2019 15:22:59 +0200 Subject: [PATCH 06/20] Update README.md --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index e978d4a..1e7210b 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ [![Actions](https://github.com/melihaksoy/Android-Kotlin-Modulerized-CleanArchitecture/workflows/Android%20CI/badge.svg)](https://github.com/melihaksoy/Android-Kotlin-Modulerized-CleanArchitecture/actions) [![CircleCI](https://circleci.com/gh/melihaksoy/Android-Kotlin-Modulerized-CleanArchitecture/tree/master.svg?style=svg)](https://circleci.com/gh/melihaksoy/Android-Kotlin-Modulerized-CleanArchitecture/tree/master) [![codebeat badge](https://codebeat.co/badges/542fb08a-b3cc-4ff8-b1bb-35a66932f12f)](https://codebeat.co/projects/github-com-melihaksoy-rocketscience-master) [![codecov](https://codecov.io/gh/melihaksoy/RocketScience/branch/master/graph/badge.svg?token=pXPKpV5dz6)](https://codecov.io/gh/melihaksoy/RocketScience) [![Awesome Kotlin Badge](https://kotlin.link/awesome-kotlin.svg)](https://github.com/KotlinBy/awesome-kotlin) -https://github.com/melihaksoy/Android-Kotlin-Modulerized-CleanArchitecture/workflows/Android%20CI/badge.svg - RocketScience is a prototype application tries to serve an example for modularization & clean architecture in Android. While there are many blogs about good practices, it's hard to come by a complete example that merges these different popular topics & approaches. From 99a2abf05071690aea29e5d2926b1419809a39be Mon Sep 17 00:00:00 2001 From: Melih Aksoy Date: Fri, 13 Sep 2019 14:09:28 +0200 Subject: [PATCH 07/20] Feature/styling (#36) * Working with material styles * Replaced deprecated ViewModelProviders.of and applied base styles with material components * Various code style fixes and styles.xml improvements --- README.md | 6 +- app/build.gradle | 3 +- .../com/melih/rocketscience/MainActivity.kt | 8 +- .../melih/rocketscience/di/AppComponent.kt | 6 +- app/src/main/res/layout/activity_main.xml | 43 +++--- build.gradle | 4 +- core/build.gradle | 2 +- .../core/base/lifecycle/BaseDaggerFragment.kt | 10 +- .../melih/core/base/lifecycle/BaseFragment.kt | 17 ++- .../core/base/paging/BasePagingDataSource.kt | 12 +- .../core/base/paging/BasePagingFactory.kt | 12 +- .../base/recycler/BasePagingListAdapter.kt | 9 ++ .../base/viewmodel/BasePagingViewModel.kt | 18 +-- .../core/base/viewmodel/BaseViewModel.kt | 12 +- .../melih/core/extensions/DiffUtilHelper.kt | 27 ++++ .../core/extensions/LifecycleExtensions.kt | 13 -- .../core/extensions/RecyclerExtensions.kt | 14 -- core/src/main/res/values/colors.xml | 12 +- core/src/main/res/values/dimens.xml | 2 + core/src/main/res/values/styles.xml | 69 +++++----- .../com/melih/detail/di/DetailContributor.kt | 4 +- .../detail/di/modules/DetailFragmentModule.kt | 4 +- .../com/melih/detail/ui/DetailFragment.kt | 13 +- .../com/melih/detail/ui/DetailViewModel.kt | 16 +-- .../src/main/res/layout/fragment_detail.xml | 126 +++++++++--------- features/launches/build.gradle | 1 + .../com/melih/list/di/LaunchesContributor.kt | 4 +- .../list/di/modules/LaunchesFragmentModule.kt | 4 +- .../com/melih/list/ui/LaunchesFragment.kt | 37 +++-- .../melih/list/ui/adapters/LaunchesAdapter.kt | 12 +- .../list/ui/paging/LaunchesPagingSource.kt | 3 + .../ui/paging/LaunchesPagingSourceFactory.kt | 3 + .../com/melih/list/ui/vm/LaunchesViewModel.kt | 26 +--- .../src/main/res/layout/fragment_launches.xml | 57 ++++---- .../src/main/res/layout/row_launch.xml | 126 ++++++++---------- gradle/wrapper/gradle-wrapper.properties | 4 +- .../kotlin/com/melih/repository/Constants.kt | 5 +- .../kotlin/com/melih/repository/Repository.kt | 5 +- .../interactors/GetLaunchDetails.kt | 6 + .../repository/interactors/GetLaunches.kt | 6 + .../interactors/base/BaseInteractor.kt | 8 +- .../repository/interactors/base/Reason.kt | 3 + .../repository/interactors/base/Result.kt | 8 +- .../com/melih/repository/network/Api.kt | 3 + .../com/melih/repository/network/ApiImpl.kt | 7 +- .../persistence/LaunchesDatabase.kt | 6 + .../persistence/converters/BaseConverter.kt | 14 +- .../converters/BaseListConverter.kt | 14 +- .../converters/LocationConverter.kt | 1 + .../persistence/converters/RocketConverter.kt | 3 +- .../repository/persistence/dao/LaunchesDao.kt | 8 +- .../melih/repository/sources/NetworkSource.kt | 16 ++- .../repository/sources/PersistenceSource.kt | 5 +- scripts/default_android_config.gradle | 1 + scripts/dependencies.gradle | 109 +++++++-------- scripts/flavors.gradle | 18 --- scripts/module.gradle | 1 - scripts/sources.gradle | 17 +++ 58 files changed, 531 insertions(+), 472 deletions(-) create mode 100644 core/src/main/kotlin/com/melih/core/extensions/DiffUtilHelper.kt delete mode 100644 core/src/main/kotlin/com/melih/core/extensions/RecyclerExtensions.kt delete mode 100644 scripts/flavors.gradle diff --git a/README.md b/README.md index 1e7210b..f4bd56d 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ -# Rocket Science +# Android-Kotlin-Modulerized-CleanArchitecture [![Actions](https://github.com/melihaksoy/Android-Kotlin-Modulerized-CleanArchitecture/workflows/Android%20CI/badge.svg)](https://github.com/melihaksoy/Android-Kotlin-Modulerized-CleanArchitecture/actions) [![CircleCI](https://circleci.com/gh/melihaksoy/Android-Kotlin-Modulerized-CleanArchitecture/tree/master.svg?style=svg)](https://circleci.com/gh/melihaksoy/Android-Kotlin-Modulerized-CleanArchitecture/tree/master) [![codebeat badge](https://codebeat.co/badges/542fb08a-b3cc-4ff8-b1bb-35a66932f12f)](https://codebeat.co/projects/github-com-melihaksoy-rocketscience-master) [![codecov](https://codecov.io/gh/melihaksoy/RocketScience/branch/master/graph/badge.svg?token=pXPKpV5dz6)](https://codecov.io/gh/melihaksoy/RocketScience) [![Awesome Kotlin Badge](https://kotlin.link/awesome-kotlin.svg)](https://github.com/KotlinBy/awesome-kotlin) -RocketScience is a prototype application tries to serve an example for modularization & clean architecture in Android. +This is a prototype application tries to serve an example for modularization & clean architecture in Android. While there are many blogs about good practices, it's hard to come by a complete example that merges these different popular topics & approaches. -RocketScience takes popular approaches and libraries to create an example on how to actually bind these components with each other. +This project takes popular approaches and libraries to create an example on how to actually bind these components with each other. I'll soon be writing small series about roadmap, challanges, alternatives and try - fails I've encountered during development in [Medium](https://medium.com/@aksoymelihcan). diff --git a/app/build.gradle b/app/build.gradle index be9186c..eac4a55 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,7 +4,6 @@ apply plugin: 'kotlin-kapt' apply from: "$rootProject.projectDir/scripts/default_android_config.gradle" apply from: "$rootProject.projectDir/scripts/sources.gradle" -apply from: "$rootProject.projectDir/scripts/flavors.gradle" android { defaultConfig { @@ -26,7 +25,6 @@ dependencies { implementation project(':features:launches') implementation project(':features:detail') - implementation libraries.coroutines implementation libraries.navigation debugImplementation libraries.leakCanary @@ -37,6 +35,7 @@ dependencies { compileOnly libraries.retrofit compileOnly libraries.room compileOnly libraries.paging + compileOnly libraries.swipeRefreshLayout // Need for proper renders in xml previews compileOnly libraries.constraintLayout diff --git a/app/src/main/kotlin/com/melih/rocketscience/MainActivity.kt b/app/src/main/kotlin/com/melih/rocketscience/MainActivity.kt index 06b7c17..696b4dd 100644 --- a/app/src/main/kotlin/com/melih/rocketscience/MainActivity.kt +++ b/app/src/main/kotlin/com/melih/rocketscience/MainActivity.kt @@ -1,24 +1,22 @@ package com.melih.rocketscience import android.os.Bundle -import androidx.databinding.DataBindingUtil +import androidx.appcompat.widget.Toolbar import androidx.navigation.NavController import androidx.navigation.findNavController import androidx.navigation.ui.NavigationUI -import com.melih.rocketscience.databinding.MainActivityBinding import dagger.android.support.DaggerAppCompatActivity class MainActivity : DaggerAppCompatActivity() { - private lateinit var binding: MainActivityBinding private lateinit var navController: NavController override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - binding = DataBindingUtil.setContentView(this, R.layout.activity_main) + setContentView(R.layout.activity_main) navController = findNavController(R.id.nav_host_fragment) - NavigationUI.setupWithNavController(binding.toolbar, navController) + NavigationUI.setupWithNavController(findViewById(R.id.toolbar), navController) } override fun onSupportNavigateUp(): Boolean { diff --git a/app/src/main/kotlin/com/melih/rocketscience/di/AppComponent.kt b/app/src/main/kotlin/com/melih/rocketscience/di/AppComponent.kt index aa77983..ebbc8a3 100644 --- a/app/src/main/kotlin/com/melih/rocketscience/di/AppComponent.kt +++ b/app/src/main/kotlin/com/melih/rocketscience/di/AppComponent.kt @@ -8,8 +8,10 @@ import dagger.android.AndroidInjector @AppScope @Component( - modules = [AndroidInjectionModule::class, - AppModule::class], + modules = [ + AndroidInjectionModule::class, + AppModule::class + ], dependencies = [CoreComponent::class] ) diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 8950ebb..51075f4 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,27 +1,22 @@ - + - + - - - - - - - + + diff --git a/build.gradle b/build.gradle index f1303a2..232dd0a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.41' + ext.kotlin_version = '1.3.50' ext.nav_version = '2.2.0-alpha01' repositories { @@ -12,7 +12,7 @@ buildscript { classpath 'com.android.tools.build:gradle:3.5.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.dokka:dokka-android-gradle-plugin:0.9.18" - classpath "de.mannodermaus.gradle.plugins:android-junit5:1.5.0.0" + classpath "de.mannodermaus.gradle.plugins:android-junit5:1.5.1.0" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/core/build.gradle b/core/build.gradle index adb92c2..96de2b7 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -4,7 +4,6 @@ apply plugin: 'kotlin-kapt' apply from: "$rootProject.projectDir/scripts/default_android_config.gradle" apply from: "$rootProject.projectDir/scripts/sources.gradle" -apply from: "$rootProject.projectDir/scripts/flavors.gradle" android { dataBinding { @@ -23,6 +22,7 @@ dependencies { implementation libraries.liveDataKTX implementation libraries.navigation implementation libraries.picasso + implementation libraries.material testImplementation testLibraries.jUnitApi testImplementation testLibraries.mockk diff --git a/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseDaggerFragment.kt b/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseDaggerFragment.kt index 0ca715f..dab5a6f 100644 --- a/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseDaggerFragment.kt +++ b/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseDaggerFragment.kt @@ -14,7 +14,7 @@ import javax.inject.Inject * Parent of fragments which has injections. Aim is to seperate [BaseFragment] functionality for fragments which * won't need any injection. * - * Note that fragments that extends from [BaseDaggerFragment] should contribute android injector. + * Note that fragments that extends from [BaseDaggerFragment] should contribute their injector. * * This class provides [viewModelFactory] which serves as factory for view models * in the project. It's injected by map of view models that this app is serving. Check [ViewModelFactory] @@ -22,16 +22,16 @@ import javax.inject.Inject */ abstract class BaseDaggerFragment : BaseFragment(), HasAndroidInjector { - // region Properties + //region Properties @get:Inject internal var androidInjector: DispatchingAndroidInjector? = null @Inject lateinit var viewModelFactory: ViewModelFactory - // endregion + //endregion - // region Functions + //region Functions override fun onAttach(context: Context) { AndroidSupportInjection.inject(this) @@ -39,5 +39,5 @@ abstract class BaseDaggerFragment : BaseFragment(), HasA } override fun androidInjector(): AndroidInjector? = androidInjector - // endregion + //endregion } diff --git a/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseFragment.kt b/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseFragment.kt index 7f37c1c..53e1d0a 100644 --- a/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseFragment.kt +++ b/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseFragment.kt @@ -20,12 +20,18 @@ import com.melih.repository.interactors.base.Reason */ abstract class BaseFragment : Fragment() { - // region Properties + //region Abstractions + + @LayoutRes + abstract fun getLayoutId(): Int + //endregion + + //region Properties protected lateinit var binding: T - // endregion + //endregion - // region Functions + //region Functions override fun onCreateView( inflater: LayoutInflater, @@ -46,8 +52,5 @@ abstract class BaseFragment : Fragment() { block() }.show() } - - @LayoutRes - abstract fun getLayoutId(): Int - // endregion + //endregion } diff --git a/core/src/main/kotlin/com/melih/core/base/paging/BasePagingDataSource.kt b/core/src/main/kotlin/com/melih/core/base/paging/BasePagingDataSource.kt index 47b6126..ff1ff69 100644 --- a/core/src/main/kotlin/com/melih/core/base/paging/BasePagingDataSource.kt +++ b/core/src/main/kotlin/com/melih/core/base/paging/BasePagingDataSource.kt @@ -37,12 +37,12 @@ const val INITIAL_PAGE = 0 @UseExperimental(ExperimentalCoroutinesApi::class) abstract class BasePagingDataSource : PageKeyedDataSource() { - // region Abstractions + //region Abstractions abstract fun loadDataForPage(page: Int): Flow>> // Load next page(s) - // endregion + //endregion - // region Properties + //region Properties private val _stateData = MutableLiveData() private val _reasonData = MutableLiveData() @@ -59,9 +59,9 @@ abstract class BasePagingDataSource : PageKeyedDataSource() { */ val reasonData: LiveData get() = _reasonData - // endregion + //endregion - // region Functions + //region Functions override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback) { // Looping through channel as we'll receive any state, error or data here @@ -136,5 +136,5 @@ abstract class BasePagingDataSource : PageKeyedDataSource() { coroutineScope.cancel() super.invalidate() } - // endregion + //endregion } diff --git a/core/src/main/kotlin/com/melih/core/base/paging/BasePagingFactory.kt b/core/src/main/kotlin/com/melih/core/base/paging/BasePagingFactory.kt index e948600..1a51f1e 100644 --- a/core/src/main/kotlin/com/melih/core/base/paging/BasePagingFactory.kt +++ b/core/src/main/kotlin/com/melih/core/base/paging/BasePagingFactory.kt @@ -16,20 +16,20 @@ import androidx.paging.DataSource */ abstract class BasePagingFactory : DataSource.Factory() { - // region Abstractions + //region Abstractions abstract fun createSource(): BasePagingDataSource - // endregion + //endregion - // region Properties + //region Properties private val _currentSource = MutableLiveData>() val currentSource: LiveData> get() = _currentSource - // endregion + //endregion - // region Functions + //region Functions override fun create(): DataSource = createSource().apply { _currentSource.postValue(this) } @@ -38,5 +38,5 @@ abstract class BasePagingFactory : DataSource.Factory() { * by calling [BasePagingDataSource.invalidate] */ fun invalidateDataSource() = currentSource.value?.invalidate() - // endregion + //endregion } diff --git a/core/src/main/kotlin/com/melih/core/base/recycler/BasePagingListAdapter.kt b/core/src/main/kotlin/com/melih/core/base/recycler/BasePagingListAdapter.kt index cfe44bc..3c9cdb4 100644 --- a/core/src/main/kotlin/com/melih/core/base/recycler/BasePagingListAdapter.kt +++ b/core/src/main/kotlin/com/melih/core/base/recycler/BasePagingListAdapter.kt @@ -15,6 +15,8 @@ abstract class BasePagingListAdapter( private val clickListener: (T) -> Unit ) : PagedListAdapter>(callback) { + //region Abstractions + /** * This method will be called to create view holder to obfuscate layout inflation creation / process * @@ -27,6 +29,9 @@ abstract class BasePagingListAdapter( parent: ViewGroup, viewType: Int ): BaseViewHolder + //endregion + + //region Functions /** * [createViewHolder] will provide holders, no need to override this @@ -51,6 +56,7 @@ abstract class BasePagingListAdapter( holder.bind(item) } } + //endregion } /** @@ -58,6 +64,8 @@ abstract class BasePagingListAdapter( */ abstract class BaseViewHolder(binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root) { + //region Functions + /** * Items are delivered to [bind] via [BaseListAdapter.onBindViewHolder] * @@ -65,4 +73,5 @@ abstract class BaseViewHolder(binding: ViewDataBinding) : RecyclerView.ViewHo * @param position position from adapter */ abstract fun bind(item: T) + //endregion } diff --git a/core/src/main/kotlin/com/melih/core/base/viewmodel/BasePagingViewModel.kt b/core/src/main/kotlin/com/melih/core/base/viewmodel/BasePagingViewModel.kt index 57da05b..413a602 100644 --- a/core/src/main/kotlin/com/melih/core/base/viewmodel/BasePagingViewModel.kt +++ b/core/src/main/kotlin/com/melih/core/base/viewmodel/BasePagingViewModel.kt @@ -1,7 +1,7 @@ package com.melih.core.base.viewmodel import androidx.lifecycle.LiveData -import androidx.lifecycle.Transformations +import androidx.lifecycle.Transformations.switchMap import androidx.lifecycle.ViewModel import androidx.paging.PagedList import androidx.paging.toLiveData @@ -20,19 +20,19 @@ import com.melih.repository.interactors.base.State */ abstract class BasePagingViewModel : ViewModel() { - // region Abstractions + //region Abstractions abstract val factory: BasePagingFactory abstract val config: PagedList.Config - // endregion + //endregion - // region Properties + //region Properties /** * Observe [stateData] to get notified of state of data */ val stateData: LiveData by lazy { - Transformations.switchMap(factory.currentSource) { + switchMap(factory.currentSource) { it.stateData } } @@ -41,7 +41,7 @@ abstract class BasePagingViewModel : ViewModel() { * Observe [errorData] to get notified if an error occurs */ val errorData: LiveData by lazy { - Transformations.switchMap(factory.currentSource) { + switchMap(factory.currentSource) { it.reasonData } } @@ -52,9 +52,9 @@ abstract class BasePagingViewModel : ViewModel() { val pagedList: LiveData> by lazy { factory.toLiveData(config) } - // endregion + //endregion - // region Functions + //region Functions fun refresh() { factory.currentSource.value?.invalidate() @@ -66,5 +66,5 @@ abstract class BasePagingViewModel : ViewModel() { fun retry() { factory.currentSource.value?.invalidate() } - // endregion + //endregion } diff --git a/core/src/main/kotlin/com/melih/core/base/viewmodel/BaseViewModel.kt b/core/src/main/kotlin/com/melih/core/base/viewmodel/BaseViewModel.kt index 2e3246b..6e7c440 100644 --- a/core/src/main/kotlin/com/melih/core/base/viewmodel/BaseViewModel.kt +++ b/core/src/main/kotlin/com/melih/core/base/viewmodel/BaseViewModel.kt @@ -15,10 +15,10 @@ import kotlinx.coroutines.launch */ abstract class BaseViewModel : ViewModel() { - // region Abstractions + //region Abstractions abstract suspend fun loadData() - // endregion + //endregion init { viewModelScope.launch { @@ -26,7 +26,7 @@ abstract class BaseViewModel : ViewModel() { } } - // region Properties + //region Properties private val _successData = MutableLiveData() private val _stateData = MutableLiveData() @@ -49,9 +49,9 @@ abstract class BaseViewModel : ViewModel() { */ val errorData: LiveData get() = _errorData - // endregion + //endregion - // region Functions + //region Functions /** * Default success handler which assigns given [data] to [successData] @@ -97,5 +97,5 @@ abstract class BaseViewModel : ViewModel() { loadData() } } - // endregion + //endregion } diff --git a/core/src/main/kotlin/com/melih/core/extensions/DiffUtilHelper.kt b/core/src/main/kotlin/com/melih/core/extensions/DiffUtilHelper.kt new file mode 100644 index 0000000..55bca7a --- /dev/null +++ b/core/src/main/kotlin/com/melih/core/extensions/DiffUtilHelper.kt @@ -0,0 +1,27 @@ +package com.melih.core.extensions + +import androidx.recyclerview.widget.DiffUtil + +/** + * Get [diff callback][DiffUtil.ItemCallback] for given type based on provided checker. + * It uses [itemCheck] for both [DiffUtil.ItemCallback.areItemsTheSame] and [DiffUtil.ItemCallback.areContentsTheSame]. + */ +inline fun createDiffCallback(crossinline itemCheck: (oldItem: T, newItem: T) -> Boolean) = createDiffCallback(itemCheck, itemCheck) + +/** + * Get [diff callback][DiffUtil.ItemCallback] for given type based on provided checker + */ +inline fun createDiffCallback( + crossinline itemCheck: (oldItem: T, newItem: T) -> Boolean, + crossinline contentCheck: (oldItem: T, newItem: T) -> Boolean +) = object : DiffUtil.ItemCallback() { + + //region Functions + + override fun areItemsTheSame(oldItem: T, newItem: T): Boolean = + itemCheck(oldItem, newItem) + + override fun areContentsTheSame(oldItem: T, newItem: T): Boolean = + contentCheck(oldItem, newItem) + //endregion +} diff --git a/core/src/main/kotlin/com/melih/core/extensions/LifecycleExtensions.kt b/core/src/main/kotlin/com/melih/core/extensions/LifecycleExtensions.kt index bf31e90..1396b42 100644 --- a/core/src/main/kotlin/com/melih/core/extensions/LifecycleExtensions.kt +++ b/core/src/main/kotlin/com/melih/core/extensions/LifecycleExtensions.kt @@ -3,9 +3,6 @@ package com.melih.core.extensions import androidx.fragment.app.Fragment import androidx.lifecycle.LiveData import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.ViewModelProviders /** * Reduces required boilerplate code to observe a live data @@ -16,13 +13,3 @@ import androidx.lifecycle.ViewModelProviders fun Fragment.observe(data: LiveData, block: (T) -> Unit) { data.observe(this, Observer(block)) } - -/** - * Method for getting viewModel from factory and run a block over it if required for easy access - * - * crossinline for unwanted returns - */ -inline fun ViewModelProvider.Factory.createFor( - fragment: Fragment, - crossinline block: T.() -> Unit = {} -): T = ViewModelProviders.of(fragment, this)[T::class.java].apply(block) diff --git a/core/src/main/kotlin/com/melih/core/extensions/RecyclerExtensions.kt b/core/src/main/kotlin/com/melih/core/extensions/RecyclerExtensions.kt deleted file mode 100644 index 011fac9..0000000 --- a/core/src/main/kotlin/com/melih/core/extensions/RecyclerExtensions.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.melih.core.extensions - -import androidx.recyclerview.widget.DiffUtil - -/** - * Get [diff callback][DiffUtil.ItemCallback] for given type based on provided checker - */ -inline fun getDiffCallbackForType(crossinline itemCheck: (oldItem: T, newItem: T) -> Boolean) = object : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: T, newItem: T): Boolean = - itemCheck(oldItem, newItem) - - override fun areContentsTheSame(oldItem: T, newItem: T): Boolean = - itemCheck(oldItem, newItem) -} diff --git a/core/src/main/res/values/colors.xml b/core/src/main/res/values/colors.xml index 270aec4..da95615 100644 --- a/core/src/main/res/values/colors.xml +++ b/core/src/main/res/values/colors.xml @@ -1,7 +1,11 @@ - #008577 - #00574B - #D81B60 - #8F8F8F + #f9a825 + #ffd95a + #c17900 + #757575 + #a4a4a4 + #494949 + #000000 + #686868 diff --git a/core/src/main/res/values/dimens.xml b/core/src/main/res/values/dimens.xml index ab4df8e..7bf765c 100644 --- a/core/src/main/res/values/dimens.xml +++ b/core/src/main/res/values/dimens.xml @@ -1,5 +1,7 @@ 8dp + 16dp + 4dp 11dp \ No newline at end of file diff --git a/core/src/main/res/values/styles.xml b/core/src/main/res/values/styles.xml index f7ae6d6..1d6d73c 100644 --- a/core/src/main/res/values/styles.xml +++ b/core/src/main/res/values/styles.xml @@ -1,35 +1,40 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/features/detail/src/main/kotlin/com/melih/detail/di/DetailContributor.kt b/features/detail/src/main/kotlin/com/melih/detail/di/DetailContributor.kt index f97d26a..6a648c9 100644 --- a/features/detail/src/main/kotlin/com/melih/detail/di/DetailContributor.kt +++ b/features/detail/src/main/kotlin/com/melih/detail/di/DetailContributor.kt @@ -12,12 +12,12 @@ import dagger.android.ContributesAndroidInjector @Module abstract class DetailContributor { - // region Contributes + //region Contributes @ContributesAndroidInjector( modules = [DetailFragmentModule::class] ) @DetailFragmentScope abstract fun detailFragment(): DetailFragment - // endregion + //endregion } diff --git a/features/detail/src/main/kotlin/com/melih/detail/di/modules/DetailFragmentModule.kt b/features/detail/src/main/kotlin/com/melih/detail/di/modules/DetailFragmentModule.kt index f7cfc4d..c4a58a2 100644 --- a/features/detail/src/main/kotlin/com/melih/detail/di/modules/DetailFragmentModule.kt +++ b/features/detail/src/main/kotlin/com/melih/detail/di/modules/DetailFragmentModule.kt @@ -15,13 +15,13 @@ import dagger.multibindings.IntoMap @Module abstract class DetailFragmentModule { - // region ViewModels + //region ViewModels @Binds @IntoMap @ViewModelKey(DetailViewModel::class) abstract fun detailViewModel(detailViewModel: DetailViewModel): ViewModel - // endregion + //endregion @Module companion object { diff --git a/features/detail/src/main/kotlin/com/melih/detail/ui/DetailFragment.kt b/features/detail/src/main/kotlin/com/melih/detail/ui/DetailFragment.kt index b9a9ca5..0ee2299 100644 --- a/features/detail/src/main/kotlin/com/melih/detail/ui/DetailFragment.kt +++ b/features/detail/src/main/kotlin/com/melih/detail/ui/DetailFragment.kt @@ -3,21 +3,20 @@ package com.melih.detail.ui import android.os.Bundle import android.text.method.ScrollingMovementMethod import android.view.View +import androidx.fragment.app.viewModels import com.melih.core.base.lifecycle.BaseDaggerFragment -import com.melih.core.extensions.createFor import com.melih.core.extensions.observe import com.melih.detail.R import com.melih.detail.databinding.DetailBinding class DetailFragment : BaseDaggerFragment() { - // region Properties + //region Properties - private val viewModel: DetailViewModel - get() = viewModelFactory.createFor(this) - // endregion + private val viewModel by viewModels { viewModelFactory } + //endregion - // region Functions + //region Functions override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -34,5 +33,5 @@ class DetailFragment : BaseDaggerFragment() { } override fun getLayoutId(): Int = R.layout.fragment_detail - // endregion + //endregion } diff --git a/features/detail/src/main/kotlin/com/melih/detail/ui/DetailViewModel.kt b/features/detail/src/main/kotlin/com/melih/detail/ui/DetailViewModel.kt index 83c28dd..f63ee29 100644 --- a/features/detail/src/main/kotlin/com/melih/detail/ui/DetailViewModel.kt +++ b/features/detail/src/main/kotlin/com/melih/detail/ui/DetailViewModel.kt @@ -1,6 +1,6 @@ package com.melih.detail.ui -import androidx.lifecycle.Transformations +import androidx.lifecycle.Transformations.map import com.melih.core.base.viewmodel.BaseViewModel import com.melih.repository.entities.LaunchEntity import com.melih.repository.interactors.GetLaunchDetails @@ -13,13 +13,13 @@ class DetailViewModel @Inject constructor( private val getLaunchDetailsParams: GetLaunchDetails.Params ) : BaseViewModel() { - // region Properties + //region Properties - val rocketName = Transformations.map(successData) { + val rocketName = map(successData) { it.rocket.name } - val description = Transformations.map(successData) { + val description = map(successData) { if (it.missions.isEmpty()) { "" } else { @@ -27,12 +27,12 @@ class DetailViewModel @Inject constructor( } } - val imageUrl = Transformations.map(successData) { + val imageUrl = map(successData) { it.rocket.imageURL } - // endregion + //endregion - // region Functions + //region Functions /** * Triggering interactor in view model scope @@ -42,5 +42,5 @@ class DetailViewModel @Inject constructor( it.handle(::handleState, ::handleFailure, ::handleSuccess) } } - // endregion + //endregion } diff --git a/features/detail/src/main/res/layout/fragment_detail.xml b/features/detail/src/main/res/layout/fragment_detail.xml index abf4e90..dde2a8f 100644 --- a/features/detail/src/main/res/layout/fragment_detail.xml +++ b/features/detail/src/main/res/layout/fragment_detail.xml @@ -1,75 +1,69 @@ + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> - + - - + + - + - + - + - - - - - + + diff --git a/features/launches/build.gradle b/features/launches/build.gradle index 1128480..740d414 100644 --- a/features/launches/build.gradle +++ b/features/launches/build.gradle @@ -8,6 +8,7 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation libraries.paging + implementation libraries.swipeRefreshLayout testImplementation testLibraries.coroutinesTest } diff --git a/features/launches/src/main/kotlin/com/melih/list/di/LaunchesContributor.kt b/features/launches/src/main/kotlin/com/melih/list/di/LaunchesContributor.kt index 9b1d947..7de0829 100644 --- a/features/launches/src/main/kotlin/com/melih/list/di/LaunchesContributor.kt +++ b/features/launches/src/main/kotlin/com/melih/list/di/LaunchesContributor.kt @@ -12,12 +12,12 @@ import dagger.android.ContributesAndroidInjector @Module abstract class LaunchesContributor { - // region Contributes + //region Contributes @ContributesAndroidInjector( modules = [LaunchesFragmentModule::class] ) @LaunchesFragmentScope abstract fun launchesFragment(): LaunchesFragment - // endregion + //endregion } diff --git a/features/launches/src/main/kotlin/com/melih/list/di/modules/LaunchesFragmentModule.kt b/features/launches/src/main/kotlin/com/melih/list/di/modules/LaunchesFragmentModule.kt index eadf9b4..fc47acb 100644 --- a/features/launches/src/main/kotlin/com/melih/list/di/modules/LaunchesFragmentModule.kt +++ b/features/launches/src/main/kotlin/com/melih/list/di/modules/LaunchesFragmentModule.kt @@ -14,13 +14,13 @@ import dagger.multibindings.IntoMap @Module abstract class LaunchesFragmentModule { - // region ViewModels + //region ViewModels @Binds @IntoMap @ViewModelKey(LaunchesViewModel::class) abstract fun listViewModel(listViewModel: LaunchesViewModel): ViewModel - // endregion + //endregion @Module companion object { diff --git a/features/launches/src/main/kotlin/com/melih/list/ui/LaunchesFragment.kt b/features/launches/src/main/kotlin/com/melih/list/ui/LaunchesFragment.kt index 66a7ec6..c4e02e7 100644 --- a/features/launches/src/main/kotlin/com/melih/list/ui/LaunchesFragment.kt +++ b/features/launches/src/main/kotlin/com/melih/list/ui/LaunchesFragment.kt @@ -2,29 +2,29 @@ package com.melih.list.ui import android.os.Bundle import android.view.View +import androidx.fragment.app.viewModels import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import com.melih.core.actions.openDetail import com.melih.core.base.lifecycle.BaseDaggerFragment -import com.melih.core.extensions.createFor import com.melih.core.extensions.observe import com.melih.list.R import com.melih.list.databinding.ListBinding import com.melih.list.ui.adapters.LaunchesAdapter import com.melih.list.ui.vm.LaunchesViewModel import com.melih.repository.entities.LaunchEntity +import com.melih.repository.interactors.base.PersistenceEmpty import com.melih.repository.interactors.base.State class LaunchesFragment : BaseDaggerFragment(), SwipeRefreshLayout.OnRefreshListener { - // region Properties + //region Properties - private val viewModel: LaunchesViewModel - get() = viewModelFactory.createFor(this) + private val viewModel by viewModels { viewModelFactory } private val launchesAdapter = LaunchesAdapter(::onItemSelected) - // endregion + //endregion - // region Functions + //region Lifecyle override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -36,6 +36,23 @@ class LaunchesFragment : BaseDaggerFragment(), SwipeRefreshLayout.O observeDataChanges() } + override fun onResume() { + super.onResume() + + // Workaround for SwipeRefreshLayout leak -> https://issuetracker.google.com/issues/136153683 + binding.swipeRefreshLayout.isEnabled = true + } + + override fun onPause() { + super.onPause() + + // Workaround for SwipeRefreshLayout leak -> https://issuetracker.google.com/issues/136153683 + binding.swipeRefreshLayout.isEnabled = false + } + //endregion + + //region Functions + private fun observeDataChanges() { // Observing state to show loading @@ -45,8 +62,10 @@ class LaunchesFragment : BaseDaggerFragment(), SwipeRefreshLayout.O // Observing error to show toast with retry action observe(viewModel.errorData) { - showSnackbarWithAction(it) { - viewModel.retry() + if (it !is PersistenceEmpty) { + showSnackbarWithAction(it) { + viewModel.retry() + } } } @@ -64,5 +83,5 @@ class LaunchesFragment : BaseDaggerFragment(), SwipeRefreshLayout.O } override fun getLayoutId(): Int = R.layout.fragment_launches - // endregion + //endregion } diff --git a/features/launches/src/main/kotlin/com/melih/list/ui/adapters/LaunchesAdapter.kt b/features/launches/src/main/kotlin/com/melih/list/ui/adapters/LaunchesAdapter.kt index c998b2c..d759333 100644 --- a/features/launches/src/main/kotlin/com/melih/list/ui/adapters/LaunchesAdapter.kt +++ b/features/launches/src/main/kotlin/com/melih/list/ui/adapters/LaunchesAdapter.kt @@ -4,25 +4,30 @@ import android.view.LayoutInflater import android.view.ViewGroup import com.melih.core.base.recycler.BasePagingListAdapter import com.melih.core.base.recycler.BaseViewHolder -import com.melih.core.extensions.getDiffCallbackForType +import com.melih.core.extensions.createDiffCallback import com.melih.list.databinding.LaunchRowBinding import com.melih.repository.entities.LaunchEntity class LaunchesAdapter(itemClickListener: (LaunchEntity) -> Unit) : BasePagingListAdapter( - getDiffCallbackForType { oldItem, newItem -> oldItem.id == newItem.id }, + createDiffCallback { oldItem, newItem -> oldItem.id == newItem.id }, itemClickListener ) { + + //region Functions + override fun createViewHolder( inflater: LayoutInflater, parent: ViewGroup, viewType: Int ): BaseViewHolder = LaunchesViewHolder(LaunchRowBinding.inflate(inflater, parent, false)) - + //endregion } class LaunchesViewHolder(private val binding: LaunchRowBinding) : BaseViewHolder(binding) { + //region Functions + override fun bind(item: LaunchEntity) { binding.entity = item @@ -31,4 +36,5 @@ class LaunchesViewHolder(private val binding: LaunchRowBinding) : BaseViewHolder binding.executePendingBindings() } + //endregion } diff --git a/features/launches/src/main/kotlin/com/melih/list/ui/paging/LaunchesPagingSource.kt b/features/launches/src/main/kotlin/com/melih/list/ui/paging/LaunchesPagingSource.kt index 9ec0bf8..a234c44 100644 --- a/features/launches/src/main/kotlin/com/melih/list/ui/paging/LaunchesPagingSource.kt +++ b/features/launches/src/main/kotlin/com/melih/list/ui/paging/LaunchesPagingSource.kt @@ -8,6 +8,9 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import javax.inject.Inject +/** + * Uses [GetLaunches] to get data for pagination + */ class LaunchesPagingSource @Inject constructor( private val getLaunches: GetLaunches, private val getLaunchesParams: GetLaunches.Params diff --git a/features/launches/src/main/kotlin/com/melih/list/ui/paging/LaunchesPagingSourceFactory.kt b/features/launches/src/main/kotlin/com/melih/list/ui/paging/LaunchesPagingSourceFactory.kt index d176076..f9f7c46 100644 --- a/features/launches/src/main/kotlin/com/melih/list/ui/paging/LaunchesPagingSourceFactory.kt +++ b/features/launches/src/main/kotlin/com/melih/list/ui/paging/LaunchesPagingSourceFactory.kt @@ -10,5 +10,8 @@ class LaunchesPagingSourceFactory @Inject constructor( private val sourceProvider: Provider ) : BasePagingFactory() { + //region Functions + override fun createSource(): BasePagingDataSource = sourceProvider.get() + //endregion } diff --git a/features/launches/src/main/kotlin/com/melih/list/ui/vm/LaunchesViewModel.kt b/features/launches/src/main/kotlin/com/melih/list/ui/vm/LaunchesViewModel.kt index 4576d9c..fc45b4c 100644 --- a/features/launches/src/main/kotlin/com/melih/list/ui/vm/LaunchesViewModel.kt +++ b/features/launches/src/main/kotlin/com/melih/list/ui/vm/LaunchesViewModel.kt @@ -12,34 +12,12 @@ class LaunchesViewModel @Inject constructor( private val launchesPagingConfig: PagedList.Config ) : BasePagingViewModel() { - // region Properties + //region Properties override val factory: BasePagingFactory get() = launchesPagingSourceFactory override val config: PagedList.Config get() = launchesPagingConfig - - //private val _filteredItems = MediatorLiveData>() - - //val filteredItems: LiveData> - // get() = _filteredItems - // endregion - - //init { - // _filteredItems.addSource(pagedList, _filteredItems::setValue) - //} - - // region Functions - - //fun filterItemListBy(query: String?) { - // - // _filteredItems.value = if (!query.isNullOrBlank()) { - // pagedList.value - // ?.snapshot() as PagedList - // } else { - // pagedList.value - // } - //} - // endregion + //endregion } diff --git a/features/launches/src/main/res/layout/fragment_launches.xml b/features/launches/src/main/res/layout/fragment_launches.xml index cac321d..dce7104 100644 --- a/features/launches/src/main/res/layout/fragment_launches.xml +++ b/features/launches/src/main/res/layout/fragment_launches.xml @@ -1,37 +1,36 @@ + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> - + - - + + - + - + - - - + + + diff --git a/features/launches/src/main/res/layout/row_launch.xml b/features/launches/src/main/res/layout/row_launch.xml index bb62bbf..184d1c0 100644 --- a/features/launches/src/main/res/layout/row_launch.xml +++ b/features/launches/src/main/res/layout/row_launch.xml @@ -1,77 +1,69 @@ + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> - + - - + + - + - + - + - + - - - - + + + diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 944f2c1..83937fc 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Jun 13 10:50:25 CEST 2019 +#Wed Sep 04 15:39:59 CEST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.5-all.zip diff --git a/repository/src/main/kotlin/com/melih/repository/Constants.kt b/repository/src/main/kotlin/com/melih/repository/Constants.kt index 05de778..5d5e2dc 100644 --- a/repository/src/main/kotlin/com/melih/repository/Constants.kt +++ b/repository/src/main/kotlin/com/melih/repository/Constants.kt @@ -1,5 +1,4 @@ package com.melih.repository -const val DEFAULT_NAME = "Default name" -const val EMPTY_STRING = "" -const val DEFAULT_IMAGE_SIZE = 480 +internal const val DEFAULT_NAME = "Default name" +internal const val EMPTY_STRING = "" diff --git a/repository/src/main/kotlin/com/melih/repository/Repository.kt b/repository/src/main/kotlin/com/melih/repository/Repository.kt index 82a9d41..af1e3d8 100644 --- a/repository/src/main/kotlin/com/melih/repository/Repository.kt +++ b/repository/src/main/kotlin/com/melih/repository/Repository.kt @@ -4,10 +4,13 @@ import com.melih.repository.entities.LaunchEntity import com.melih.repository.interactors.base.Result /** - * Abstract class to create contract in sources to seperate low level business logic from build and return type + * Contract for sources to seperate low level business logic from build and return type */ abstract class Repository { + //region Abstractions + internal abstract suspend fun getNextLaunches(count: Int, page: Int): Result> internal abstract suspend fun getLaunchById(id: Long): Result + //endregion } diff --git a/repository/src/main/kotlin/com/melih/repository/interactors/GetLaunchDetails.kt b/repository/src/main/kotlin/com/melih/repository/interactors/GetLaunchDetails.kt index 7bf13d5..5f9de0d 100644 --- a/repository/src/main/kotlin/com/melih/repository/interactors/GetLaunchDetails.kt +++ b/repository/src/main/kotlin/com/melih/repository/interactors/GetLaunchDetails.kt @@ -18,11 +18,16 @@ import javax.inject.Inject @UseExperimental(ExperimentalCoroutinesApi::class) class GetLaunchDetails @Inject constructor() : BaseInteractor() { + //region Properties + @field:Inject internal lateinit var networkSource: NetworkSource @field:Inject internal lateinit var persistenceSource: PersistenceSource + //endregion + + //region Functions override suspend fun FlowCollector>.run(params: Params) { val result = persistenceSource.getLaunchById(params.id) @@ -42,6 +47,7 @@ class GetLaunchDetails @Inject constructor() : BaseInteractor, GetLaunches.Params>() { + //region Properties + @field:Inject internal lateinit var networkSource: NetworkSource @field:Inject internal lateinit var persistenceSource: PersistenceSource + //endregion + + //region Functions override suspend fun FlowCollector>>.run(params: Params) { @@ -40,6 +45,7 @@ class GetLaunches @Inject constructor() : BaseInteractor, Get } } } + //endregion data class Params( val count: Int = DEFAULT_LAUNCHES_AMOUNT, diff --git a/repository/src/main/kotlin/com/melih/repository/interactors/base/BaseInteractor.kt b/repository/src/main/kotlin/com/melih/repository/interactors/base/BaseInteractor.kt index e431a2d..bb7605c 100644 --- a/repository/src/main/kotlin/com/melih/repository/interactors/base/BaseInteractor.kt +++ b/repository/src/main/kotlin/com/melih/repository/interactors/base/BaseInteractor.kt @@ -13,12 +13,12 @@ import kotlinx.coroutines.flow.flowOn @UseExperimental(ExperimentalCoroutinesApi::class) abstract class BaseInteractor { - // region Abstractions + //region Abstractions protected abstract suspend fun FlowCollector>.run(params: P) - // endregion + //endregion - // region Functions + //region Functions operator fun invoke(params: P) = flow> { @@ -26,7 +26,7 @@ abstract class BaseInteractor { run(params) emit(State.Loaded()) }.flowOn(Dispatchers.IO) - // endregion + //endregion } /** diff --git a/repository/src/main/kotlin/com/melih/repository/interactors/base/Reason.kt b/repository/src/main/kotlin/com/melih/repository/interactors/base/Reason.kt index e14b96e..fa14e9a 100644 --- a/repository/src/main/kotlin/com/melih/repository/interactors/base/Reason.kt +++ b/repository/src/main/kotlin/com/melih/repository/interactors/base/Reason.kt @@ -9,6 +9,8 @@ import com.melih.repository.R */ sealed class Reason(@StringRes val messageRes: Int) +//region Subclasses + class NetworkError : Reason(R.string.reason_network) class EmptyResultError : Reason(R.string.reason_empty_body) class GenericError : Reason(R.string.reason_generic) @@ -16,3 +18,4 @@ class ResponseError : Reason(R.string.reason_response) class TimeoutError : Reason(R.string.reason_timeout) class PersistenceEmpty : Reason(R.string.reason_persistance_empty) class NoNetworkPersistenceEmpty : Reason(R.string.reason_no_network_persistance_empty) +//endregion diff --git a/repository/src/main/kotlin/com/melih/repository/interactors/base/Result.kt b/repository/src/main/kotlin/com/melih/repository/interactors/base/Result.kt index a5684c7..e34eea7 100644 --- a/repository/src/main/kotlin/com/melih/repository/interactors/base/Result.kt +++ b/repository/src/main/kotlin/com/melih/repository/interactors/base/Result.kt @@ -9,7 +9,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi @UseExperimental(ExperimentalCoroutinesApi::class) sealed class Result -// region Subclasses +//region Subclasses class Success(val successData: T) : Result() class Failure(val errorData: Reason) : Result() @@ -18,9 +18,9 @@ sealed class State : Result() { class Loading : State() class Loaded : State() } -// endregion +//endregion -// region Extensions +//region Extensions inline fun Result.handle(stateBlock: (State) -> Unit, failureBlock: (Reason) -> Unit, successBlock: (T) -> Unit) { when (this) { @@ -50,4 +50,4 @@ inline fun Result.onState(stateBlock: (State) -> Unit): Result { return this } -// endregion +//endregion diff --git a/repository/src/main/kotlin/com/melih/repository/network/Api.kt b/repository/src/main/kotlin/com/melih/repository/network/Api.kt index 0b410d8..2783559 100644 --- a/repository/src/main/kotlin/com/melih/repository/network/Api.kt +++ b/repository/src/main/kotlin/com/melih/repository/network/Api.kt @@ -12,6 +12,8 @@ import retrofit2.http.Query */ internal interface Api { + //region Get + @GET("launch/next/{count}") suspend fun getNextLaunches( @Path("count") count: Int, @@ -22,4 +24,5 @@ internal interface Api { suspend fun getLaunchById( @Path("id") id: Long ): Response + //endregion } diff --git a/repository/src/main/kotlin/com/melih/repository/network/ApiImpl.kt b/repository/src/main/kotlin/com/melih/repository/network/ApiImpl.kt index 85ca556..c56ffb8 100644 --- a/repository/src/main/kotlin/com/melih/repository/network/ApiImpl.kt +++ b/repository/src/main/kotlin/com/melih/repository/network/ApiImpl.kt @@ -16,7 +16,7 @@ internal const val TIMEOUT_DURATION = 7L internal class ApiImpl @Inject constructor() : Api { - // region Properties + //region Properties private val service by lazy { val moshi = Moshi.Builder() @@ -39,7 +39,9 @@ internal class ApiImpl @Inject constructor() : Api { .build() .create(Api::class.java) } - // endregion + //endregion + + //region Functions override suspend fun getNextLaunches( count: Int, @@ -51,4 +53,5 @@ internal class ApiImpl @Inject constructor() : Api { id: Long ): Response = service.getLaunchById(id) + //endregion } diff --git a/repository/src/main/kotlin/com/melih/repository/persistence/LaunchesDatabase.kt b/repository/src/main/kotlin/com/melih/repository/persistence/LaunchesDatabase.kt index 4ab8fb7..636dbab 100644 --- a/repository/src/main/kotlin/com/melih/repository/persistence/LaunchesDatabase.kt +++ b/repository/src/main/kotlin/com/melih/repository/persistence/LaunchesDatabase.kt @@ -28,6 +28,8 @@ const val DB_NAME = "LaunchesDB" ) internal abstract class LaunchesDatabase : RoomDatabase() { + //region Companion + companion object { private lateinit var instance: LaunchesDatabase @@ -42,6 +44,10 @@ internal abstract class LaunchesDatabase : RoomDatabase() { } } + //endregion + + //region Abstractions internal abstract val launchesDao: LaunchesDao + //endregion } diff --git a/repository/src/main/kotlin/com/melih/repository/persistence/converters/BaseConverter.kt b/repository/src/main/kotlin/com/melih/repository/persistence/converters/BaseConverter.kt index 735d615..cc9d698 100644 --- a/repository/src/main/kotlin/com/melih/repository/persistence/converters/BaseConverter.kt +++ b/repository/src/main/kotlin/com/melih/repository/persistence/converters/BaseConverter.kt @@ -10,13 +10,19 @@ import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory */ abstract class BaseConverter { + //region Abstractions + + abstract fun getAdapter(moshi: Moshi): JsonAdapter + //endregion + + //region Properties + private val moshi = Moshi.Builder() .add(KotlinJsonAdapterFactory()) .build() + //endregion - abstract fun getAdapter(moshi: Moshi): JsonAdapter - - // region Functions + //region Functions @TypeConverter fun convertFrom(item: T) = @@ -25,5 +31,5 @@ abstract class BaseConverter { @TypeConverter fun convertTo(string: String) = getAdapter(moshi).fromJson(string) - // endregion + //endregion } diff --git a/repository/src/main/kotlin/com/melih/repository/persistence/converters/BaseListConverter.kt b/repository/src/main/kotlin/com/melih/repository/persistence/converters/BaseListConverter.kt index f304c0a..7bdc189 100644 --- a/repository/src/main/kotlin/com/melih/repository/persistence/converters/BaseListConverter.kt +++ b/repository/src/main/kotlin/com/melih/repository/persistence/converters/BaseListConverter.kt @@ -10,13 +10,19 @@ import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory */ abstract class BaseListConverter { + //region Abstractions + + abstract fun getAdapter(moshi: Moshi): JsonAdapter> + //endregion + + //region Properties + private val moshi = Moshi.Builder() .add(KotlinJsonAdapterFactory()) .build() + //endregion - abstract fun getAdapter(moshi: Moshi): JsonAdapter> - - // region Functions + //region Functions @TypeConverter fun convertFrom(items: List) = @@ -25,5 +31,5 @@ abstract class BaseListConverter { @TypeConverter fun convertTo(string: String): List? = getAdapter(moshi).fromJson(string) - // endregion + //endregion } diff --git a/repository/src/main/kotlin/com/melih/repository/persistence/converters/LocationConverter.kt b/repository/src/main/kotlin/com/melih/repository/persistence/converters/LocationConverter.kt index 6742ecb..399efb9 100644 --- a/repository/src/main/kotlin/com/melih/repository/persistence/converters/LocationConverter.kt +++ b/repository/src/main/kotlin/com/melih/repository/persistence/converters/LocationConverter.kt @@ -9,6 +9,7 @@ import com.squareup.moshi.Moshi * Converts [location][LocationEntity] */ class LocationConverter : BaseConverter() { + override fun getAdapter(moshi: Moshi): JsonAdapter = LocationEntityJsonAdapter(moshi) } diff --git a/repository/src/main/kotlin/com/melih/repository/persistence/converters/RocketConverter.kt b/repository/src/main/kotlin/com/melih/repository/persistence/converters/RocketConverter.kt index de2e3b8..66b3606 100644 --- a/repository/src/main/kotlin/com/melih/repository/persistence/converters/RocketConverter.kt +++ b/repository/src/main/kotlin/com/melih/repository/persistence/converters/RocketConverter.kt @@ -9,6 +9,7 @@ import com.squareup.moshi.Moshi * Converts [rocket][RocketEntity] */ class RocketConverter : BaseConverter() { + override fun getAdapter(moshi: Moshi): JsonAdapter = - RocketEntityJsonAdapter(moshi) + RocketEntityJsonAdapter(moshi) } diff --git a/repository/src/main/kotlin/com/melih/repository/persistence/dao/LaunchesDao.kt b/repository/src/main/kotlin/com/melih/repository/persistence/dao/LaunchesDao.kt index df4b014..2490cdd 100644 --- a/repository/src/main/kotlin/com/melih/repository/persistence/dao/LaunchesDao.kt +++ b/repository/src/main/kotlin/com/melih/repository/persistence/dao/LaunchesDao.kt @@ -12,7 +12,7 @@ import com.melih.repository.entities.LaunchEntity @Dao internal abstract class LaunchesDao { - // region Queries + //region Queries @Query("SELECT * FROM Launches ORDER BY launchStartTime DESC LIMIT :count OFFSET :page*:count") abstract suspend fun getLaunches(count: Int, page: Int): List @@ -22,14 +22,14 @@ internal abstract class LaunchesDao { @Query("DELETE FROM Launches") abstract suspend fun nukeLaunches() - // endregion + //endregion - // region Insertion + //region Insertion @Insert(onConflict = OnConflictStrategy.REPLACE) abstract suspend fun saveLaunches(launches: List) @Insert(onConflict = OnConflictStrategy.REPLACE) abstract suspend fun saveLaunch(launch: LaunchEntity) - // endregion + //endregion } diff --git a/repository/src/main/kotlin/com/melih/repository/sources/NetworkSource.kt b/repository/src/main/kotlin/com/melih/repository/sources/NetworkSource.kt index ed14fbe..785e0a2 100644 --- a/repository/src/main/kotlin/com/melih/repository/sources/NetworkSource.kt +++ b/repository/src/main/kotlin/com/melih/repository/sources/NetworkSource.kt @@ -1,7 +1,6 @@ package com.melih.repository.sources import android.net.NetworkInfo -import com.melih.repository.DEFAULT_IMAGE_SIZE import com.melih.repository.Repository import com.melih.repository.entities.LaunchEntity import com.melih.repository.interactors.DEFAULT_LAUNCHES_AMOUNT @@ -19,6 +18,8 @@ import java.io.IOException import javax.inject.Inject import javax.inject.Provider +private const val DEFAULT_IMAGE_SIZE = 480 + /** * NetworkSource for fetching results using api and wrapping them as contracted in [repository][Repository], * returning either [failure][Failure] with proper [reason][Reason] or [success][Success] with data @@ -27,16 +28,17 @@ internal class NetworkSource @Inject constructor( private val apiImpl: ApiImpl, private val networkInfoProvider: Provider ) : Repository() { - // region Properties + + //region Properties private val isNetworkConnected: Boolean get() { val networkInfo = networkInfoProvider.get() return networkInfo != null && networkInfo.isConnected } - // endregion + //endregion - // region Functions + //region Functions override suspend fun getNextLaunches(count: Int, page: Int): Result> = safeExecute({ @@ -73,8 +75,8 @@ internal class NetworkSource @Inject constructor( } } - private suspend inline fun safeExecute( - block: suspend () -> Response, + private inline fun safeExecute( + block: () -> Response, transform: (T) -> R ) = if (isNetworkConnected) { @@ -112,5 +114,5 @@ internal class NetworkSource @Inject constructor( } catch (e: Exception) { imageUrl } - // endregion + //endregion } diff --git a/repository/src/main/kotlin/com/melih/repository/sources/PersistenceSource.kt b/repository/src/main/kotlin/com/melih/repository/sources/PersistenceSource.kt index 366c683..c49881d 100644 --- a/repository/src/main/kotlin/com/melih/repository/sources/PersistenceSource.kt +++ b/repository/src/main/kotlin/com/melih/repository/sources/PersistenceSource.kt @@ -16,7 +16,8 @@ import javax.inject.Inject internal class PersistenceSource @Inject constructor( ctx: Context ) : Repository() { - // region Functions + + //region Functions private val launchesDatabase = LaunchesDatabase.getInstance(ctx) @@ -45,5 +46,5 @@ internal class PersistenceSource @Inject constructor( internal suspend fun saveLaunch(launch: LaunchEntity) { launchesDatabase.launchesDao.saveLaunch(launch) } - // endregion + //endregion } diff --git a/scripts/default_android_config.gradle b/scripts/default_android_config.gradle index 576744b..4546de2 100644 --- a/scripts/default_android_config.gradle +++ b/scripts/default_android_config.gradle @@ -2,6 +2,7 @@ apply from: "$rootProject.projectDir/scripts/default_dependencies.gradle" android { compileSdkVersion versions.compileSdkVersion + buildToolsVersion versions.buildToolsVersion defaultConfig { minSdkVersion versions.minSdkVersion diff --git a/scripts/dependencies.gradle b/scripts/dependencies.gradle index 2c3811a..21ff897 100644 --- a/scripts/dependencies.gradle +++ b/scripts/dependencies.gradle @@ -1,77 +1,80 @@ ext { versions = [ - minSdkVersion : 21, - compileSdkVersion : 28, - targetSdkVersion : 28, - buildToolsVersion : "28.0.3", - supportLibraryVersion : "28.0.0", - appCompatVersion : "1.1.0-rc01", - lifecycleVersion : "2.2.0-alpha03", - fragmentVersion : "1.2.0-alpha02", - workManagerVersion : "2.2.0-rc01", - constraintLayoutVesion: "2.0.0-beta1", - cardViewVersion : "1.0.0", - recyclerViewVersion : "1.1.0-beta02", - pagingVersion : "2.1.0", - viewPagerVersion : "1.0.0-beta03", - collectionVersion : "1.1.0", - roomVersion : "2.2.0-alpha02", - daggerVersion : "2.24", - okHttpVersion : "4.0.1", - retrofitVersion : "2.6.1", - picassoVersion : "2.71828", - moshiVersion : "1.8.0", - coroutinesVersion : "1.3.0-RC2", - leakCanaryVersion : "2.0-beta-2", - timberVersion : "4.7.1", - jUnitVersion : "5.5.1", - espressoVersion : "3.2.0", - mockkVersion : "1.9.3", - kluentVersion : "1.53", + minSdkVersion : 21, + compileSdkVersion : 29, + targetSdkVersion : 29, + buildToolsVersion : "29.0.2", + appCompatVersion : "1.1.0-rc01", + lifecycleVersion : "2.2.0-alpha03", + fragmentVersion : "1.2.0-alpha02", + workManagerVersion : "2.3.0-alpha01", + constraintLayoutVesion : "2.0.0-beta2", + cardViewVersion : "1.0.0", + recyclerViewVersion : "1.1.0-beta03", + pagingVersion : "2.1.0", + viewPagerVersion : "1.0.0-beta03", + materialVersion : "1.1.0-alpha09", + swipeRefreshLayoutVersion: "1.1.0-alpha02", + collectionVersion : "1.1.0", + roomVersion : "2.2.0-rc01", + daggerVersion : "2.24", + okHttpVersion : "4.0.1", + retrofitVersion : "2.6.1", + picassoVersion : "2.71828", + moshiVersion : "1.8.0", + coroutinesVersion : "1.3.0-RC2", + leakCanaryVersion : "2.0-beta-2", + timberVersion : "4.7.1", + jUnitVersion : "5.5.1", + espressoVersion : "3.2.0", + mockkVersion : "1.9.3", + kluentVersion : "1.53", ] libraries = [ /** * Android libraries */ - appCompat : "androidx.appcompat:appcompat:${versions.appCompatVersion}", - recyclerView : "androidx.recyclerview:recyclerview:${versions.recyclerViewVersion}", - cardView : "androidx.cardview:cardview:${versions.cardViewVersion}", - constraintLayout: "androidx.constraintlayout:constraintlayout:${versions.constraintLayoutVesion}", - multixDex : "androidx.multidex:multidex:2.0.1", - fragment : "androidx.fragment:fragment-ktx:${versions.fragmentVersion}", + appCompat : "androidx.appcompat:appcompat:${versions.appCompatVersion}", + recyclerView : "androidx.recyclerview:recyclerview:${versions.recyclerViewVersion}", + cardView : "androidx.cardview:cardview:${versions.cardViewVersion}", + constraintLayout : "androidx.constraintlayout:constraintlayout:${versions.constraintLayoutVesion}", + multixDex : "androidx.multidex:multidex:2.0.1", + fragment : "androidx.fragment:fragment-ktx:${versions.fragmentVersion}", + material : "com.google.android.material:material:${versions.materialVersion}", /** * Jetpack */ - navigation : [ + navigation : [ "androidx.navigation:navigation-fragment-ktx:$nav_version", "androidx.navigation:navigation-ui-ktx:$nav_version" ], - room : [ + room : [ "androidx.room:room-runtime:${versions.roomVersion}", "androidx.room:room-ktx:${versions.roomVersion}" ], - lifecycle : "androidx.lifecycle:lifecycle-extensions:${versions.lifecycleVersion}", - liveDataKTX : "androidx.lifecycle:lifecycle-livedata-ktx:${versions.lifecycleVersion}", - workManager : "androidx.work:work-runtime-ktx:${versions.workManagerVersion}", - paging : "androidx.paging:paging-runtime-ktx:${versions.pagingVersion}", - viewPager : "androidx.viewpager2:viewpager2:${versions.viewPagerVersion}", - collection : "androidx.collection:collection-ktx:${versions.collectionVersion}", + lifecycle : "androidx.lifecycle:lifecycle-extensions:${versions.lifecycleVersion}", + liveDataKTX : "androidx.lifecycle:lifecycle-livedata-ktx:${versions.lifecycleVersion}", + workManager : "androidx.work:work-runtime-ktx:${versions.workManagerVersion}", + paging : "androidx.paging:paging-runtime-ktx:${versions.pagingVersion}", + viewPager : "androidx.viewpager2:viewpager2:${versions.viewPagerVersion}", + collection : "androidx.collection:collection-ktx:${versions.collectionVersion}", + swipeRefreshLayout: "androidx.swiperefreshlayout:swiperefreshlayout:${versions.swipeRefreshLayoutVersion}", /** * Kotlin */ - kotlin : "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version", - coroutines : "org.jetbrains.kotlinx:kotlinx-coroutines-android:${versions.coroutinesVersion}", + kotlin : "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version", + coroutines : "org.jetbrains.kotlinx:kotlinx-coroutines-android:${versions.coroutinesVersion}", /** * Dagger */ - dagger : [ + dagger : [ "com.google.dagger:dagger:${versions.daggerVersion}", "com.google.dagger:dagger-android:${versions.daggerVersion}", "com.google.dagger:dagger-android-support:${versions.daggerVersion}" @@ -80,17 +83,17 @@ ext { /** * OkHttp */ - okHttp : [ + okHttp : [ "com.squareup.okhttp3:okhttp:${versions.okHttpVersion}", "com.squareup.okhttp3:logging-interceptor:${versions.okHttpVersion}" ], - okHttpLogger : "com.squareup.okhttp3:logging-interceptor:${versions.okHttpVersion}", + okHttpLogger : "com.squareup.okhttp3:logging-interceptor:${versions.okHttpVersion}", /** * Retrofit */ - retrofit : [ + retrofit : [ "com.squareup.retrofit2:retrofit:${versions.retrofitVersion}", "com.squareup.retrofit2:converter-moshi:${versions.retrofitVersion}" ], @@ -98,27 +101,27 @@ ext { /** * Moshi */ - moshi : [ + moshi : [ "com.squareup.moshi:moshi:${versions.moshiVersion}", "com.squareup.moshi:moshi-kotlin:${versions.moshiVersion}" ], - moshiKotlin : "com.squareup.moshi:moshi-kotlin:${versions.moshiVersion}", + moshiKotlin : "com.squareup.moshi:moshi-kotlin:${versions.moshiVersion}", /** * Picasso for image loading */ - picasso : "com.squareup.picasso:picasso:${versions.picassoVersion}", + picasso : "com.squareup.picasso:picasso:${versions.picassoVersion}", /** * LeakCanary */ - leakCanary : "com.squareup.leakcanary:leakcanary-android:${versions.leakCanaryVersion}", + leakCanary : "com.squareup.leakcanary:leakcanary-android:${versions.leakCanaryVersion}", /** * Timber */ - timber : "com.jakewharton.timber:timber:${versions.timberVersion}" + timber : "com.jakewharton.timber:timber:${versions.timberVersion}" ] annotationProcessors = [ diff --git a/scripts/flavors.gradle b/scripts/flavors.gradle deleted file mode 100644 index 496ab22..0000000 --- a/scripts/flavors.gradle +++ /dev/null @@ -1,18 +0,0 @@ -android { - buildTypes { - release { - minifyEnabled true - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - - debug { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - testCoverageEnabled = true - } - - dev { - initWith debug - } - } -} diff --git a/scripts/module.gradle b/scripts/module.gradle index 8bfa550..de40f3f 100644 --- a/scripts/module.gradle +++ b/scripts/module.gradle @@ -1,3 +1,2 @@ apply from: "$rootProject.projectDir/scripts/default_android_config.gradle" apply from: "$rootProject.projectDir/scripts/sources.gradle" -apply from: "$rootProject.projectDir/scripts/flavors.gradle" diff --git a/scripts/sources.gradle b/scripts/sources.gradle index dcbd8fc..a6af0a8 100644 --- a/scripts/sources.gradle +++ b/scripts/sources.gradle @@ -14,4 +14,21 @@ android { jvmTarget = '1.8' freeCompilerArgs += "-Xuse-experimental=kotlin.Experimental" } + + buildTypes { + release { + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + + debug { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + testCoverageEnabled = true + } + + dev { + initWith debug + } + } } From c3dba5ed71911293e42135c295954d1a73ea821a Mon Sep 17 00:00:00 2001 From: Melih Aksoy Date: Fri, 20 Sep 2019 18:31:17 +0200 Subject: [PATCH 08/20] Update android.yml --- .github/workflows/android.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 1bbca1e..0972801 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -1,6 +1,6 @@ name: Android CI -on: [push] +on: [push, pull_request] jobs: From 9aa7a5b25f76f9c658b7cc31053af9b0898262a7 Mon Sep 17 00:00:00 2001 From: Melih Aksoy Date: Mon, 23 Sep 2019 21:50:30 +0200 Subject: [PATCH 09/20] Updated fastlane version (#39) Signed-off-by: Melih Aksoy --- Gemfile.lock | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 7eac911..b9db40d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,9 +1,9 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.0) - addressable (2.6.0) - public_suffix (>= 2.0.2, < 4.0) + CFPropertyList (3.0.1) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) atomos (0.1.3) babosa (1.0.2) claide (1.0.3) @@ -26,8 +26,8 @@ GEM http-cookie (~> 1.0.0) faraday_middleware (0.13.1) faraday (>= 0.7.4, < 1.0) - fastimage (2.1.5) - fastlane (2.129.0) + fastimage (2.1.7) + fastlane (2.131.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.3, < 3.0.0) babosa (>= 1.0.2, < 2.0.0) @@ -73,9 +73,9 @@ GEM representable (~> 3.0) retriable (>= 2.0, < 4.0) signet (~> 0.9) - google-cloud-core (1.3.0) + google-cloud-core (1.3.1) google-cloud-env (~> 1.0) - google-cloud-env (1.2.0) + google-cloud-env (1.2.1) faraday (~> 0.11) google-cloud-storage (1.16.0) digest-crc (~> 0.4) @@ -96,9 +96,9 @@ GEM json (2.2.0) jwt (2.1.0) memoist (0.16.0) - mime-types (3.2.2) + mime-types (3.3) mime-types-data (~> 3.2015) - mime-types-data (3.2019.0331) + mime-types-data (3.2019.0904) mini_magick (4.9.5) multi_json (1.13.1) multi_xml (0.6.0) @@ -114,14 +114,14 @@ GEM uber (< 0.2.0) retriable (3.1.2) rouge (2.0.7) - rubyzip (1.2.3) + rubyzip (1.2.4) security (0.1.3) signet (0.11.0) addressable (~> 2.3) faraday (~> 0.9) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - simctl (1.6.5) + simctl (1.6.6) CFPropertyList naturally slack-notifier (2.3.2) From 8344e7f94bdf4ed46cc4337c1fea6704d7051224 Mon Sep 17 00:00:00 2001 From: Victor Vicari Date: Tue, 24 Sep 2019 11:04:02 -0300 Subject: [PATCH 10/20] #34 Change use of deprecated NetworkInfo enhancement (#38) * replace NetworkInto with the ConnectivityManager. Since this network validation is being done at repository level, I've had to add the network dependency on it's own AndroidManifest. * moved network_state_permission from repository manifest to app's manifest. removed wildcard imports. now, network_info it's been provided and not the connectivity_manager --- app/src/main/AndroidManifest.xml | 1 + core/src/main/kotlin/com/melih/core/di/CoreModule.kt | 8 ++++++-- repository/src/main/AndroidManifest.xml | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d905a0d..6013c48 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,6 +4,7 @@ package="com.melih.rocketscience"> + + From 83e39400a97efdcbdfaf3fbe9607027e3fea04ff Mon Sep 17 00:00:00 2001 From: Melih Aksoy Date: Thu, 26 Sep 2019 17:23:53 +0200 Subject: [PATCH 11/20] Feature/core repository separation (#44) * Fixed wrong package name. Fixes #43 * Updated package name. Fixes #41 * Ignoring not-used warning - this graph is included in main graph * Separated repository from core. Fixes #42 * Removing adapter from recyclerView when view is destroyed to prevent leak. Fixes #40 * Updated new module graph Signed-off-by: Melih Aksoy --- app/build.gradle | 1 + .../com/melih/rocketscience/di/AppModule.kt | 2 +- core/build.gradle | 4 +-- docs/module_graph.png | Bin 39188 -> 51442 bytes features/detail/build.gradle | 2 ++ .../com/melih/detail/di/DetailContributor.kt | 2 +- .../detail/di/scopes/DetailFragmentScope.kt | 2 +- .../com/melih/detail/di/scopes/DetailScope.kt | 2 +- features/launches/build.gradle | 2 ++ .../launches/src/main/AndroidManifest.xml | 2 +- .../di/LaunchesContributor.kt | 8 +++--- .../di/modules/LaunchesFragmentModule.kt | 4 +-- .../di/scopes/LaunchesFragmentScope.kt | 2 +- .../di/scopes/LaunchesScope.kt | 2 +- .../{list => launches}/ui/LaunchesFragment.kt | 15 ++++++++---- .../ui/adapters/LaunchesAdapter.kt | 4 +-- .../ui/paging/LaunchesPagingSource.kt | 2 +- .../ui/paging/LaunchesPagingSourceFactory.kt | 2 +- .../ui/vm/LaunchesViewModel.kt | 4 +-- .../src/main/res/layout/fragment_launches.xml | 2 +- .../src/main/res/navigation/nav_launches.xml | 23 +++++++++--------- .../BaseTestWithMainThread.kt | 3 ++- 22 files changed, 50 insertions(+), 40 deletions(-) rename features/launches/src/main/kotlin/com/melih/{list => launches}/di/LaunchesContributor.kt (67%) rename features/launches/src/main/kotlin/com/melih/{list => launches}/di/modules/LaunchesFragmentModule.kt (91%) rename features/launches/src/main/kotlin/com/melih/{list => launches}/di/scopes/LaunchesFragmentScope.kt (75%) rename features/launches/src/main/kotlin/com/melih/{list => launches}/di/scopes/LaunchesScope.kt (73%) rename features/launches/src/main/kotlin/com/melih/{list => launches}/ui/LaunchesFragment.kt (87%) rename features/launches/src/main/kotlin/com/melih/{list => launches}/ui/adapters/LaunchesAdapter.kt (92%) rename features/launches/src/main/kotlin/com/melih/{list => launches}/ui/paging/LaunchesPagingSource.kt (95%) rename features/launches/src/main/kotlin/com/melih/{list => launches}/ui/paging/LaunchesPagingSourceFactory.kt (93%) rename features/launches/src/main/kotlin/com/melih/{list => launches}/ui/vm/LaunchesViewModel.kt (87%) rename features/launches/src/test/kotlin/com/melih/{list => launches}/BaseTestWithMainThread.kt (89%) diff --git a/app/build.gradle b/app/build.gradle index eac4a55..4eb083f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -32,6 +32,7 @@ dependencies { androidTestImplementation testLibraries.espresso // These libraries required by dagger to create dependency graph, but not by app + compileOnly project(':repository') compileOnly libraries.retrofit compileOnly libraries.room compileOnly libraries.paging diff --git a/app/src/main/kotlin/com/melih/rocketscience/di/AppModule.kt b/app/src/main/kotlin/com/melih/rocketscience/di/AppModule.kt index 2cc3b52..65eabe5 100644 --- a/app/src/main/kotlin/com/melih/rocketscience/di/AppModule.kt +++ b/app/src/main/kotlin/com/melih/rocketscience/di/AppModule.kt @@ -1,7 +1,7 @@ package com.melih.rocketscience.di import com.melih.detail.di.DetailContributor -import com.melih.list.di.LaunchesContributor +import com.melih.launches.di.LaunchesContributor import com.melih.rocketscience.MainActivity import dagger.Module import dagger.android.ContributesAndroidInjector diff --git a/core/build.gradle b/core/build.gradle index 96de2b7..ef8cb83 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -14,7 +14,7 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - api project(":repository") + implementation project(':repository') implementation libraries.fragment implementation libraries.paging @@ -28,6 +28,4 @@ dependencies { testImplementation testLibraries.mockk testImplementation testLibraries.kluent testImplementation testLibraries.coroutinesTest - - compileOnly libraries.room } diff --git a/docs/module_graph.png b/docs/module_graph.png index 93bab7997822958c62652e946459c980b4d6a2c1..7add4b6e609205841326756ede652bfcf4d45178 100644 GIT binary patch literal 51442 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 F{{!l18Mpud literal 39188 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 diff --git a/features/detail/build.gradle b/features/detail/build.gradle index cea8317..43a02bd 100644 --- a/features/detail/build.gradle +++ b/features/detail/build.gradle @@ -14,5 +14,7 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation project(':repository') + testImplementation testLibraries.coroutinesTest } diff --git a/features/detail/src/main/kotlin/com/melih/detail/di/DetailContributor.kt b/features/detail/src/main/kotlin/com/melih/detail/di/DetailContributor.kt index 6a648c9..ad1b7fa 100644 --- a/features/detail/src/main/kotlin/com/melih/detail/di/DetailContributor.kt +++ b/features/detail/src/main/kotlin/com/melih/detail/di/DetailContributor.kt @@ -1,8 +1,8 @@ package com.melih.detail.di import com.melih.detail.di.modules.DetailFragmentModule +import com.melih.detail.di.scopes.DetailFragmentScope import com.melih.detail.ui.DetailFragment -import com.melih.list.di.scopes.DetailFragmentScope import dagger.Module import dagger.android.ContributesAndroidInjector diff --git a/features/detail/src/main/kotlin/com/melih/detail/di/scopes/DetailFragmentScope.kt b/features/detail/src/main/kotlin/com/melih/detail/di/scopes/DetailFragmentScope.kt index c5a983b..61e38e2 100644 --- a/features/detail/src/main/kotlin/com/melih/detail/di/scopes/DetailFragmentScope.kt +++ b/features/detail/src/main/kotlin/com/melih/detail/di/scopes/DetailFragmentScope.kt @@ -1,4 +1,4 @@ -package com.melih.list.di.scopes +package com.melih.detail.di.scopes import javax.inject.Scope diff --git a/features/detail/src/main/kotlin/com/melih/detail/di/scopes/DetailScope.kt b/features/detail/src/main/kotlin/com/melih/detail/di/scopes/DetailScope.kt index e264fde..93d96b0 100644 --- a/features/detail/src/main/kotlin/com/melih/detail/di/scopes/DetailScope.kt +++ b/features/detail/src/main/kotlin/com/melih/detail/di/scopes/DetailScope.kt @@ -1,4 +1,4 @@ -package com.melih.list.di.scopes +package com.melih.detail.di.scopes import javax.inject.Scope diff --git a/features/launches/build.gradle b/features/launches/build.gradle index 740d414..78a3446 100644 --- a/features/launches/build.gradle +++ b/features/launches/build.gradle @@ -7,6 +7,8 @@ apply from: "$rootProject.projectDir/scripts/feature_module.gradle" dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation project(':repository') + implementation libraries.paging implementation libraries.swipeRefreshLayout diff --git a/features/launches/src/main/AndroidManifest.xml b/features/launches/src/main/AndroidManifest.xml index c857a13..01b8e2a 100644 --- a/features/launches/src/main/AndroidManifest.xml +++ b/features/launches/src/main/AndroidManifest.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/features/launches/src/main/kotlin/com/melih/list/di/LaunchesContributor.kt b/features/launches/src/main/kotlin/com/melih/launches/di/LaunchesContributor.kt similarity index 67% rename from features/launches/src/main/kotlin/com/melih/list/di/LaunchesContributor.kt rename to features/launches/src/main/kotlin/com/melih/launches/di/LaunchesContributor.kt index 7de0829..a69725e 100644 --- a/features/launches/src/main/kotlin/com/melih/list/di/LaunchesContributor.kt +++ b/features/launches/src/main/kotlin/com/melih/launches/di/LaunchesContributor.kt @@ -1,8 +1,8 @@ -package com.melih.list.di +package com.melih.launches.di -import com.melih.list.di.modules.LaunchesFragmentModule -import com.melih.list.di.scopes.LaunchesFragmentScope -import com.melih.list.ui.LaunchesFragment +import com.melih.launches.di.modules.LaunchesFragmentModule +import com.melih.launches.di.scopes.LaunchesFragmentScope +import com.melih.launches.ui.LaunchesFragment import dagger.Module import dagger.android.ContributesAndroidInjector diff --git a/features/launches/src/main/kotlin/com/melih/list/di/modules/LaunchesFragmentModule.kt b/features/launches/src/main/kotlin/com/melih/launches/di/modules/LaunchesFragmentModule.kt similarity index 91% rename from features/launches/src/main/kotlin/com/melih/list/di/modules/LaunchesFragmentModule.kt rename to features/launches/src/main/kotlin/com/melih/launches/di/modules/LaunchesFragmentModule.kt index fc47acb..207eaf3 100644 --- a/features/launches/src/main/kotlin/com/melih/list/di/modules/LaunchesFragmentModule.kt +++ b/features/launches/src/main/kotlin/com/melih/launches/di/modules/LaunchesFragmentModule.kt @@ -1,9 +1,9 @@ -package com.melih.list.di.modules +package com.melih.launches.di.modules import androidx.lifecycle.ViewModel import androidx.paging.Config import com.melih.core.di.keys.ViewModelKey -import com.melih.list.ui.vm.LaunchesViewModel +import com.melih.launches.ui.vm.LaunchesViewModel import com.melih.repository.interactors.DEFAULT_LAUNCHES_AMOUNT import com.melih.repository.interactors.GetLaunches import dagger.Binds diff --git a/features/launches/src/main/kotlin/com/melih/list/di/scopes/LaunchesFragmentScope.kt b/features/launches/src/main/kotlin/com/melih/launches/di/scopes/LaunchesFragmentScope.kt similarity index 75% rename from features/launches/src/main/kotlin/com/melih/list/di/scopes/LaunchesFragmentScope.kt rename to features/launches/src/main/kotlin/com/melih/launches/di/scopes/LaunchesFragmentScope.kt index e5b898b..eb5898f 100644 --- a/features/launches/src/main/kotlin/com/melih/list/di/scopes/LaunchesFragmentScope.kt +++ b/features/launches/src/main/kotlin/com/melih/launches/di/scopes/LaunchesFragmentScope.kt @@ -1,4 +1,4 @@ -package com.melih.list.di.scopes +package com.melih.launches.di.scopes import javax.inject.Scope diff --git a/features/launches/src/main/kotlin/com/melih/list/di/scopes/LaunchesScope.kt b/features/launches/src/main/kotlin/com/melih/launches/di/scopes/LaunchesScope.kt similarity index 73% rename from features/launches/src/main/kotlin/com/melih/list/di/scopes/LaunchesScope.kt rename to features/launches/src/main/kotlin/com/melih/launches/di/scopes/LaunchesScope.kt index 31b4887..24baafb 100644 --- a/features/launches/src/main/kotlin/com/melih/list/di/scopes/LaunchesScope.kt +++ b/features/launches/src/main/kotlin/com/melih/launches/di/scopes/LaunchesScope.kt @@ -1,4 +1,4 @@ -package com.melih.list.di.scopes +package com.melih.launches.di.scopes import javax.inject.Scope diff --git a/features/launches/src/main/kotlin/com/melih/list/ui/LaunchesFragment.kt b/features/launches/src/main/kotlin/com/melih/launches/ui/LaunchesFragment.kt similarity index 87% rename from features/launches/src/main/kotlin/com/melih/list/ui/LaunchesFragment.kt rename to features/launches/src/main/kotlin/com/melih/launches/ui/LaunchesFragment.kt index c4e02e7..e29d29c 100644 --- a/features/launches/src/main/kotlin/com/melih/list/ui/LaunchesFragment.kt +++ b/features/launches/src/main/kotlin/com/melih/launches/ui/LaunchesFragment.kt @@ -1,4 +1,4 @@ -package com.melih.list.ui +package com.melih.launches.ui import android.os.Bundle import android.view.View @@ -7,10 +7,10 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import com.melih.core.actions.openDetail import com.melih.core.base.lifecycle.BaseDaggerFragment import com.melih.core.extensions.observe -import com.melih.list.R -import com.melih.list.databinding.ListBinding -import com.melih.list.ui.adapters.LaunchesAdapter -import com.melih.list.ui.vm.LaunchesViewModel +import com.melih.launches.R +import com.melih.launches.databinding.ListBinding +import com.melih.launches.ui.adapters.LaunchesAdapter +import com.melih.launches.ui.vm.LaunchesViewModel import com.melih.repository.entities.LaunchEntity import com.melih.repository.interactors.base.PersistenceEmpty import com.melih.repository.interactors.base.State @@ -49,6 +49,11 @@ class LaunchesFragment : BaseDaggerFragment(), SwipeRefreshLayout.O // Workaround for SwipeRefreshLayout leak -> https://issuetracker.google.com/issues/136153683 binding.swipeRefreshLayout.isEnabled = false } + + override fun onDestroyView() { + super.onDestroyView() + binding.rocketList.adapter = null + } //endregion //region Functions diff --git a/features/launches/src/main/kotlin/com/melih/list/ui/adapters/LaunchesAdapter.kt b/features/launches/src/main/kotlin/com/melih/launches/ui/adapters/LaunchesAdapter.kt similarity index 92% rename from features/launches/src/main/kotlin/com/melih/list/ui/adapters/LaunchesAdapter.kt rename to features/launches/src/main/kotlin/com/melih/launches/ui/adapters/LaunchesAdapter.kt index d759333..d9e88ec 100644 --- a/features/launches/src/main/kotlin/com/melih/list/ui/adapters/LaunchesAdapter.kt +++ b/features/launches/src/main/kotlin/com/melih/launches/ui/adapters/LaunchesAdapter.kt @@ -1,11 +1,11 @@ -package com.melih.list.ui.adapters +package com.melih.launches.ui.adapters import android.view.LayoutInflater import android.view.ViewGroup import com.melih.core.base.recycler.BasePagingListAdapter import com.melih.core.base.recycler.BaseViewHolder import com.melih.core.extensions.createDiffCallback -import com.melih.list.databinding.LaunchRowBinding +import com.melih.launches.databinding.LaunchRowBinding import com.melih.repository.entities.LaunchEntity class LaunchesAdapter(itemClickListener: (LaunchEntity) -> Unit) : BasePagingListAdapter( diff --git a/features/launches/src/main/kotlin/com/melih/list/ui/paging/LaunchesPagingSource.kt b/features/launches/src/main/kotlin/com/melih/launches/ui/paging/LaunchesPagingSource.kt similarity index 95% rename from features/launches/src/main/kotlin/com/melih/list/ui/paging/LaunchesPagingSource.kt rename to features/launches/src/main/kotlin/com/melih/launches/ui/paging/LaunchesPagingSource.kt index a234c44..6dc3056 100644 --- a/features/launches/src/main/kotlin/com/melih/list/ui/paging/LaunchesPagingSource.kt +++ b/features/launches/src/main/kotlin/com/melih/launches/ui/paging/LaunchesPagingSource.kt @@ -1,4 +1,4 @@ -package com.melih.list.ui.paging +package com.melih.launches.ui.paging import com.melih.core.base.paging.BasePagingDataSource import com.melih.repository.entities.LaunchEntity diff --git a/features/launches/src/main/kotlin/com/melih/list/ui/paging/LaunchesPagingSourceFactory.kt b/features/launches/src/main/kotlin/com/melih/launches/ui/paging/LaunchesPagingSourceFactory.kt similarity index 93% rename from features/launches/src/main/kotlin/com/melih/list/ui/paging/LaunchesPagingSourceFactory.kt rename to features/launches/src/main/kotlin/com/melih/launches/ui/paging/LaunchesPagingSourceFactory.kt index f9f7c46..98b3485 100644 --- a/features/launches/src/main/kotlin/com/melih/list/ui/paging/LaunchesPagingSourceFactory.kt +++ b/features/launches/src/main/kotlin/com/melih/launches/ui/paging/LaunchesPagingSourceFactory.kt @@ -1,4 +1,4 @@ -package com.melih.list.ui.paging +package com.melih.launches.ui.paging import com.melih.core.base.paging.BasePagingDataSource import com.melih.core.base.paging.BasePagingFactory diff --git a/features/launches/src/main/kotlin/com/melih/list/ui/vm/LaunchesViewModel.kt b/features/launches/src/main/kotlin/com/melih/launches/ui/vm/LaunchesViewModel.kt similarity index 87% rename from features/launches/src/main/kotlin/com/melih/list/ui/vm/LaunchesViewModel.kt rename to features/launches/src/main/kotlin/com/melih/launches/ui/vm/LaunchesViewModel.kt index fc45b4c..39133a7 100644 --- a/features/launches/src/main/kotlin/com/melih/list/ui/vm/LaunchesViewModel.kt +++ b/features/launches/src/main/kotlin/com/melih/launches/ui/vm/LaunchesViewModel.kt @@ -1,9 +1,9 @@ -package com.melih.list.ui.vm +package com.melih.launches.ui.vm import androidx.paging.PagedList import com.melih.core.base.paging.BasePagingFactory import com.melih.core.base.viewmodel.BasePagingViewModel -import com.melih.list.ui.paging.LaunchesPagingSourceFactory +import com.melih.launches.ui.paging.LaunchesPagingSourceFactory import com.melih.repository.entities.LaunchEntity import javax.inject.Inject diff --git a/features/launches/src/main/res/layout/fragment_launches.xml b/features/launches/src/main/res/layout/fragment_launches.xml index dce7104..f9f368d 100644 --- a/features/launches/src/main/res/layout/fragment_launches.xml +++ b/features/launches/src/main/res/layout/fragment_launches.xml @@ -8,7 +8,7 @@ + type="com.melih.launches.ui.vm.LaunchesViewModel" /> + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/nav_launches" + app:startDestination="@id/launchesFragment" + tools:ignore="UnusedNavigation"> - - \ No newline at end of file + + diff --git a/features/launches/src/test/kotlin/com/melih/list/BaseTestWithMainThread.kt b/features/launches/src/test/kotlin/com/melih/launches/BaseTestWithMainThread.kt similarity index 89% rename from features/launches/src/test/kotlin/com/melih/list/BaseTestWithMainThread.kt rename to features/launches/src/test/kotlin/com/melih/launches/BaseTestWithMainThread.kt index 358b7d3..c04f0ac 100644 --- a/features/launches/src/test/kotlin/com/melih/list/BaseTestWithMainThread.kt +++ b/features/launches/src/test/kotlin/com/melih/launches/BaseTestWithMainThread.kt @@ -1,6 +1,6 @@ @file:UseExperimental(ExperimentalCoroutinesApi::class) -package com.melih.list +package com.melih.launches import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -10,6 +10,7 @@ import kotlinx.coroutines.test.setMain import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach +@UseExperimental(ExperimentalCoroutinesApi::class) abstract class BaseTestWithMainThread { protected val dispatcher = TestCoroutineDispatcher() From 88022629e1c7eb8dc52da46ad3f8858791d40068 Mon Sep 17 00:00:00 2001 From: Melih Aksoy Date: Wed, 30 Oct 2019 17:27:53 +0100 Subject: [PATCH 12/20] WIP: feature/abstractions (#45) * Abstraction layer backup * Removed DataEntity, was unnecessary for now * Separated network, persistence, entities and interaction, closes #29 * Renamed binding * Removed build files, example tests Removed build files, example tests * Fixed build files were not being ignored all around app * Updated CI ymls * Small changes * Fixed legacy repository package names * Fixed CQ findings * Updated Fastlane * Packaging changes and version upgrades * Removed core from interactors * Version bumps * Added new module graph --- .circleci/config.yml | 56 +++++- .github/workflows/android.yml | 30 +++- .gitignore | 1 + Gemfile.lock | 16 +- {core => abstractions}/.gitignore | 0 abstractions/build.gradle | 17 ++ .../consumer-rules.pro | 0 abstractions/proguard-rules.pro | 21 +++ abstractions/src/main/AndroidManifest.xml | 3 + .../com/melih/abstractions/data/ViewEntity.kt | 3 + .../melih/abstractions/deliverable/Reason.kt | 6 + .../melih/abstractions/deliverable}/Result.kt | 13 +- .../com/melih/abstractions/mapper/Mapper.kt | 8 + .../com/melih/abstractions}/ResultTest.kt | 18 +- app/build.gradle | 11 +- app/proguard-rules.pro | 5 +- build.gradle | 14 +- core/build.gradle | 4 +- .../melih/core/base/lifecycle/BaseFragment.kt | 2 +- .../core/base/paging/BasePagingDataSource.kt | 26 +-- .../core/base/paging/BasePagingFactory.kt | 3 +- .../base/viewmodel/BasePagingViewModel.kt | 7 +- .../core/base/viewmodel/BaseViewModel.kt | 4 +- .../melih/core/extensions/UtilityExtension.kt | 32 ---- .../core/paging/BasePagingDataSourceTest.kt | 55 +++--- .../core/paging/BasePagingFactoryTest.kt | 8 +- data/definitions/build.gradle | 17 ++ data/definitions/consumer-rules.pro | 0 data/definitions/proguard-rules.pro | 21 +++ data/definitions/src/main/AndroidManifest.xml | 3 + .../com/melih/definitions}/Constants.kt | 2 +- .../kotlin/com/melih/definitions/Source.kt | 23 +++ .../definitions}/entities/LaunchEntity.kt | 4 +- .../definitions}/entities/LaunchesEntity.kt | 2 +- .../definitions}/entities/LocationEntity.kt | 4 +- .../definitions}/entities/MissionEntity.kt | 6 +- .../definitions}/entities/RocketEntity.kt | 6 +- data/interactors/build.gradle | 22 +++ data/interactors/consumer-rules.pro | 0 data/interactors/proguard-rules.pro | 21 +++ data/interactors/src/main/AndroidManifest.xml | 3 + .../com/melih/interactors/GetLaunchDetails.kt | 40 +++++ .../com/melih/interactors/GetLaunches.kt | 44 +++++ .../melih}/interactors/base/BaseInteractor.kt | 4 +- .../error/InteractionErrorReason.kt | 17 ++ .../interactors/sources/LaunchesSource.kt | 163 ++++++++++++++++++ .../src/main/res/values/strings.xml | 5 +- .../interactors/base/BaseInteractorTest.kt | 7 +- data/network/build.gradle | 20 +++ data/network/consumer-rules.pro | 0 data/network/proguard-rules.pro | 21 +++ data/network/src/main/AndroidManifest.xml | 3 + .../main/kotlin/com/melih/network/api}/Api.kt | 6 +- .../kotlin/com/melih/network/api}/ApiImpl.kt | 8 +- {repository => data/persistence}/build.gradle | 9 +- data/persistence/consumer-rules.pro | 0 data/persistence/proguard-rules.pro | 21 +++ data/persistence/src/main/AndroidManifest.xml | 3 + .../melih}/persistence/LaunchesDatabase.kt | 16 +- .../persistence/converters/BaseConverter.kt | 2 +- .../converters/BaseListConverter.kt | 2 +- .../converters/LocationConverter.kt | 6 +- .../converters/MissionConverter.kt | 4 +- .../persistence/converters/RocketConverter.kt | 6 +- .../com/melih}/persistence/dao/LaunchesDao.kt | 6 +- docs/module_graph.png | Bin 51442 -> 129138 bytes fastlane/Fastfile | 46 ++++- features/detail/build.gradle | 2 - .../com/melih/detail/data/LaunchDetailItem.kt | 10 ++ .../melih/detail/data/LaunchDetailMapper.kt | 18 ++ .../detail/di/modules/DetailFragmentModule.kt | 9 +- .../com/melih/detail/ui/DetailViewModel.kt | 21 +-- .../src/main/res/layout/fragment_detail.xml | 120 ++++++------- .../com/melih/detail/DetailViewModelTest.kt | 5 +- features/launches/build.gradle | 2 - .../com/melih/launches/data/LaunchItem.kt | 10 ++ .../com/melih/launches/data/LaunchMapper.kt | 18 ++ .../di/modules/LaunchesFragmentModule.kt | 15 +- .../com/melih/launches/ui/LaunchesFragment.kt | 14 +- .../launches/ui/adapters/LaunchesAdapter.kt | 15 +- .../ui/paging/LaunchesPagingSource.kt | 12 +- .../ui/paging/LaunchesPagingSourceFactory.kt | 6 +- .../melih/launches/ui/vm/LaunchesViewModel.kt | 6 +- .../src/main/res/layout/fragment_launches.xml | 2 +- .../src/main/res/layout/row_launch.xml | 119 ++++++------- repository/.gitignore | 1 - repository/src/main/AndroidManifest.xml | 2 - .../kotlin/com/melih/repository/Repository.kt | 16 -- .../interactors/GetLaunchDetails.kt | 55 ------ .../repository/interactors/GetLaunches.kt | 54 ------ .../repository/interactors/base/Reason.kt | 21 --- .../melih/repository/sources/NetworkSource.kt | 118 ------------- .../repository/sources/PersistenceSource.kt | 50 ------ .../repository/sources/NetworkSourceTest.kt | 104 ----------- .../sources/PersistanceSourceTest.kt | 73 -------- scripts/{ => cq}/detekt.gradle | 0 scripts/{ => cq}/dokka.gradle | 0 scripts/cq/jacoco.gradle | 53 ++++++ scripts/default_android_config.gradle | 4 +- scripts/default_dependencies.gradle | 59 +------ scripts/dependencies.gradle | 30 ++-- scripts/feature_module.gradle | 4 + settings.gradle | 13 +- 103 files changed, 1097 insertions(+), 920 deletions(-) rename {core => abstractions}/.gitignore (100%) create mode 100644 abstractions/build.gradle rename repository/proguard-rules.pro => abstractions/consumer-rules.pro (100%) create mode 100644 abstractions/proguard-rules.pro create mode 100644 abstractions/src/main/AndroidManifest.xml create mode 100644 abstractions/src/main/kotlin/com/melih/abstractions/data/ViewEntity.kt create mode 100644 abstractions/src/main/kotlin/com/melih/abstractions/deliverable/Reason.kt rename {repository/src/main/kotlin/com/melih/repository/interactors/base => abstractions/src/main/kotlin/com/melih/abstractions/deliverable}/Result.kt (70%) create mode 100644 abstractions/src/main/kotlin/com/melih/abstractions/mapper/Mapper.kt rename {repository/src/test/kotlin/com/melih/repository/interactors/base => abstractions/src/test/kotlin/com/melih/abstractions}/ResultTest.kt (80%) delete mode 100644 core/src/main/kotlin/com/melih/core/extensions/UtilityExtension.kt create mode 100644 data/definitions/build.gradle create mode 100644 data/definitions/consumer-rules.pro create mode 100644 data/definitions/proguard-rules.pro create mode 100644 data/definitions/src/main/AndroidManifest.xml rename {repository/src/main/kotlin/com/melih/repository => data/definitions/src/main/kotlin/com/melih/definitions}/Constants.kt (74%) create mode 100644 data/definitions/src/main/kotlin/com/melih/definitions/Source.kt rename {repository/src/main/kotlin/com/melih/repository => data/definitions/src/main/kotlin/com/melih/definitions}/entities/LaunchEntity.kt (87%) rename {repository/src/main/kotlin/com/melih/repository => data/definitions/src/main/kotlin/com/melih/definitions}/entities/LaunchesEntity.kt (86%) rename {repository/src/main/kotlin/com/melih/repository => data/definitions/src/main/kotlin/com/melih/definitions}/entities/LocationEntity.kt (87%) rename {repository/src/main/kotlin/com/melih/repository => data/definitions/src/main/kotlin/com/melih/definitions}/entities/MissionEntity.kt (73%) rename {repository/src/main/kotlin/com/melih/repository => data/definitions/src/main/kotlin/com/melih/definitions}/entities/RocketEntity.kt (78%) create mode 100644 data/interactors/build.gradle create mode 100644 data/interactors/consumer-rules.pro create mode 100644 data/interactors/proguard-rules.pro create mode 100644 data/interactors/src/main/AndroidManifest.xml create mode 100644 data/interactors/src/main/kotlin/com/melih/interactors/GetLaunchDetails.kt create mode 100644 data/interactors/src/main/kotlin/com/melih/interactors/GetLaunches.kt rename {repository/src/main/kotlin/com/melih/repository => data/interactors/src/main/kotlin/com/melih}/interactors/base/BaseInteractor.kt (88%) create mode 100644 data/interactors/src/main/kotlin/com/melih/interactors/error/InteractionErrorReason.kt create mode 100644 data/interactors/src/main/kotlin/com/melih/interactors/sources/LaunchesSource.kt rename {repository => data/interactors}/src/main/res/values/strings.xml (81%) rename {repository/src/test/kotlin/com/melih/repository => data/interactors/src/test/kotlin/com/melih}/interactors/base/BaseInteractorTest.kt (91%) create mode 100644 data/network/build.gradle create mode 100644 data/network/consumer-rules.pro create mode 100644 data/network/proguard-rules.pro create mode 100644 data/network/src/main/AndroidManifest.xml rename {repository/src/main/kotlin/com/melih/repository/network => data/network/src/main/kotlin/com/melih/network/api}/Api.kt (78%) rename {repository/src/main/kotlin/com/melih/repository/network => data/network/src/main/kotlin/com/melih/network/api}/ApiImpl.kt (88%) rename {repository => data/persistence}/build.gradle (82%) create mode 100644 data/persistence/consumer-rules.pro create mode 100644 data/persistence/proguard-rules.pro create mode 100644 data/persistence/src/main/AndroidManifest.xml rename {repository/src/main/kotlin/com/melih/repository => data/persistence/src/main/kotlin/com/melih}/persistence/LaunchesDatabase.kt (65%) rename {repository/src/main/kotlin/com/melih/repository => data/persistence/src/main/kotlin/com/melih}/persistence/converters/BaseConverter.kt (93%) rename {repository/src/main/kotlin/com/melih/repository => data/persistence/src/main/kotlin/com/melih}/persistence/converters/BaseListConverter.kt (93%) rename {repository/src/main/kotlin/com/melih/repository => data/persistence/src/main/kotlin/com/melih}/persistence/converters/LocationConverter.kt (63%) rename {repository/src/main/kotlin/com/melih/repository => data/persistence/src/main/kotlin/com/melih}/persistence/converters/MissionConverter.kt (81%) rename {repository/src/main/kotlin/com/melih/repository => data/persistence/src/main/kotlin/com/melih}/persistence/converters/RocketConverter.kt (63%) rename {repository/src/main/kotlin/com/melih/repository => data/persistence/src/main/kotlin/com/melih}/persistence/dao/LaunchesDao.kt (86%) create mode 100644 features/detail/src/main/kotlin/com/melih/detail/data/LaunchDetailItem.kt create mode 100644 features/detail/src/main/kotlin/com/melih/detail/data/LaunchDetailMapper.kt create mode 100644 features/launches/src/main/kotlin/com/melih/launches/data/LaunchItem.kt create mode 100644 features/launches/src/main/kotlin/com/melih/launches/data/LaunchMapper.kt delete mode 100644 repository/.gitignore delete mode 100644 repository/src/main/AndroidManifest.xml delete mode 100644 repository/src/main/kotlin/com/melih/repository/Repository.kt delete mode 100644 repository/src/main/kotlin/com/melih/repository/interactors/GetLaunchDetails.kt delete mode 100644 repository/src/main/kotlin/com/melih/repository/interactors/GetLaunches.kt delete mode 100644 repository/src/main/kotlin/com/melih/repository/interactors/base/Reason.kt delete mode 100644 repository/src/main/kotlin/com/melih/repository/sources/NetworkSource.kt delete mode 100644 repository/src/main/kotlin/com/melih/repository/sources/PersistenceSource.kt delete mode 100644 repository/src/test/kotlin/com/melih/repository/sources/NetworkSourceTest.kt delete mode 100644 repository/src/test/kotlin/com/melih/repository/sources/PersistanceSourceTest.kt rename scripts/{ => cq}/detekt.gradle (100%) rename scripts/{ => cq}/dokka.gradle (100%) create mode 100644 scripts/cq/jacoco.gradle diff --git a/.circleci/config.yml b/.circleci/config.yml index edbe35c..e4542dd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -76,12 +76,60 @@ jobs: - store_test_results: # for display in Test Summary: https://circleci.com/docs/2.0/collect-test-data/ path: features/detail/build/test-results - ## Repository + ## Abstractions - run: - name: Test Repository + name: Test Abstractions command: | - fastlane test_repository - ./gradlew repository:jacocoTestReport + fastlane test_abstractions + ./gradlew abstractions:jacocoTestReport + bash <(curl -s https://codecov.io/bash) + - store_artifacts: # for display in Artifacts: https://circleci.com/docs/2.0/artifacts/ + path: repository/build/reports/tests + - store_test_results: # for display in Test Summary: https://circleci.com/docs/2.0/collect-test-data/ + path: repository/build/test-results + + ## Definitions + - run: + name: Test Definitions + command: | + fastlane test_definitions + ./gradlew data:definitions:jacocoTestReport + bash <(curl -s https://codecov.io/bash) + - store_artifacts: # for display in Artifacts: https://circleci.com/docs/2.0/artifacts/ + path: repository/build/reports/tests + - store_test_results: # for display in Test Summary: https://circleci.com/docs/2.0/collect-test-data/ + path: repository/build/test-results + + ## Interactors + - run: + name: Test Interactors + command: | + fastlane test_interactors + ./gradlew data:interactors:jacocoTestReport + bash <(curl -s https://codecov.io/bash) + - store_artifacts: # for display in Artifacts: https://circleci.com/docs/2.0/artifacts/ + path: repository/build/reports/tests + - store_test_results: # for display in Test Summary: https://circleci.com/docs/2.0/collect-test-data/ + path: repository/build/test-results + + ## Network + - run: + name: Test Network + command: | + fastlane test_network + ./gradlew data:network:jacocoTestReport + bash <(curl -s https://codecov.io/bash) + - store_artifacts: # for display in Artifacts: https://circleci.com/docs/2.0/artifacts/ + path: repository/build/reports/tests + - store_test_results: # for display in Test Summary: https://circleci.com/docs/2.0/collect-test-data/ + path: repository/build/test-results + + ## Persistence + - run: + name: Test Persistence + command: | + fastlane test_persistence + ./gradlew data:persistence:jacocoTestReport bash <(curl -s https://codecov.io/bash) - store_artifacts: # for display in Artifacts: https://circleci.com/docs/2.0/artifacts/ path: repository/build/reports/tests diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 0972801..13de4f6 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -55,8 +55,32 @@ jobs: ./gradlew features:detail:jacocoTestReport bash <(curl -s https://codecov.io/bash) - - name: Test Repository + - name: Test Abstractions run: | - fastlane test_repository - ./gradlew repository:jacocoTestReport + fastlane test_abstractions + ./gradlew abstractions:jacocoTestReport bash <(curl -s https://codecov.io/bash) + + - name: Test Definitions + run: | + fastlane test_definitions + ./gradlew data:definitions:jacocoTestReport + bash <(curl -s https://codecov.io/bash) + + - name: Test Interactors + run: | + fastlane test_interactors + ./gradlew data:interactors:jacocoTestReport + bash <(curl -s https://codecov.io/bash) + + - name: Test Network + run: | + fastlane test_network + ./gradlew data:network:jacocoTestReport + bash <(curl -s https://codecov.io/bash) + + - name: Test Persistence + run: | + fastlane test_persistence + ./gradlew data:persistence:jacocoTestReport + bash <(curl -s https://codecov.io/bash) \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3a8ee31..4cdb0a4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /.idea .DS_Store /build +**/build /fastlane/README.md /fastlane/report.xml /captures diff --git a/Gemfile.lock b/Gemfile.lock index b9db40d..192c06b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -5,7 +5,7 @@ GEM addressable (2.7.0) public_suffix (>= 2.0.2, < 5.0) atomos (0.1.3) - babosa (1.0.2) + babosa (1.0.3) claide (1.0.3) colored (1.2) colored2 (3.1.2) @@ -18,7 +18,7 @@ GEM unf (>= 0.0.5, < 1.0.0) dotenv (2.7.5) emoji_regex (1.0.1) - excon (0.66.0) + excon (0.67.0) faraday (0.15.4) multipart-post (>= 1.2, < 3) faraday-cookie_jar (0.0.6) @@ -27,7 +27,7 @@ GEM faraday_middleware (0.13.1) faraday (>= 0.7.4, < 1.0) fastimage (2.1.7) - fastlane (2.131.0) + fastlane (2.133.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.3, < 3.0.0) babosa (>= 1.0.2, < 2.0.0) @@ -37,9 +37,9 @@ GEM dotenv (>= 2.1.1, < 3.0.0) emoji_regex (>= 0.1, < 2.0) excon (>= 0.45.0, < 1.0.0) - faraday (~> 0.9) + faraday (< 0.16.0) faraday-cookie_jar (~> 0.0.6) - faraday_middleware (~> 0.9) + faraday_middleware (< 0.16.0) fastimage (>= 2.1.0, < 3.0.0) gh_inspector (>= 1.1.2, < 2.0.0) google-api-client (>= 0.21.2, < 0.24.0) @@ -52,7 +52,7 @@ GEM multipart-post (~> 2.0.0) plist (>= 3.1.0, < 4.0.0) public_suffix (~> 2.0.0) - rubyzip (>= 1.2.2, < 2.0.0) + rubyzip (>= 1.3.0, < 2.0.0) security (= 0.1.3) simctl (~> 1.6.3) slack-notifier (>= 2.0.0, < 3.0.0) @@ -98,7 +98,7 @@ GEM memoist (0.16.0) mime-types (3.3) mime-types-data (~> 3.2015) - mime-types-data (3.2019.0904) + mime-types-data (3.2019.1009) mini_magick (4.9.5) multi_json (1.13.1) multi_xml (0.6.0) @@ -114,7 +114,7 @@ GEM uber (< 0.2.0) retriable (3.1.2) rouge (2.0.7) - rubyzip (1.2.4) + rubyzip (1.3.0) security (0.1.3) signet (0.11.0) addressable (~> 2.3) diff --git a/core/.gitignore b/abstractions/.gitignore similarity index 100% rename from core/.gitignore rename to abstractions/.gitignore diff --git a/abstractions/build.gradle b/abstractions/build.gradle new file mode 100644 index 0000000..86354c0 --- /dev/null +++ b/abstractions/build.gradle @@ -0,0 +1,17 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: "de.mannodermaus.android-junit5" + +apply from: "$rootProject.projectDir/scripts/module.gradle" + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + implementation libraries.kotlin + + testImplementation testLibraries.jUnitApi + testImplementation testLibraries.mockk + testImplementation testLibraries.kluent + + testRuntimeOnly testLibraries.jUnitEngine +} diff --git a/repository/proguard-rules.pro b/abstractions/consumer-rules.pro similarity index 100% rename from repository/proguard-rules.pro rename to abstractions/consumer-rules.pro diff --git a/abstractions/proguard-rules.pro b/abstractions/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/abstractions/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/abstractions/src/main/AndroidManifest.xml b/abstractions/src/main/AndroidManifest.xml new file mode 100644 index 0000000..c706bc8 --- /dev/null +++ b/abstractions/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + diff --git a/abstractions/src/main/kotlin/com/melih/abstractions/data/ViewEntity.kt b/abstractions/src/main/kotlin/com/melih/abstractions/data/ViewEntity.kt new file mode 100644 index 0000000..6d9668e --- /dev/null +++ b/abstractions/src/main/kotlin/com/melih/abstractions/data/ViewEntity.kt @@ -0,0 +1,3 @@ +package com.melih.abstractions.data + +interface ViewEntity diff --git a/abstractions/src/main/kotlin/com/melih/abstractions/deliverable/Reason.kt b/abstractions/src/main/kotlin/com/melih/abstractions/deliverable/Reason.kt new file mode 100644 index 0000000..030a02e --- /dev/null +++ b/abstractions/src/main/kotlin/com/melih/abstractions/deliverable/Reason.kt @@ -0,0 +1,6 @@ +package com.melih.abstractions.deliverable + +abstract class Reason : Throwable() { + + abstract val messageRes: Int +} diff --git a/repository/src/main/kotlin/com/melih/repository/interactors/base/Result.kt b/abstractions/src/main/kotlin/com/melih/abstractions/deliverable/Result.kt similarity index 70% rename from repository/src/main/kotlin/com/melih/repository/interactors/base/Result.kt rename to abstractions/src/main/kotlin/com/melih/abstractions/deliverable/Result.kt index e34eea7..f81e27e 100644 --- a/repository/src/main/kotlin/com/melih/repository/interactors/base/Result.kt +++ b/abstractions/src/main/kotlin/com/melih/abstractions/deliverable/Result.kt @@ -1,12 +1,9 @@ -package com.melih.repository.interactors.base - -import kotlinx.coroutines.ExperimentalCoroutinesApi +package com.melih.abstractions.deliverable /** - * Result class that wraps any [Success], [Failure] or [State] that can be generated by any derivation of [BaseInteractor] + * Result class that wraps any [Success], [Failure] or [State] */ -@UseExperimental(ExperimentalCoroutinesApi::class) sealed class Result //region Subclasses @@ -22,7 +19,11 @@ sealed class State : Result() { //region Extensions -inline fun Result.handle(stateBlock: (State) -> Unit, failureBlock: (Reason) -> Unit, successBlock: (T) -> Unit) { +inline fun Result.handle( + stateBlock: (State) -> Unit, + failureBlock: (Reason) -> Unit, + successBlock: (T) -> Unit +) { when (this) { is Success -> successBlock(successData) is Failure -> failureBlock(errorData) diff --git a/abstractions/src/main/kotlin/com/melih/abstractions/mapper/Mapper.kt b/abstractions/src/main/kotlin/com/melih/abstractions/mapper/Mapper.kt new file mode 100644 index 0000000..aac38d2 --- /dev/null +++ b/abstractions/src/main/kotlin/com/melih/abstractions/mapper/Mapper.kt @@ -0,0 +1,8 @@ +package com.melih.abstractions.mapper + +import com.melih.abstractions.data.ViewEntity + +abstract class Mapper { + + abstract fun convert(t: T): R +} diff --git a/repository/src/test/kotlin/com/melih/repository/interactors/base/ResultTest.kt b/abstractions/src/test/kotlin/com/melih/abstractions/ResultTest.kt similarity index 80% rename from repository/src/test/kotlin/com/melih/repository/interactors/base/ResultTest.kt rename to abstractions/src/test/kotlin/com/melih/abstractions/ResultTest.kt index cc770df..a21f935 100644 --- a/repository/src/test/kotlin/com/melih/repository/interactors/base/ResultTest.kt +++ b/abstractions/src/test/kotlin/com/melih/abstractions/ResultTest.kt @@ -1,6 +1,10 @@ -package com.melih.repository.interactors.base +package com.melih.abstractions -import com.melih.repository.R +import com.melih.abstractions.deliverable.Failure +import com.melih.abstractions.deliverable.Reason +import com.melih.abstractions.deliverable.State +import com.melih.abstractions.deliverable.Success +import com.melih.abstractions.deliverable.handle import io.mockk.called import io.mockk.spyk import io.mockk.verify @@ -8,12 +12,17 @@ import org.amshove.kluent.shouldBeInstanceOf import org.amshove.kluent.shouldEqualTo import org.junit.jupiter.api.Test + class ResultTest { private val number = 10 private val success = Success(number) - private val failure = Failure(GenericError()) + private val failure = Failure(object : Reason() { + override val messageRes: Int + get() = 10 + + }) private val state = State.Loading() private val emptyStateBlock = spyk({ _: State -> }) @@ -37,8 +46,7 @@ class ResultTest { @Test fun `Failure should only invoke failureBlock with correct error`() { val actualFailureBlock = spyk({ reason: Reason -> - reason shouldBeInstanceOf GenericError::class - (reason as GenericError).messageRes shouldEqualTo R.string.reason_generic + reason.messageRes shouldEqualTo 10 Unit }) diff --git a/app/build.gradle b/app/build.gradle index 4eb083f..4d1af18 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,6 +3,7 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' apply from: "$rootProject.projectDir/scripts/default_android_config.gradle" +apply from: "$rootProject.projectDir/scripts/default_dependencies.gradle" apply from: "$rootProject.projectDir/scripts/sources.gradle" android { @@ -31,10 +32,14 @@ dependencies { androidTestImplementation testLibraries.espresso - // These libraries required by dagger to create dependency graph, but not by app - compileOnly project(':repository') + // These libraries required by dagger to create dependency graph and application compilation, but not used by app + + compileOnly project(':abstractions') + compileOnly project(':data:interactors') + compileOnly project(':data:network') + compileOnly project(':data:definitions') + compileOnly libraries.retrofit - compileOnly libraries.room compileOnly libraries.paging compileOnly libraries.swipeRefreshLayout diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index da2a358..ce0ec64 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -21,8 +21,5 @@ @com.squareup.moshi.ToJson ; } --keepnames @kotlin.Metadata class com.myapp.packagename.model.** --keep class com.myapp.packagnename.model.** { *; } - # Keeping entities intact --keep class com.melih.repository.entities.** { *; } +-keep class com.melih.definitions.entities.** { *; } diff --git a/build.gradle b/build.gradle index 232dd0a..d709464 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { ext.kotlin_version = '1.3.50' - ext.nav_version = '2.2.0-alpha01' + ext.nav_version = '2.2.0-beta01' repositories { google() @@ -9,7 +9,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.5.0' + classpath 'com.android.tools.build:gradle:3.5.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.dokka:dokka-android-gradle-plugin:0.9.18" classpath "de.mannodermaus.gradle.plugins:android-junit5:1.5.1.0" @@ -20,9 +20,9 @@ buildscript { } plugins { - id "io.gitlab.arturbosch.detekt" version "1.0.0" - id "org.jetbrains.dokka" version "0.9.18" - id "jacoco" + id 'io.gitlab.arturbosch.detekt' version '1.1.1' + id 'org.jetbrains.dokka' version '0.9.18' + id 'jacoco' } allprojects { @@ -97,7 +97,9 @@ task projectDependencyGraph { rootProjects.remove(dependency) def graphKey = new Tuple2(project, dependency) - def traits = dependencies.computeIfAbsent(graphKey) { new ArrayList() } + def traits = dependencies.computeIfAbsent(graphKey) { + new ArrayList() + } if (config.name.toLowerCase().endsWith('implementation')) { traits.add('style=dotted') diff --git a/core/build.gradle b/core/build.gradle index ef8cb83..0d9ae40 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -3,6 +3,7 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' apply from: "$rootProject.projectDir/scripts/default_android_config.gradle" +apply from: "$rootProject.projectDir/scripts/default_dependencies.gradle" apply from: "$rootProject.projectDir/scripts/sources.gradle" android { @@ -14,8 +15,7 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation project(':repository') - + implementation libraries.coroutines implementation libraries.fragment implementation libraries.paging implementation libraries.lifecycle diff --git a/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseFragment.kt b/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseFragment.kt index 53e1d0a..368a89e 100644 --- a/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseFragment.kt +++ b/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseFragment.kt @@ -9,8 +9,8 @@ import androidx.databinding.DataBindingUtil import androidx.databinding.ViewDataBinding import androidx.fragment.app.Fragment import com.google.android.material.snackbar.Snackbar +import com.melih.abstractions.deliverable.Reason import com.melih.core.R -import com.melih.repository.interactors.base.Reason /** * Parent of all fragments. diff --git a/core/src/main/kotlin/com/melih/core/base/paging/BasePagingDataSource.kt b/core/src/main/kotlin/com/melih/core/base/paging/BasePagingDataSource.kt index ff1ff69..a004f0e 100644 --- a/core/src/main/kotlin/com/melih/core/base/paging/BasePagingDataSource.kt +++ b/core/src/main/kotlin/com/melih/core/base/paging/BasePagingDataSource.kt @@ -4,12 +4,13 @@ import androidx.annotation.CallSuper import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.paging.PageKeyedDataSource -import com.melih.repository.interactors.base.Reason -import com.melih.repository.interactors.base.Result -import com.melih.repository.interactors.base.State -import com.melih.repository.interactors.base.onFailure -import com.melih.repository.interactors.base.onState -import com.melih.repository.interactors.base.onSuccess +import com.melih.abstractions.data.ViewEntity +import com.melih.abstractions.deliverable.Reason +import com.melih.abstractions.deliverable.Result +import com.melih.abstractions.deliverable.State +import com.melih.abstractions.deliverable.onFailure +import com.melih.abstractions.deliverable.onState +import com.melih.abstractions.deliverable.onSuccess import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -35,11 +36,11 @@ const val INITIAL_PAGE = 0 */ @UseExperimental(ExperimentalCoroutinesApi::class) -abstract class BasePagingDataSource : PageKeyedDataSource() { +abstract class BasePagingDataSource : PageKeyedDataSource() { //region Abstractions - abstract fun loadDataForPage(page: Int): Flow>> // Load next page(s) + abstract fun loadDataForPage(page: Int): Flow>> // Load next page(s) //endregion //region Properties @@ -63,7 +64,10 @@ abstract class BasePagingDataSource : PageKeyedDataSource() { //region Functions - override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback) { + override fun loadInitial( + params: LoadInitialParams, + callback: LoadInitialCallback + ) { // Looping through channel as we'll receive any state, error or data here loadDataForPage(INITIAL_PAGE) .onEach { result -> @@ -81,7 +85,7 @@ abstract class BasePagingDataSource : PageKeyedDataSource() { .launchIn(coroutineScope) } - override fun loadAfter(params: LoadParams, callback: LoadCallback) { + override fun loadAfter(params: LoadParams, callback: LoadCallback) { // Key for which page to load is in params val page = params.key @@ -104,7 +108,7 @@ abstract class BasePagingDataSource : PageKeyedDataSource() { /** * This loads previous pages, we don't have a use for it yet, so it's a no-op override */ - override fun loadBefore(params: LoadParams, callback: LoadCallback) { + override fun loadBefore(params: LoadParams, callback: LoadCallback) { // no-op } diff --git a/core/src/main/kotlin/com/melih/core/base/paging/BasePagingFactory.kt b/core/src/main/kotlin/com/melih/core/base/paging/BasePagingFactory.kt index 1a51f1e..ddd12b6 100644 --- a/core/src/main/kotlin/com/melih/core/base/paging/BasePagingFactory.kt +++ b/core/src/main/kotlin/com/melih/core/base/paging/BasePagingFactory.kt @@ -3,6 +3,7 @@ package com.melih.core.base.paging import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.paging.DataSource +import com.melih.abstractions.data.ViewEntity /** * Base [factory][DataSource.Factory] class for any [dataSource][DataSource]s in project. @@ -14,7 +15,7 @@ import androidx.paging.DataSource * * Purpose of this transmission is to encapuslate [basePagingDataSource][BasePagingDataSource]. */ -abstract class BasePagingFactory : DataSource.Factory() { +abstract class BasePagingFactory : DataSource.Factory() { //region Abstractions diff --git a/core/src/main/kotlin/com/melih/core/base/viewmodel/BasePagingViewModel.kt b/core/src/main/kotlin/com/melih/core/base/viewmodel/BasePagingViewModel.kt index 413a602..7a0caad 100644 --- a/core/src/main/kotlin/com/melih/core/base/viewmodel/BasePagingViewModel.kt +++ b/core/src/main/kotlin/com/melih/core/base/viewmodel/BasePagingViewModel.kt @@ -5,9 +5,10 @@ import androidx.lifecycle.Transformations.switchMap import androidx.lifecycle.ViewModel import androidx.paging.PagedList import androidx.paging.toLiveData +import com.melih.abstractions.data.ViewEntity +import com.melih.abstractions.deliverable.Reason +import com.melih.abstractions.deliverable.State import com.melih.core.base.paging.BasePagingFactory -import com.melih.repository.interactors.base.Reason -import com.melih.repository.interactors.base.State /** * Base [ViewModel] for view models that will use [PagedList]. @@ -18,7 +19,7 @@ import com.melih.repository.interactors.base.State * * If paging won't be used, use [BaseViewModel] instead. */ -abstract class BasePagingViewModel : ViewModel() { +abstract class BasePagingViewModel : ViewModel() { //region Abstractions diff --git a/core/src/main/kotlin/com/melih/core/base/viewmodel/BaseViewModel.kt b/core/src/main/kotlin/com/melih/core/base/viewmodel/BaseViewModel.kt index 6e7c440..525a772 100644 --- a/core/src/main/kotlin/com/melih/core/base/viewmodel/BaseViewModel.kt +++ b/core/src/main/kotlin/com/melih/core/base/viewmodel/BaseViewModel.kt @@ -4,8 +4,8 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.melih.repository.interactors.base.Reason -import com.melih.repository.interactors.base.State +import com.melih.abstractions.deliverable.Reason +import com.melih.abstractions.deliverable.State import kotlinx.coroutines.launch /** diff --git a/core/src/main/kotlin/com/melih/core/extensions/UtilityExtension.kt b/core/src/main/kotlin/com/melih/core/extensions/UtilityExtension.kt deleted file mode 100644 index c57840f..0000000 --- a/core/src/main/kotlin/com/melih/core/extensions/UtilityExtension.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.melih.core.extensions - -import android.view.MenuItem -import androidx.appcompat.widget.SearchView -import com.melih.core.utils.ClearFocusQueryTextListener - -/** - * Shorthand for [contains] with ignoreCase set [true] - */ -fun CharSequence.containsIgnoreCase(other: CharSequence) = contains(other, true) - -/** - * Adds [ClearFocusQueryTextListener] as [SearchView.OnQueryTextListener] - */ -fun SearchView.setOnQueryChangedListener(block: (String?) -> Unit) = setOnQueryTextListener(ClearFocusQueryTextListener(this, block)) - -/** - * Shortening set menu item expands / collapses - */ -fun MenuItem.onExpandOrCollapse(onExpand: () -> Unit, onCollapse: () -> Unit) { - setOnActionExpandListener(object : MenuItem.OnActionExpandListener { - override fun onMenuItemActionCollapse(item: MenuItem?): Boolean { - onCollapse() - return true - } - - override fun onMenuItemActionExpand(item: MenuItem?): Boolean { - onExpand() - return true - } - }) -} diff --git a/core/src/test/kotlin/com/melih/core/paging/BasePagingDataSourceTest.kt b/core/src/test/kotlin/com/melih/core/paging/BasePagingDataSourceTest.kt index 5750ad1..700bbfb 100644 --- a/core/src/test/kotlin/com/melih/core/paging/BasePagingDataSourceTest.kt +++ b/core/src/test/kotlin/com/melih/core/paging/BasePagingDataSourceTest.kt @@ -3,14 +3,15 @@ package com.melih.core.paging import androidx.paging.PageKeyedDataSource +import com.melih.abstractions.data.ViewEntity +import com.melih.abstractions.deliverable.Failure +import com.melih.abstractions.deliverable.Reason +import com.melih.abstractions.deliverable.Result +import com.melih.abstractions.deliverable.State +import com.melih.abstractions.deliverable.Success import com.melih.core.BaseTestWithMainThread import com.melih.core.base.paging.BasePagingDataSource import com.melih.core.testObserve -import com.melih.repository.interactors.base.Failure -import com.melih.repository.interactors.base.GenericError -import com.melih.repository.interactors.base.Result -import com.melih.repository.interactors.base.State -import com.melih.repository.interactors.base.Success import io.mockk.mockk import io.mockk.spyk import io.mockk.verify @@ -28,7 +29,7 @@ class BasePagingDataSourceTest : BaseTestWithMainThread() { val failureSource = spyk(TestFailureSource()) val data = 10 - val errorMessage = "Generic Error" + val errorMessageResId = 1313 @Nested inner class BasePagingSource { @@ -37,10 +38,9 @@ class BasePagingDataSourceTest : BaseTestWithMainThread() { inner class LoadInitial { @Test - fun `should update state accordingly`() { val params = mockk>(relaxed = true) - val callback = mockk>(relaxed = true) + val callback = mockk>(relaxed = true) runBlocking { @@ -54,10 +54,9 @@ class BasePagingDataSourceTest : BaseTestWithMainThread() { } @Test - fun `should update error Error accordingly`() { val params = PageKeyedDataSource.LoadInitialParams(10, false) - val callback = mockk>(relaxed = true) + val callback = mockk>(relaxed = true) runBlocking { @@ -65,7 +64,7 @@ class BasePagingDataSourceTest : BaseTestWithMainThread() { failureSource.loadInitial(params, callback) failureSource.reasonData.testObserve { - it shouldBeInstanceOf GenericError::class + it shouldBeInstanceOf TestFailureReason::class } } } @@ -75,10 +74,9 @@ class BasePagingDataSourceTest : BaseTestWithMainThread() { inner class LoadAfter { @Test - fun `should update state accordingly`() { val params = PageKeyedDataSource.LoadParams(2, 10) - val callback = mockk>(relaxed = true) + val callback = mockk>(relaxed = true) runBlocking { @@ -92,10 +90,9 @@ class BasePagingDataSourceTest : BaseTestWithMainThread() { } @Test - fun `should update error Error accordingly`() { val params = PageKeyedDataSource.LoadParams(2, 10) - val callback = mockk>(relaxed = true) + val callback = mockk>(relaxed = true) runBlocking { @@ -103,17 +100,16 @@ class BasePagingDataSourceTest : BaseTestWithMainThread() { failureSource.loadAfter(params, callback) failureSource.reasonData.testObserve { - it shouldBeInstanceOf GenericError::class + it shouldBeInstanceOf TestFailureReason::class } } } } @Test - fun `should use loadDataForPage in loadInitial and transform emmited value`() { val params = mockk>(relaxed = true) - val callback = mockk>(relaxed = true) + val callback = mockk>(relaxed = true) // Fake loading source.loadInitial(params, callback) @@ -126,10 +122,9 @@ class BasePagingDataSourceTest : BaseTestWithMainThread() { } @Test - fun `should use loadDataForPage in loadAfter and transform emmited value`() { val params = PageKeyedDataSource.LoadParams(2, 10) - val callback = mockk>(relaxed = true) + val callback = mockk>(relaxed = true) // Fake loading source.loadAfter(params, callback) @@ -142,25 +137,27 @@ class BasePagingDataSourceTest : BaseTestWithMainThread() { } } - inner class TestSource : BasePagingDataSource() { - + inner class TestSource : BasePagingDataSource() { val result = flow { emit(State.Loading()) - emit(Success(listOf(data))) + emit(Success(listOf(TestViewEntity(data)))) } - - override fun loadDataForPage(page: Int): Flow>> = result + override fun loadDataForPage(page: Int): Flow>> = result } - inner class TestFailureSource : BasePagingDataSource() { + inner class TestFailureSource : BasePagingDataSource() { val result = flow { emit(State.Loading()) - emit(Failure(GenericError())) + emit(Failure(TestFailureReason(errorMessageResId))) } - override fun loadDataForPage(page: Int): Flow>> = result + override fun loadDataForPage(page: Int): Flow>> = result } -} \ No newline at end of file + + inner class TestViewEntity(data: Int) : ViewEntity + + inner class TestFailureReason(override val messageRes: Int) : Reason() +} diff --git a/core/src/test/kotlin/com/melih/core/paging/BasePagingFactoryTest.kt b/core/src/test/kotlin/com/melih/core/paging/BasePagingFactoryTest.kt index 5829189..cfb6145 100644 --- a/core/src/test/kotlin/com/melih/core/paging/BasePagingFactoryTest.kt +++ b/core/src/test/kotlin/com/melih/core/paging/BasePagingFactoryTest.kt @@ -1,5 +1,6 @@ package com.melih.core.paging +import com.melih.abstractions.data.ViewEntity import com.melih.core.BaseTestWithMainThread import com.melih.core.base.paging.BasePagingDataSource import com.melih.core.base.paging.BasePagingFactory @@ -25,9 +26,10 @@ class BasePagingFactoryTest : BaseTestWithMainThread() { } } - inner class TestFactory : BasePagingFactory() { - - override fun createSource(): BasePagingDataSource = mockk(relaxed = true) + inner class TestFactory : BasePagingFactory() { + override fun createSource(): BasePagingDataSource = mockk(relaxed = true) } + + inner class TestViewEntity : ViewEntity } diff --git a/data/definitions/build.gradle b/data/definitions/build.gradle new file mode 100644 index 0000000..b234e17 --- /dev/null +++ b/data/definitions/build.gradle @@ -0,0 +1,17 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' + +apply from: "$rootProject.projectDir/scripts/module.gradle" + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + implementation project(':abstractions') + + implementation libraries.room + implementation libraries.moshiKotlin + + kapt annotationProcessors.roomCompiler + kapt annotationProcessors.moshi +} diff --git a/data/definitions/consumer-rules.pro b/data/definitions/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/data/definitions/proguard-rules.pro b/data/definitions/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/data/definitions/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/data/definitions/src/main/AndroidManifest.xml b/data/definitions/src/main/AndroidManifest.xml new file mode 100644 index 0000000..8363ce0 --- /dev/null +++ b/data/definitions/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + diff --git a/repository/src/main/kotlin/com/melih/repository/Constants.kt b/data/definitions/src/main/kotlin/com/melih/definitions/Constants.kt similarity index 74% rename from repository/src/main/kotlin/com/melih/repository/Constants.kt rename to data/definitions/src/main/kotlin/com/melih/definitions/Constants.kt index 5d5e2dc..533f176 100644 --- a/repository/src/main/kotlin/com/melih/repository/Constants.kt +++ b/data/definitions/src/main/kotlin/com/melih/definitions/Constants.kt @@ -1,4 +1,4 @@ -package com.melih.repository +package com.melih.definitions internal const val DEFAULT_NAME = "Default name" internal const val EMPTY_STRING = "" diff --git a/data/definitions/src/main/kotlin/com/melih/definitions/Source.kt b/data/definitions/src/main/kotlin/com/melih/definitions/Source.kt new file mode 100644 index 0000000..289304f --- /dev/null +++ b/data/definitions/src/main/kotlin/com/melih/definitions/Source.kt @@ -0,0 +1,23 @@ +package com.melih.definitions + +import com.melih.abstractions.data.ViewEntity +import com.melih.abstractions.deliverable.Result +import com.melih.abstractions.mapper.Mapper +import com.melih.definitions.entities.LaunchEntity + +/** + * Contract for sources to seperate business logic from build and return type + */ +interface Source { + + //region Abstractions + + suspend fun getNextLaunches( + count: Int, + page: Int, + mapper: Mapper + ): Result> + + suspend fun getLaunchById(id: Long, mapper: Mapper): Result + //endregion +} diff --git a/repository/src/main/kotlin/com/melih/repository/entities/LaunchEntity.kt b/data/definitions/src/main/kotlin/com/melih/definitions/entities/LaunchEntity.kt similarity index 87% rename from repository/src/main/kotlin/com/melih/repository/entities/LaunchEntity.kt rename to data/definitions/src/main/kotlin/com/melih/definitions/entities/LaunchEntity.kt index 2a7bdbd..43779e8 100644 --- a/repository/src/main/kotlin/com/melih/repository/entities/LaunchEntity.kt +++ b/data/definitions/src/main/kotlin/com/melih/definitions/entities/LaunchEntity.kt @@ -1,8 +1,8 @@ -package com.melih.repository.entities +package com.melih.definitions.entities import androidx.room.Entity import androidx.room.PrimaryKey -import com.melih.repository.DEFAULT_NAME +import com.melih.definitions.DEFAULT_NAME import com.squareup.moshi.Json import com.squareup.moshi.JsonClass diff --git a/repository/src/main/kotlin/com/melih/repository/entities/LaunchesEntity.kt b/data/definitions/src/main/kotlin/com/melih/definitions/entities/LaunchesEntity.kt similarity index 86% rename from repository/src/main/kotlin/com/melih/repository/entities/LaunchesEntity.kt rename to data/definitions/src/main/kotlin/com/melih/definitions/entities/LaunchesEntity.kt index eaa55df..f6f3762 100644 --- a/repository/src/main/kotlin/com/melih/repository/entities/LaunchesEntity.kt +++ b/data/definitions/src/main/kotlin/com/melih/definitions/entities/LaunchesEntity.kt @@ -1,4 +1,4 @@ -package com.melih.repository.entities +package com.melih.definitions.entities import com.squareup.moshi.JsonClass diff --git a/repository/src/main/kotlin/com/melih/repository/entities/LocationEntity.kt b/data/definitions/src/main/kotlin/com/melih/definitions/entities/LocationEntity.kt similarity index 87% rename from repository/src/main/kotlin/com/melih/repository/entities/LocationEntity.kt rename to data/definitions/src/main/kotlin/com/melih/definitions/entities/LocationEntity.kt index 652c4d3..af217ef 100644 --- a/repository/src/main/kotlin/com/melih/repository/entities/LocationEntity.kt +++ b/data/definitions/src/main/kotlin/com/melih/definitions/entities/LocationEntity.kt @@ -1,7 +1,7 @@ -package com.melih.repository.entities +package com.melih.definitions.entities import androidx.room.ColumnInfo -import com.melih.repository.DEFAULT_NAME +import com.melih.definitions.DEFAULT_NAME import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) diff --git a/repository/src/main/kotlin/com/melih/repository/entities/MissionEntity.kt b/data/definitions/src/main/kotlin/com/melih/definitions/entities/MissionEntity.kt similarity index 73% rename from repository/src/main/kotlin/com/melih/repository/entities/MissionEntity.kt rename to data/definitions/src/main/kotlin/com/melih/definitions/entities/MissionEntity.kt index 971ec00..a8d1141 100644 --- a/repository/src/main/kotlin/com/melih/repository/entities/MissionEntity.kt +++ b/data/definitions/src/main/kotlin/com/melih/definitions/entities/MissionEntity.kt @@ -1,8 +1,8 @@ -package com.melih.repository.entities +package com.melih.definitions.entities import androidx.room.ColumnInfo -import com.melih.repository.DEFAULT_NAME -import com.melih.repository.EMPTY_STRING +import com.melih.definitions.DEFAULT_NAME +import com.melih.definitions.EMPTY_STRING import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) diff --git a/repository/src/main/kotlin/com/melih/repository/entities/RocketEntity.kt b/data/definitions/src/main/kotlin/com/melih/definitions/entities/RocketEntity.kt similarity index 78% rename from repository/src/main/kotlin/com/melih/repository/entities/RocketEntity.kt rename to data/definitions/src/main/kotlin/com/melih/definitions/entities/RocketEntity.kt index 7fd8c3d..1b3f01e 100644 --- a/repository/src/main/kotlin/com/melih/repository/entities/RocketEntity.kt +++ b/data/definitions/src/main/kotlin/com/melih/definitions/entities/RocketEntity.kt @@ -1,8 +1,8 @@ -package com.melih.repository.entities +package com.melih.definitions.entities import androidx.room.ColumnInfo -import com.melih.repository.DEFAULT_NAME -import com.melih.repository.EMPTY_STRING +import com.melih.definitions.DEFAULT_NAME +import com.melih.definitions.EMPTY_STRING import com.squareup.moshi.Json import com.squareup.moshi.JsonClass diff --git a/data/interactors/build.gradle b/data/interactors/build.gradle new file mode 100644 index 0000000..b09d7d2 --- /dev/null +++ b/data/interactors/build.gradle @@ -0,0 +1,22 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' + +apply from: "$rootProject.projectDir/scripts/module.gradle" +apply from: "$rootProject.projectDir/scripts/default_dependencies.gradle" + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + implementation project(':data:definitions') + implementation project(':data:network') + implementation project(':data:persistence') + + implementation libraries.coroutines + implementation libraries.retrofit + + testImplementation testLibraries.coroutinesCore + testImplementation testLibraries.coroutinesTest + + compileOnly libraries.room +} diff --git a/data/interactors/consumer-rules.pro b/data/interactors/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/data/interactors/proguard-rules.pro b/data/interactors/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/data/interactors/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/data/interactors/src/main/AndroidManifest.xml b/data/interactors/src/main/AndroidManifest.xml new file mode 100644 index 0000000..86a5aa4 --- /dev/null +++ b/data/interactors/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + diff --git a/data/interactors/src/main/kotlin/com/melih/interactors/GetLaunchDetails.kt b/data/interactors/src/main/kotlin/com/melih/interactors/GetLaunchDetails.kt new file mode 100644 index 0000000..50fef98 --- /dev/null +++ b/data/interactors/src/main/kotlin/com/melih/interactors/GetLaunchDetails.kt @@ -0,0 +1,40 @@ +package com.melih.interactors + +import com.melih.abstractions.data.ViewEntity +import com.melih.abstractions.deliverable.Result +import com.melih.abstractions.mapper.Mapper +import com.melih.definitions.entities.LaunchEntity +import com.melih.interactors.base.BaseInteractor +import com.melih.interactors.base.InteractorParameters +import com.melih.interactors.sources.LaunchesSource +import kotlinx.coroutines.flow.FlowCollector +import javax.inject.Inject + +/** + * Gets next given number of launches + */ +class GetLaunchDetails @Inject constructor( + private val mapper: @JvmSuppressWildcards Mapper +) : BaseInteractor() { + + //region Properties + + @Inject + internal lateinit var launchesSource: LaunchesSource + //endregion + + //region Functions + + override suspend fun FlowCollector>.run(params: Params) { + emit(launchesSource.getLaunchById(params.id, mapper)) + } + //endregion + + + //region Parameters + + data class Params( + val id: Long + ) : InteractorParameters + //endregion +} diff --git a/data/interactors/src/main/kotlin/com/melih/interactors/GetLaunches.kt b/data/interactors/src/main/kotlin/com/melih/interactors/GetLaunches.kt new file mode 100644 index 0000000..0f3e601 --- /dev/null +++ b/data/interactors/src/main/kotlin/com/melih/interactors/GetLaunches.kt @@ -0,0 +1,44 @@ +package com.melih.interactors + +import com.melih.abstractions.data.ViewEntity +import com.melih.abstractions.deliverable.Result +import com.melih.abstractions.mapper.Mapper +import com.melih.definitions.entities.LaunchEntity +import com.melih.interactors.base.BaseInteractor +import com.melih.interactors.base.InteractorParameters +import com.melih.interactors.sources.LaunchesSource +import kotlinx.coroutines.flow.FlowCollector +import javax.inject.Inject + +const val DEFAULT_LAUNCHES_AMOUNT = 15 + +/** + * Gets next given number of launches + */ +class GetLaunches @Inject constructor( + private val mapper: @JvmSuppressWildcards Mapper +) : BaseInteractor, GetLaunches.Params>() { + + //region Properties + + @Inject + internal lateinit var launchesSource: LaunchesSource + //endregion + + //region Functions + + override suspend fun FlowCollector>>.run(params: Params) { + + // Start network fetch - we're not handling state here to ommit them + emit( + launchesSource + .getNextLaunches(params.count, params.page, mapper) + ) + } + //endregion + + data class Params( + val count: Int = DEFAULT_LAUNCHES_AMOUNT, + val page: Int + ) : InteractorParameters +} diff --git a/repository/src/main/kotlin/com/melih/repository/interactors/base/BaseInteractor.kt b/data/interactors/src/main/kotlin/com/melih/interactors/base/BaseInteractor.kt similarity index 88% rename from repository/src/main/kotlin/com/melih/repository/interactors/base/BaseInteractor.kt rename to data/interactors/src/main/kotlin/com/melih/interactors/base/BaseInteractor.kt index bb7605c..ffcca7f 100644 --- a/repository/src/main/kotlin/com/melih/repository/interactors/base/BaseInteractor.kt +++ b/data/interactors/src/main/kotlin/com/melih/interactors/base/BaseInteractor.kt @@ -1,5 +1,7 @@ -package com.melih.repository.interactors.base +package com.melih.interactors.base +import com.melih.abstractions.deliverable.Result +import com.melih.abstractions.deliverable.State import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow diff --git a/data/interactors/src/main/kotlin/com/melih/interactors/error/InteractionErrorReason.kt b/data/interactors/src/main/kotlin/com/melih/interactors/error/InteractionErrorReason.kt new file mode 100644 index 0000000..3242f88 --- /dev/null +++ b/data/interactors/src/main/kotlin/com/melih/interactors/error/InteractionErrorReason.kt @@ -0,0 +1,17 @@ +package com.melih.interactors.error + +import androidx.annotation.StringRes +import com.melih.abstractions.deliverable.Reason +import com.melih.interactors.R + +sealed class InteractionErrorReason(@StringRes override val messageRes: Int) : Reason() + +class GenericError(@StringRes override val messageRes: Int = R.string.reason_generic) : InteractionErrorReason(messageRes) + +sealed class NetworkError(override val messageRes: Int) : InteractionErrorReason(messageRes) +class ConnectionError : NetworkError(R.string.reason_network) +class EmptyResultError : NetworkError(R.string.reason_empty_body) +class ResponseError : NetworkError(R.string.reason_response) +class TimeoutError : NetworkError(R.string.reason_timeout) + +class PersistenceEmptyError : InteractionErrorReason(R.string.reason_persistance_empty) diff --git a/data/interactors/src/main/kotlin/com/melih/interactors/sources/LaunchesSource.kt b/data/interactors/src/main/kotlin/com/melih/interactors/sources/LaunchesSource.kt new file mode 100644 index 0000000..ad73cce --- /dev/null +++ b/data/interactors/src/main/kotlin/com/melih/interactors/sources/LaunchesSource.kt @@ -0,0 +1,163 @@ +package com.melih.interactors.sources + +import android.content.Context +import android.net.NetworkInfo +import com.melih.abstractions.data.ViewEntity +import com.melih.abstractions.deliverable.Failure +import com.melih.abstractions.deliverable.Result +import com.melih.abstractions.deliverable.Success +import com.melih.abstractions.mapper.Mapper +import com.melih.definitions.Source +import com.melih.definitions.entities.LaunchEntity +import com.melih.interactors.DEFAULT_LAUNCHES_AMOUNT +import com.melih.interactors.error.ConnectionError +import com.melih.interactors.error.EmptyResultError +import com.melih.interactors.error.NetworkError +import com.melih.interactors.error.PersistenceEmptyError +import com.melih.interactors.error.ResponseError +import com.melih.interactors.error.TimeoutError +import com.melih.network.ApiImpl +import com.melih.persistence.LaunchesDatabase +import retrofit2.Response +import java.io.IOException +import javax.inject.Inject +import javax.inject.Provider + +private const val DEFAULT_IMAGE_SIZE = 480 + +internal class LaunchesSource @Inject constructor( + ctx: Context, + private val apiImpl: ApiImpl, + private val networkInfoProvider: Provider +) : Source { + + //region Properties + + private val launchesDatabase = LaunchesDatabase.getInstance(ctx) + + private val isNetworkConnected: Boolean + get() { + val networkInfo = networkInfoProvider.get() + return networkInfo != null && networkInfo.isConnected + } + //endregion + + //region Functions + + override suspend fun getNextLaunches( + count: Int, + page: Int, mapper: Mapper + ): Result> { + val networkResponse = safeExecute({ + apiImpl.getNextLaunches(count, page * DEFAULT_LAUNCHES_AMOUNT) + }) { entity -> + entity.launches + .map(::transformRocketImageUrl) + .saveLaunches() + .map(mapper::convert) + } + + return if (networkResponse is NetworkError) { + launchesDatabase + .launchesDao + .getLaunches(count, page) + .takeUnless { it.isNullOrEmpty() } + ?.run { + Success(map(mapper::convert)) + } ?: Failure(PersistenceEmptyError()) + } else { + networkResponse + } + } + + override suspend fun getLaunchById( + id: Long, + mapper: Mapper + ): Result { + return launchesDatabase + .launchesDao + .getLaunchById(id) + .takeIf { it != null } + ?.run { + Success(mapper.convert(this)) + } ?: loadLaunchFromNetwork(id, mapper) + } + + private suspend fun loadLaunchFromNetwork( + id: Long, + mapper: Mapper + ): Result = + safeExecute({ + apiImpl.getLaunchById(id) + }) { + mapper.convert( + transformRocketImageUrl(it) + .saveLaunch() + ) + } + + private suspend fun List.saveLaunches() = run { + launchesDatabase.launchesDao.saveLaunches(this) + this + } + + private suspend fun LaunchEntity.saveLaunch() = run { + launchesDatabase.launchesDao.saveLaunch(this) + this + } + + private inline fun safeExecute( + block: () -> Response, + transform: (T) -> R + ) = + if (isNetworkConnected) { + try { + block().extractResponseBody(transform) + } catch (e: IOException) { + Failure(TimeoutError()) + } + } else { + Failure(ConnectionError()) + } + + private inline fun Response.extractResponseBody(transform: (T) -> R) = + if (isSuccessful) { + body()?.let { + Success(transform(it)) + } ?: Failure(EmptyResultError()) + } else { + Failure(ResponseError()) + } + + private fun transformRocketImageUrl(launch: LaunchEntity) = + if (!launch.rocket.imageURL.isNotBlank()) { + launch.copy( + rocket = launch.rocket.copy( + imageURL = transformImageUrl( + launch.rocket.imageURL, + launch.rocket.imageSizes + ) + ) + ) + } else { + launch + } + + private fun transformImageUrl(imageUrl: String, supportedSizes: IntArray) = + try { + val urlSplit = imageUrl.split("_") + val url = urlSplit[0] + val format = urlSplit[1].split(".")[1] + + val requestedSize = if (!supportedSizes.contains(DEFAULT_IMAGE_SIZE)) { + supportedSizes.last { it < DEFAULT_IMAGE_SIZE } + } else { + DEFAULT_IMAGE_SIZE + } + + "${url}_$requestedSize.$format" + } catch (e: Exception) { + imageUrl + } + //endregion +} diff --git a/repository/src/main/res/values/strings.xml b/data/interactors/src/main/res/values/strings.xml similarity index 81% rename from repository/src/main/res/values/strings.xml rename to data/interactors/src/main/res/values/strings.xml index 92cdbc9..13e42df 100644 --- a/repository/src/main/res/values/strings.xml +++ b/data/interactors/src/main/res/values/strings.xml @@ -1,9 +1,8 @@ + Something went wrong + There are no saved launches Network error Response is empty - Something went wrong Woops, seems we got a server error Server timed out - There are no saved launches - Seems there are no data and network diff --git a/repository/src/test/kotlin/com/melih/repository/interactors/base/BaseInteractorTest.kt b/data/interactors/src/test/kotlin/com/melih/interactors/base/BaseInteractorTest.kt similarity index 91% rename from repository/src/test/kotlin/com/melih/repository/interactors/base/BaseInteractorTest.kt rename to data/interactors/src/test/kotlin/com/melih/interactors/base/BaseInteractorTest.kt index 35d922c..5505f1a 100644 --- a/repository/src/test/kotlin/com/melih/repository/interactors/base/BaseInteractorTest.kt +++ b/data/interactors/src/test/kotlin/com/melih/interactors/base/BaseInteractorTest.kt @@ -1,5 +1,8 @@ -package com.melih.repository.interactors.base +package com.melih.interactors.base +import com.melih.abstractions.deliverable.Result +import com.melih.abstractions.deliverable.State +import com.melih.abstractions.deliverable.Success import io.mockk.coVerify import io.mockk.spyk import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -10,7 +13,7 @@ import kotlinx.coroutines.runBlocking import org.amshove.kluent.shouldBeInstanceOf import org.amshove.kluent.shouldEqualTo import org.junit.jupiter.api.Test -import java.util.* +import java.util.ArrayDeque @UseExperimental(ExperimentalCoroutinesApi::class) class BaseInteractorTest { diff --git a/data/network/build.gradle b/data/network/build.gradle new file mode 100644 index 0000000..cc02d5c --- /dev/null +++ b/data/network/build.gradle @@ -0,0 +1,20 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' + +apply from: "$rootProject.projectDir/scripts/module.gradle" +apply from: "$rootProject.projectDir/scripts/default_dependencies.gradle" + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + implementation project(':data:definitions') + + implementation libraries.okHttpLogger + implementation libraries.moshiKotlin + implementation libraries.coroutines + implementation libraries.retrofit + + testImplementation testLibraries.coroutinesCore + testImplementation testLibraries.coroutinesTest +} diff --git a/data/network/consumer-rules.pro b/data/network/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/data/network/proguard-rules.pro b/data/network/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/data/network/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/data/network/src/main/AndroidManifest.xml b/data/network/src/main/AndroidManifest.xml new file mode 100644 index 0000000..32d8ca6 --- /dev/null +++ b/data/network/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + diff --git a/repository/src/main/kotlin/com/melih/repository/network/Api.kt b/data/network/src/main/kotlin/com/melih/network/api/Api.kt similarity index 78% rename from repository/src/main/kotlin/com/melih/repository/network/Api.kt rename to data/network/src/main/kotlin/com/melih/network/api/Api.kt index 2783559..bef517d 100644 --- a/repository/src/main/kotlin/com/melih/repository/network/Api.kt +++ b/data/network/src/main/kotlin/com/melih/network/api/Api.kt @@ -1,7 +1,7 @@ -package com.melih.repository.network +package com.melih.network -import com.melih.repository.entities.LaunchEntity -import com.melih.repository.entities.LaunchesEntity +import com.melih.definitions.entities.LaunchEntity +import com.melih.definitions.entities.LaunchesEntity import retrofit2.Response import retrofit2.http.GET import retrofit2.http.Path diff --git a/repository/src/main/kotlin/com/melih/repository/network/ApiImpl.kt b/data/network/src/main/kotlin/com/melih/network/api/ApiImpl.kt similarity index 88% rename from repository/src/main/kotlin/com/melih/repository/network/ApiImpl.kt rename to data/network/src/main/kotlin/com/melih/network/api/ApiImpl.kt index c56ffb8..ff195c6 100644 --- a/repository/src/main/kotlin/com/melih/repository/network/ApiImpl.kt +++ b/data/network/src/main/kotlin/com/melih/network/api/ApiImpl.kt @@ -1,7 +1,7 @@ -package com.melih.repository.network +package com.melih.network -import com.melih.repository.entities.LaunchEntity -import com.melih.repository.entities.LaunchesEntity +import com.melih.definitions.entities.LaunchEntity +import com.melih.definitions.entities.LaunchesEntity import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import okhttp3.OkHttpClient @@ -14,7 +14,7 @@ import javax.inject.Inject internal const val TIMEOUT_DURATION = 7L -internal class ApiImpl @Inject constructor() : Api { +class ApiImpl @Inject constructor() : Api { //region Properties diff --git a/repository/build.gradle b/data/persistence/build.gradle similarity index 82% rename from repository/build.gradle rename to data/persistence/build.gradle index 843f46d..fcc536e 100644 --- a/repository/build.gradle +++ b/data/persistence/build.gradle @@ -3,6 +3,7 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' apply from: "$rootProject.projectDir/scripts/module.gradle" +apply from: "$rootProject.projectDir/scripts/default_dependencies.gradle" android { defaultConfig { @@ -17,15 +18,13 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation project(':data:definitions') + + implementation libraries.moshiKotlin implementation libraries.coroutines - implementation libraries.liveDataKTX - implementation libraries.retrofit implementation libraries.room - implementation libraries.moshiKotlin - implementation libraries.okHttpLogger kapt annotationProcessors.roomCompiler - kapt annotationProcessors.moshi testImplementation testLibraries.coroutinesCore testImplementation testLibraries.coroutinesTest diff --git a/data/persistence/consumer-rules.pro b/data/persistence/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/data/persistence/proguard-rules.pro b/data/persistence/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/data/persistence/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/data/persistence/src/main/AndroidManifest.xml b/data/persistence/src/main/AndroidManifest.xml new file mode 100644 index 0000000..4d3d76b --- /dev/null +++ b/data/persistence/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + diff --git a/repository/src/main/kotlin/com/melih/repository/persistence/LaunchesDatabase.kt b/data/persistence/src/main/kotlin/com/melih/persistence/LaunchesDatabase.kt similarity index 65% rename from repository/src/main/kotlin/com/melih/repository/persistence/LaunchesDatabase.kt rename to data/persistence/src/main/kotlin/com/melih/persistence/LaunchesDatabase.kt index 636dbab..da7a83c 100644 --- a/repository/src/main/kotlin/com/melih/repository/persistence/LaunchesDatabase.kt +++ b/data/persistence/src/main/kotlin/com/melih/persistence/LaunchesDatabase.kt @@ -1,15 +1,15 @@ -package com.melih.repository.persistence +package com.melih.persistence import android.content.Context import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.TypeConverters -import com.melih.repository.entities.LaunchEntity -import com.melih.repository.persistence.converters.LocationConverter -import com.melih.repository.persistence.converters.MissionConverter -import com.melih.repository.persistence.converters.RocketConverter -import com.melih.repository.persistence.dao.LaunchesDao +import com.melih.definitions.entities.LaunchEntity +import com.melih.persistence.converters.LocationConverter +import com.melih.persistence.converters.MissionConverter +import com.melih.persistence.converters.RocketConverter +import com.melih.persistence.dao.LaunchesDao const val DB_NAME = "LaunchesDB" @@ -26,7 +26,7 @@ const val DB_NAME = "LaunchesDB" RocketConverter::class, MissionConverter::class ) -internal abstract class LaunchesDatabase : RoomDatabase() { +abstract class LaunchesDatabase : RoomDatabase() { //region Companion @@ -48,6 +48,6 @@ internal abstract class LaunchesDatabase : RoomDatabase() { //region Abstractions - internal abstract val launchesDao: LaunchesDao + abstract val launchesDao: LaunchesDao //endregion } diff --git a/repository/src/main/kotlin/com/melih/repository/persistence/converters/BaseConverter.kt b/data/persistence/src/main/kotlin/com/melih/persistence/converters/BaseConverter.kt similarity index 93% rename from repository/src/main/kotlin/com/melih/repository/persistence/converters/BaseConverter.kt rename to data/persistence/src/main/kotlin/com/melih/persistence/converters/BaseConverter.kt index cc9d698..a0bb43f 100644 --- a/repository/src/main/kotlin/com/melih/repository/persistence/converters/BaseConverter.kt +++ b/data/persistence/src/main/kotlin/com/melih/persistence/converters/BaseConverter.kt @@ -1,4 +1,4 @@ -package com.melih.repository.persistence.converters +package com.melih.persistence.converters import androidx.room.TypeConverter import com.squareup.moshi.JsonAdapter diff --git a/repository/src/main/kotlin/com/melih/repository/persistence/converters/BaseListConverter.kt b/data/persistence/src/main/kotlin/com/melih/persistence/converters/BaseListConverter.kt similarity index 93% rename from repository/src/main/kotlin/com/melih/repository/persistence/converters/BaseListConverter.kt rename to data/persistence/src/main/kotlin/com/melih/persistence/converters/BaseListConverter.kt index 7bdc189..df5fe73 100644 --- a/repository/src/main/kotlin/com/melih/repository/persistence/converters/BaseListConverter.kt +++ b/data/persistence/src/main/kotlin/com/melih/persistence/converters/BaseListConverter.kt @@ -1,4 +1,4 @@ -package com.melih.repository.persistence.converters +package com.melih.persistence.converters import androidx.room.TypeConverter import com.squareup.moshi.JsonAdapter diff --git a/repository/src/main/kotlin/com/melih/repository/persistence/converters/LocationConverter.kt b/data/persistence/src/main/kotlin/com/melih/persistence/converters/LocationConverter.kt similarity index 63% rename from repository/src/main/kotlin/com/melih/repository/persistence/converters/LocationConverter.kt rename to data/persistence/src/main/kotlin/com/melih/persistence/converters/LocationConverter.kt index 399efb9..a4d375d 100644 --- a/repository/src/main/kotlin/com/melih/repository/persistence/converters/LocationConverter.kt +++ b/data/persistence/src/main/kotlin/com/melih/persistence/converters/LocationConverter.kt @@ -1,7 +1,7 @@ -package com.melih.repository.persistence.converters +package com.melih.persistence.converters -import com.melih.repository.entities.LocationEntity -import com.melih.repository.entities.LocationEntityJsonAdapter +import com.melih.definitions.entities.LocationEntity +import com.melih.definitions.entities.LocationEntityJsonAdapter import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi diff --git a/repository/src/main/kotlin/com/melih/repository/persistence/converters/MissionConverter.kt b/data/persistence/src/main/kotlin/com/melih/persistence/converters/MissionConverter.kt similarity index 81% rename from repository/src/main/kotlin/com/melih/repository/persistence/converters/MissionConverter.kt rename to data/persistence/src/main/kotlin/com/melih/persistence/converters/MissionConverter.kt index 1d25e9a..6a524e4 100644 --- a/repository/src/main/kotlin/com/melih/repository/persistence/converters/MissionConverter.kt +++ b/data/persistence/src/main/kotlin/com/melih/persistence/converters/MissionConverter.kt @@ -1,6 +1,6 @@ -package com.melih.repository.persistence.converters +package com.melih.persistence.converters -import com.melih.repository.entities.MissionEntity +import com.melih.definitions.entities.MissionEntity import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi import com.squareup.moshi.Types diff --git a/repository/src/main/kotlin/com/melih/repository/persistence/converters/RocketConverter.kt b/data/persistence/src/main/kotlin/com/melih/persistence/converters/RocketConverter.kt similarity index 63% rename from repository/src/main/kotlin/com/melih/repository/persistence/converters/RocketConverter.kt rename to data/persistence/src/main/kotlin/com/melih/persistence/converters/RocketConverter.kt index 66b3606..d98102b 100644 --- a/repository/src/main/kotlin/com/melih/repository/persistence/converters/RocketConverter.kt +++ b/data/persistence/src/main/kotlin/com/melih/persistence/converters/RocketConverter.kt @@ -1,7 +1,7 @@ -package com.melih.repository.persistence.converters +package com.melih.persistence.converters -import com.melih.repository.entities.RocketEntity -import com.melih.repository.entities.RocketEntityJsonAdapter +import com.melih.definitions.entities.RocketEntity +import com.melih.definitions.entities.RocketEntityJsonAdapter import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi diff --git a/repository/src/main/kotlin/com/melih/repository/persistence/dao/LaunchesDao.kt b/data/persistence/src/main/kotlin/com/melih/persistence/dao/LaunchesDao.kt similarity index 86% rename from repository/src/main/kotlin/com/melih/repository/persistence/dao/LaunchesDao.kt rename to data/persistence/src/main/kotlin/com/melih/persistence/dao/LaunchesDao.kt index 2490cdd..7d14666 100644 --- a/repository/src/main/kotlin/com/melih/repository/persistence/dao/LaunchesDao.kt +++ b/data/persistence/src/main/kotlin/com/melih/persistence/dao/LaunchesDao.kt @@ -1,16 +1,16 @@ -package com.melih.repository.persistence.dao +package com.melih.persistence.dao import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query -import com.melih.repository.entities.LaunchEntity +import com.melih.definitions.entities.LaunchEntity /** * DAO for list of [launches][LaunchEntity] */ @Dao -internal abstract class LaunchesDao { +abstract class LaunchesDao { //region Queries diff --git a/docs/module_graph.png b/docs/module_graph.png index 7add4b6e609205841326756ede652bfcf4d45178..eab7666beee4f17e4b46cc06004a7671d5a331b1 100644 GIT binary patch literal 129138 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 z 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
W!;4bmte_{PvZzh`!(a z5)DnJYs6C~Q!WJT4q?iFNt4;xA*W(+g0PXi>P{oMg^;yWnXI-Jiy7mAc`5R54PdfW z9_^17q6{KQkXm6jbv5nh)=vT~ur#gsBXgC8Cyx_awUy?f!-hR?#HO?{!n+&{2@ z8@u)Jl*f2}MVY;l8(p;1yL9K zb>sdl5r-j^fG7c6G?!!Ty0%wggkSgjjEUYJ@t2^V`JvmvZ7I&DY2 z7X-HyWbEwQygAddxv@#bn8)KE2`==r%Y3_OLWZsiQE4?0t_S7I8N%Vt%SdAX{ zJ4R{Z1q2<$s7C%X^I_Gf>4%YGmn(GTB}`_8dwcG~_x9%5vVNZ@U8x1u3A+cm6?Mck z&DohvGObG{wkC*hZ+9Ilzy-_nx#_Yd1unD@qP7hh%tq0L*jMZiv9I?eGy-eSNl2?c zanoJXf{-qD;BkX{VLg!nl^o#v9Fd_@f~T-d%Q-3N@+^xU*OwD#Ar0V^ad2b$@`>!t zFuVq%(jt)c)QpUROyx43E9}y;O#QnETEZJOR)vd;HlN&AqSLTam(|*j#(EH;DCYyY zQ>sxD)ZZM3x^LKkTG#Q$5f|5@0y0TU%GD~Qq$OOGSsLE#1y^W=yXaQ3(4=_L6Buo; zuRI)+mHhKFvEqrSlj9&>Ajas1OkW5Q_AC%JvNeR1v5s!@`w%(Q9c^{WY`HczGjrq0 z;)REUHyB{NyN+6Ho_uaDMy^*=re%9GSJ?(zcyp_Ane0(?G1-pP;qYB%UB(WM#iZsp zz8R}FDe1<6ar_w6ZXf)-n}iW4dI0f?VG`Z)i6Kwi0#iAVZwo%!+Gq|p(9Oai7_;Ak zEA@xFn+4xrEe*}u7`_IR{k!0@CN|wn&Q-vOOv1an)Ey=6`bGMa-vET%6@qN%2ITL2 zwEFNmJng~@gUz?@Ysu*;bmJkIy4|3%`)D zL<9k|_rRT-Wopcg9~wBj@k9H;@0NDofMyh{&wHDhreOn-KjN^oy((wwkqCOU@==Aq zI)~g#p5OHR^ZqR(FpDL>2dadfIGbKUu$RgcQjNY z;xX}?W`Fkg3&HlRBH<#JXe8-IjKs(R8^SD6lqRCT_+xD9{GDKe=}|@U=-GVL(_baX zM<@XjTU+qI0ZT=>))rO&F)}sU{EmR=+SWzo)|Hq;LA8&8Z^=|FqWL z%c>|tlh9RV5#MGA6Qm76V(s%@-I-MXH1c(94hG}MPloUWd>xaOs=R^3HuvryHA3)t zSJ6t&3m-mcuF9_DLHEiTIy9Gn)+v5c)*PxL#oQ{h5XB|b_v`jI|2+J)PAU|8>K;_j z-q=vSlZ@6;kZc$MiLW^__R#a!hwVy=Frb>M69t}m9e}~wu}7|` zutSlFCLJX1Ad&Qc>c%24o?ydRretrz=Mw1XSW?nmXZ1rnzMS~qySg9!~IUn{2y48X42Pf&v@kU9( z{HJE0yaq=+;e11WFZSCvyF@8;@{fnBKn$s4pkF}qi~DLtCai3uI084P1oY~=gL61= zs0fdmV0pd!hu}j8Bz;&WO4zz34UEE+P_{0d#l&;~FH`>Q2M!^pV9__7i@ntygTY({ zCxBv@Z!3e=hgi96m~dym{XIU$wzsQSq79)>EQbWSa4?;} z3esU86^bt<4JdZZk_KDwii=OpdlWqaH}ZWRIK`p*zQbgE-wGR$mY$t*`_hzb^WeKc z6`Xh0Fzmnyye2@~bW5F`5tq3I8mG8|>GuM{R9rSBxZM1G(b&k{@os5xI<)N0eA~6X z4pGXk${Y#0&siRy;8%tgNtydFB4c|Vjx}JdbO7z}svxIJP{s*io~_`dW=#!7DS5@B z-{|v2k=!7wo`DL#JJuU@% z=yyroXTOt%=kfMDGR`?eC6JekjHJZr_7wLI56|hyLX5-!=B)64AiQT<@1Ui8Sw8U2 zSi#kzb*SgtFN$J4MKAK3!U`bwWUovAAosspvfk@T&_k!av30%v0BO_RrHWLqf82yxl?1q4 zn~Cu)atjg^oj#qH>g8#@j31up$h?S>%qkipSJO;zW`hSt0T9iI&>HZsFi)-~O7Kk1tu7 z!Sm*^cZ~y_R^XNSIMw8ieY;N=kKO9G16iQvTIdrE2s|Kl(ma~@k;g}~enjaUW7j|S z&%@vz`{g5{)DVgugKNgonE}RQ!pPx1s5YbxOVMk~fHra#=3&0yyk`GYttnH?i5$G9 z^YKY93}5**fZ~to1Zkz38MfV=`$3}{sBZXs4yhV}@3}Ulbz~g_7QqC~PEQ$de{+Va zNUXHrEOE2p-b}LmfQl3y`Hbh;QK44DZG?R6rG01!wLCzjJ`9Re_%rv1NSd#m+$$8IjJGFUwd^{R2j};I z#4m{dfZ!8loNSWfBQvvX8Qn4`BAVaUUgp!G@!c>ySAxy*CeyPpY2_bILt$FhcMA$z z?^nbO;eWT~^+)gh{#^=4xaECeXr{WK{sFj!?oY2N$xpf`q3_T!Po9(j9#hwx|8DJi z{10U>+m!`>W=A*`vY+4SoR)S{U+KTS)E!gcA21Xa7S;$eq_ZUCrD+4L4_z9UEOqJ? zY~p>qjoe5HGa|-*q!+bL76yxcn0lda=EJ?Hrf?i_I@EjD(KSZfEodZmwSB9qpt1mC*Xurht76=l~w;24<)0ib9WLvrCdyvnt_Zr zg2XbHr_aOQFc34u5m`s@TzG8+>AdjTFB;C3Bdy1~V34G06XSCw2 zCK(`E81f!km@T?3>Rxm`T~ncvgE$Jb-i!CBN>+|SuyYpPNOocO24D^}PZ=jXFw zP2`<3R^6Uiic=y);}DOBf0f&=rnv-lTv0q~*uhi=X=&De}$& zF!<= z7{~C5#{izj7Lwb@f@YjgTT|AM{$+4e@1YH&&vGx3JeiiSeJoey@Y(p+3O}V5WgHG| zE>2E<07zRHGm;6YSXo6PA7?sph|+y(3zUK?evQsuY5mc8ssU^D9!DqdBZ;6Ev-T?} zGIlgAtvXgA43#K`^edx{`2$*~p-hgUwjVUL4o;y=jj=d6YM&xwNcdrKvnHA`ESpJ!L#Zv_sTZ*g(joLmsE&!-Zn6D50O&YLQWakpqX z0v{uy2`;Mt?tk|dpn*$!lor$2MpJO{2&AntgkbgnZdDw)NU^@U%F}r4`Q#M7XixEC zXC}4B)R?Y&vz3+xvn-oK0!zyfm(vKbGy~8%syy?soxz~U$l@QYJZO_}4FmQUH}hQh z=36uAyLy?TU6t>WW)L9~I`HaP4(2r`w4;zuR=jAgM_@#JH~1}NtH}NFz6I% z#dbY5>h1%`Rn62jSNgI*oDyIhs?=P!16SwiaR*VIXLJ~?B<7d!R6Hg5)bw~I0DLuT z4IX#=A2xu4n6(<@u)=0+9kp^~PXQP!fiI#{Y}TZ|nhvNiTh`gcj%~k(V@(nkd+{a8Vj*rp6_QZJPpfkdA)Rw_eY{v+k~Q-L#`-A3a^3Aq ztW#5$9{n|%bm1QzgvJJY^l)A#5jW9E;Hrg5=mcn6mX{ykkS0NWC~dACYjhOp3Mc4= zvYmUGR%A-%;sX!-SZg<4!ws`fqAi{SOCdPl(ZIrZLFG+@w1+%a9l#m}V&HL@f1)VLe!ZBj ziRJczw1Gm97EHj_4!>=8H}GM?13(KjFh(_R zRSKOmgw2&>R`4|Jcn>*4W7PcosQL9f+gLodqG2(@2ks)S%(ja7q$t`^rU&e%b3>wv z@Q^LuK?25Go~h*xPPqK+?4fVl+T`VbP@Ft+$ITa2t@~Co!q)k1oP_+22zF*t_6|t1 z$Ma5j{vN&9@?iUJ==e``x;oAl$w)KW3M}j^y$>H8W`LJ*;vL-|jwvLUv1Ot_6fq_! zwBc5%-i1g~X~L;nhzh-G^6&?(h)p2b+-qp^EAQe2I57-6>LX03 zdsuBIbm>2xKis0|>HKP`1Pv`Av(!97Bq$c6PlXt>ve9$&R_^6zR)($2_H>&!=G5G` zEAnh=I#+HY6;2O~0B9F5?8RONHg>KdzVx9;Xm^xNTet1DcfT~JV!5}kS4F=+3B^V0r>@z7KO!{-vvg_7ylIdA zpzgtsBzMfcYyV2$qi5R!-+aj1x5br<1j?k9%eosJw2+!pF>%S@WC@OB& z@q^bwo%@o^kK0?#VExWnpWs3!IEAjafsp8I$n&nzPkh}9v;#TJx5E#vray(^Lhb`O zHJLtL8rp7=m%!crnp=-ypC1;$2Q|Dt{>1=$gyd38QxA3*Ce&i6$Ywa4J8)@fiIwC} zL?<_hZHiibcL#7Uyy2iiJosz%VTw)a8vzpAR$#rGoIkZ)?|z9JRNeok1g!pdSQ>LI z-}4fbk3wbQme2mkVLox=Rxpq54D=wIj^fS!>PoE06h1v&_>@n}FS+*Zp(^@?1%W{I zJ~_CP$TKUJNT03{9MJQU>=h2o92Sn<_zKv76myNhdG;33ecCF35>sKuRfkdY&EAVY z7eNIchdofBBJtdm`<$;={KN)e9d!o5L?c|}lpXDD#wxzxBLHwUK*K2H z9=o~3nup_^IFwNnd2|8xcZk)yge&lI+lXm9>Bct;vOw7uYq994@SkExpBncHA?9nm z0C5Ak4kpR ze?I~T;1P?GrUzg+*bW*T_nf4SBSWpL8=%&RI}Xv))V-iEO|tcZ@n;IMTR>x(uRzdO z?9PPHtbrNisxm9}e+UfL9SlTfo~1-JgYcrP%8PK$A#L_abC&FAlbZ=|t^?5}^(JW@PRQp_#w@wipP<(=O-c6Yu$9CD*;v%P_w#2F z7>_^JuiH!aqU#bSF8lkzsIP<^=_2bL7#KJSbKrE>1+T(8$fSruV9t~+VcK(@j=v3J zHei<3p@}eMakJ4By6bKd{$59os}?dr9}vW=M<|LSKC37RWs|VS#h=5_gJoA-@ffp7 z06;-6=k@JD^-j_z>M_G z@d63-{b(&2WKx?nC#P~Vc{86iEQAe5-J$#{9Z4em?FGD-1x z4o{&9Rk&>Te?{y3=*;J{w|g(i>ud^)n7z>Q)7_ng4!!fg3+Je%0|1PcpDG;8^Vc^( zf&C$w%RAx@N012u1L*- z&#}eqacClZufHSi-EF}cj^bnP22NWSlGIB8P~@=*Wn5cs#Eo@B^9-;ULr<*B z{S8qI&+&u8*S}0`xgN*3xKu4)iHk@ zRB7E??+wb;;hdRO*QL27-Exl%25~by7uwRr$aA3Ajh~xc>W=7QfiN~P*aFLu%U+ZG z3ePHS4c?GesY37Avn4jgAMr2ERB-%@1dS_fw(qr%prj)^0m2k8C{iV#Hn3j;V`>bi zV|U4%Nj0uyoZ$>9^c-VBi?*VWB3%U`64mc%x~(pJT`Y{9WZ+d-(n7jt;wrKt4Q#OT zz*F|nQ)KtsyU!qDPYw0%60{i@wTbk7r6#Oq4SJI13DTK#qA;_lH~=EXfK9%=IWRli zSCY=Tz11bpWei`LXv|Q+RkuW0(uv5KI<8PoCJN zi^R&zV7#U~!*Oo{Wn-EWJ195cHsl^LC_&DCA;ccm0hA&*&zSvRUUnSPgqZSz&`_Kx zr-;^uNTug_eVHYI2wGOXpP|RiK^^`5YXk$3w=L6MuUb>$&X3t}Dl_78TSc;nVP}VR z3fwj#I?o|=&<=LX4=`&_@%Q(eVw9d|#{o-UZ=M#1!}Lm)QoW1x$9uMi;)2L4t~Bg> zpfQ|;)w>deZYDnbxLE#f5ulK0g~oka%c zcxEYeZ?ymWdF?tWIQHI*bhb>G0I|ge44nz4-+mHvgg$=(fS=Q&fT+DSS1e?3ilW!@ z3PYd$p(LUE!?}9qSw~E_vzr&_PKcP@OJI^3VYxo{)e(kcwbys_RugJ-#vhV!%Dv(8 z=;6@E=J?D$u9BB2C^lQK{0C_fl|an`d|6a(yKg2Qi4*Rxldte>rXYGvXZty$S`$Po z2}7oIBE(DB&!qjg`tX<<6hfHH^lc=QKxPCa7Mg!b7!H85iDmlbt%V7uP$9#?N;LOWw(tf6xV3sjGRcjU&WxdqoG zrJFe@I7FRBn9C72zO)BhI#`Psm62xM1aS^6=99*jR$N9m;=!kBQVFcyrgpdq>s81_(zACpWb%tgDN&V^f47*!tjzZbgM>hu2+1y z87e{N8cv@g8FH}SQ^eiX4ms^@jW85(>~m$&DmN~@i(Eq>qOFjD4>FM)=5z`@)Y=!3 z&#}c)GCqKTD)Cz|5?x;ldM4~B3_X6DSpc zl{gCj6P5Mmdim3)grP>}^kL@vKEdiXX&`l|h!a>-Z$M#zJsSqw00QR+?5;LnteR|W z=gDy7#!5_v`@rk+o06E7I4@$hmOuR)PB}er&gCGuGI~rcPyt8c0VG1AktArzftV&7 zTosWe1S5%b`Vt3WM+C@yV}>boy1*ZV7*MfHp$6g;vK4e`&-8w{}vh?wd|ZTKomvF-@)jDJ#8t4?)}a(jMF+Lx<~ zIhXVLAxWg0^tzljno;MzS+!-8yKP^OHQ1e?aUX1+p{ew&;8QLVKB0TbSJu0)04daogps6{+!<@#DSKe_cyn(* zwDKL?zOFYS2`#S(od)Mf-ggERi4&PO5$yGF=PC}V*iiT{g+~$ zj~NDw21?WIuw;Q&XwV%AX6dNerB^QH?W9fhMDs%#k+nXvYm_}9Av|2a4>j(J)YRs2G4rSgQo-UCPDhJ*C4 zGwkaYo{o{7jgbCm%j*MEee2w}aO_30M(Q{k1RY)DOu}z`56$}HJbhY`qQbp(!oiLh z%4)D>GXLm)j8|lC3ha{y4s~{R zuGgSF2yFwi4q`hYA6+d8&oab8bIL)M#v2(6oEoa5EYpYGy)$`cW=3l5#)ET;4qlWz z`r`$Rc$Db3YO^}d>v>l`8DiSsL5C}=+8f&H zGN@_tD#wKW>7JXL+tjMc?Z(#)ynsQHI2!`4$IaY!A$`ABLOTC&YesLjBj@|8P>bnl z{lYwYio-i>2oyytu*OU4bUD8yA}LZM-UMIJNLNm4gfYfT7E-%D9l_j)d>URN5j$Jyj&#V7iZD;Fp ztONqP^K$?n-S2UUm2)~388ZdwRl%7u=ge_9JJ|heGRcQqw%d6US$17tw!j(|X=KX# zn*%586(}Jc1U^KcG~G^^O27$uxpLXf^sm|W>ga*6>M^wVlYnffiC?qv5iI$CFMz-; zFPUTb=*ql07`G7E_`m#W%|dL?OG!L8v%C>yBt(~`5qJ4j>=`m6fO;ywV?obNMZ&uU3YLweaiB#|vtpO<9-($H6*?P8Ye4a96@v>Yga0 zK4kPU!F|K)j47^GLs^? zogQKUxy^9jjStrPwtH`+6igjB3lyz3 zA9+wBhVSfZV?LBJS%cJSXEI#pm*~q7F4woRy81f|Cj?^Vy88=?9R@Diw=A&JH{GR- zsxZB@D>g}Fts&4skS8Vj-p)`8F-Ff-m3ilWMULf~?lyKu{4Z%%WE}K^eK(Uje1zbW zeZaZ6Ja((uQ_Y#g!CChyuT5esSz5DynpHW=C8{Z<+RjQ*BuNFgyjpG6qj+$X17c6r z9e_M)2(A7lkBfH?#jx{?BU3$VM=H~dL+7~=@_t3L7WGa8^mJp;CJ|}jOw;mrrPpmX zi=P{=EIqb64~(pmOW zs!(OVe*%e%U+vW%foiMm9D6^IGQ+%=ddw4-50_jA8Z){=x6x;EPIaWW9FRl*Iq`Cc z`C_FO7wyG%b=pb9_rM?pty_6@Z{K~|m?(_EhVu5~fsZXd_pnP=n|We(o&O#|n@0^> z*KxD9(dvx00ZVk*iVs6iKL%X%A$YCE3(y%qw{M;gfO z#*?kGNm4uTkt8l-Nes+2< zzW=4L&F}1Avvf?@Q^rU6Is=^OQA1zsnuhtU{fKuZ_mS=qz^O*s6vva1=tza{ekn^z zk~>#7jw#XXe}o93Mki5fGjU&hsk%Hx!>gMWBu2zWd}Oppjl<-dx`t3D`Kj`Or!kW& zULom780N;z7o?2h{2!@qM=nJ&5dEj3)?7#B6K7st{`yZWh#NpgUfs9s+;7wW@p5u@ z3oQFb9u;<5^ah9LQhm6H`F-|uzU}awX*{_2U3_m#6b||DlE$)p&C!#4epeg9Za~*3 zKr~OqhgstBAUhPqy4ZB%vaDH%%0(97UKg0R4e0BIn)*%|?vc4)WJ?GII#7eLtD>M) zDB0GwQZ_{j6Vsu-_3=5>FF!Z4m)_P`S8=BL!!oP!#&w=D`296>e$4`Lf%KhaqpDm5 z<0?aP?WdO+`t;I5DY>=z+bG9!X57|)58j|Y=1jR6saXErm(9@mgG!6{vS8G6GaU*Q zR@GNLL>&5Zl6R*xBRhNFv6i=cVv4SG8qiP}xGXE7?Enu^gVrvin#omz52ZBc4pO(n zn=xMGIdiRtMK_NM{r|1knSHzTXggi}5|3VbcuD{t&%%`~0ayJivVp%iZx21oqAb6_^IyYAgJ!xU<*ji2Kr)B#%}JX+bRs+M;@mk7Ybk#E({ zOx#6l9&c;uN-5kas{1gGT0j|)JKbNSG_T@tBJpO&ICEs@m)M#7enpq4wO;jgT_}7w ze|kuN?dfHi(vd|KSj{d=n-H4r$TvFjGpaZ4FU{cA)C_-hB%B^kcitoRbji_Km0lY57lia{x)dnZS=Qkq>P;>v1)~% ze?x0gakVM7{8eH`XA1JAh$m+?IFvmPF&D7>=#s{zLB1dUl3Jm`zKKX6#0fbSkJDH> zo72TM=n!7%njqg}4e%sZbF-{JA;#?fk~WGYTJ03o$r=bUHu1n6mzLly$=k#E&juuG zbXFNni@xx@;3H18ckt`X%I)2qZ&J~aVlC;p_h&q-jW4oU zpeS%-CVE$UNAG(^*yWWEnj7`E3mVNQ;?5^eN;7m%CiSZhDx-Kuyo&WTM!~F}=<2r+ zcmAmPx7Q9`qcvZ)QZv#k`_>R@+~eqa;l_JwAcybb>R`^r&j*;bM9-)iQ$o%me6E6E zWI?=Ig|BL3wN-QdAHtO~%oFgB1O!RBcE#o=DI7(91is~gYDYuk_Ua_(;nbCF8{ZOs zSDKdV@n4&NuKPydhMJ4>AK_2BIZiK2Atk^dOc~#KcBTiVwW+gz{NgwMB(3UP*hZ>E z7vlb6BMk+q<+k}2-<7?i+!lpV#+Wd}Lx=Z?$Mii6Dtn zHA9o;rQ~UZES`W7gf1*xajisz?b5)eOk`A4Y*HpHI3$>?OgvE{MehAg48#=fdaZiS z6LY9HwEXo26oIp{PE01wn=twjL2q|dYG;oDco6JxHZ)KwHqFM6F)2Lx_|j6Z`SEHY zt;%PkC|(om%N_2X>kmvK-0OapcQ}tuY^QNKkuyqoQbT(5fSq+_*kUVyyu^IwaZzV& zC?;*y;V}CaW@KtTP#jw1I~=UT;~ekJroIpav4=`16R;@+MCxiOtE~)bYOkj!)zb`V zeR{lBk~`5dVjz_aN5*ACh=_4`^i}|EwsiQxfdSV{8a6Z1 z0Dyf#(C)Xlh}&hs2_mkiOiR@w*yY#Mz5Adsf~5+jS@C8u59T1WdIGc7WSL%ZuG%mC zR)bsmStm5lRxFP1zIjP7zFhZk;tzgKucO1e0|w(J_oYMnu%ze`n=+)sZt$HYHtA{t z0)qAon<3>(trt1Q$u;Kdm_%BMyIm+O_}~8dF{dJh=VpjC4hbx@N-Nh188EU4NFTV^ zNcB9nmc%fTHsDM+yyoOq(cpV-u~+tV8_P>x-ik+g?v58Np7Wc$w6M+;SJrp3{dQW( zc*Re28(0^co&NuH58aN~9C=j(?^2ew88t!tBUjF)i{Tb#oMy&KRFR8P%m>|+$~D&Z zOWj)k)Su8_MT>sfuz7oXkalydlB+G??_=51JswCoC=~R%GmE!Ga~^kaZ8U#mg`Xhgub~OrqrbX5{KKl(}UUN$|Dw7opzdr_=KFp&cq} z?~UlO5>9`owwB+jNraiqjL)E-;|oV0>@F;<>+c;|tKxK|!*_=XyI|#_Ju{6tz;m?M zOd%G#d=2?<5cUs=Iac%pMlq>xvF;~`yqK)r1Wln?RW7N%6@k>sfa=dvUhk9ybjtpi z6rkC$;T|1iN9I4h__ZafOz6V#%Wx2QMK+zZ3*63rfq`+{!R{hz^$L?DA#|S6?LM|d z^euv(YA(P>n!Yr@j*cv}8!?cUO0Tev)}9hnIUb}-8OkfVY|6yQA3=JSOxug`Ki!;qS)y8I-#N?iZN0F2ldg;LjND3W1Um@B&9#pFO^tvWc_NPttRXV(l zEsNktbNr!XG*q5>JIXQ z%9CIsZgwZrtY_I4#Di0?fgcfnlcncWsYxlEcD9pCoKJ&G|3ON4czA{Bv(E?8|1QPP zOUKsJ3{(o^5&w?Xf6eF1n!(&oH{KjvzW!3}A<=iCsFv@pxk`&bWhk?p&!qHmUh~Nc zps6tYwaIyK>1UNF)=Uje>6i28!h+V{jGYo8!`er=&0dDzb)Qv&L!9ex^SF4vvjof- zBLbyJlHUg~@x6GTzGF51`2ML3_5@@Z&x*r?*ccY>*++a%%Ems4BNp|PKf)XtrD1s98@TaJ1z zghiruc5uU6@5S9K^t)3yM$3Ow8~01cxP_u*;GhxdE?%i;=%S~1LIdd7m{lYZW%U+ z2dJ`Jzb2h3+I=s+G$x$w>4{5o&n!CR#7Fq{NJ^H%-Cr2kjk=X+*8sZy8gyr1cnYqA z_p~`(RG4R1p|dq|JZjSv0^EGVX3M_f8?!tojv^PLy-4-81jF<$RWCr1P=>xiI=4|V ze@|k1b5Ff%7H0WNa?6f}Ju*yU_Mf~Q%gLX~rnq%^i!UweoNRZJp43_zX$#zLaI(_0J++O% z)eZ7lqZC51svjjzCp`DX61P9k$ZGiMG9B*03Ixzj^X>1N(r6IRt<0`c{w0r$1v9lP zZ9&JE|Aaqa?xS?dfB9E`>ncly=IZi;Dfb1=4-ZA1_eI0+Ou>aV+2RMM>0Bj>5E?uJ z(gMTMYsng>7qdegFZnRf$BQ{sKOpc_F~EFaNXiK@lYbih@-+S9{W+y$;uWf;gyN@{ zKKnY5sB7x{N2H?n@OuZMg85sDtb&eLGq9j^a_4OO$0lQMq*-%Jf+lkZHs$o^1(0O+ z8C>4!0sGQ2nJpjuiPr(d&599aR1ZK+Z%}+ zsT(;wTwoyz=qxrI#%v4i^sGzc=xSOrZp;UPs>^iHV%vfXSQ5!27AW z28x{z?`Eb^$)ak>AJRCX0sNn?wkcG{IhnaCq3I-q9udDJK1?%{eqaZqKAmKLIgbf$ za_yz|zB%%3Wj#)|N#LVQHn=ZG`NC#L`s;(K4^S;C{bH97xX;A0{rqUt-K^B~mg|I{ z;4HPz-qz7Ov7I){z7k9<;WCvZjH~ekNnuKu%$^1YqWHM{bZ_t4Du%;%=Zrt&Qf18i zwl}1A;bQ+l-(SDp?13sn6V?HOfeTYB-dX2@$6uB8T^D{p&BesuW3 z(KBPxI$jWPt9gp9M))Fvr!-g4-dCqA_|f6yBg1e586CsbbkPT{;5N?$3IL%$%0X$E z7(CoewK`7>p{4;j1oEW7MrF~DrI&X(I|rg7oS`f}1CC7{Dw0?2TI({W;OKJ(W21rn z&|C_gT`v8@RCVv;<=dYjhw(4rQN|!@(a~}@A|S0)^8ur9*42&M)1))K+ts#>A!~}% zv_Yx0!rQ2;m)z6YdseCF*0D(jBsInVR+75LCGCY?O8MKRO4Qh+ZpDdy4LsZ}eM$PK z=w^jJ#{leOThxd$qe9 z{QkjVNd9!e8FXBKOuu;o?a$unXplh0l}7S*)Kl5{y?bhP<1`g@@`b%5iK<_@R-OF) zPGH2C#(A4~0D@yvhUv3?!+xqczpr@~y%RAXNdXp;;N@hLQ77-iL?)PbV3;_JIq_QL zmVLd7*3zTYsQM(lwX`Z=ox>`b*!S8&V&>-$L`un#(CPPUO1!l%u(lLt@?E3hc8M<>o?GI-& zOCHOssl+q?yN4dxr`yyg>p8l{O?KmN9*zD`RJ&JxdVOZZs_;2}14;Q;yXkA~@~n-A z$otuUFIvk}>6~Rv=-X^xHf~mnfAhYl=1U1#!;EH`SgZhy-HkuK9~;-}n%_N8yJp}x zbIbD1;jOv#o3qvf=E4_WtMLo}G!-w+5-F)jt z|9M%A3`wf+cXIskk=vFf0sR$`4u8)_S601Gf0zhV2L*%IXw4!`DycO6oCM|EZyL*d zwc?46%$nWV75}pXhEsW4BK&Q6tx?W=tr;KPyR*eI$0dhUF7~M#A76=HGNmV6T+ipZ z;B&e`5+_|1EV(oNxLt523Z2IO@a$jJt3fg_@#Sy_Z#89`PynRw@PsdY?i;|@bX`S^ z8bnq_g&VxcJ{dJECXJk?#%9}9LIY+>;aBYa0p&U0?Dfd-IIy30GcSE^Bd3bxOb)r12+VAG(h1)iNQZ7eO zxXEOOxG3l|E7l+5{I1#~8fLv5_3^a%@jG88H4Q1Y5+5ytLU?`YCw z8<+CfujwZmvXsz0Ew4Wq1a4;MYg>7&{Yzn%F^<&xa<7fX8F?Ix?y_tYz3}q>mMbB? z(xYHpYI2?Nl}ETYc>sx;bX6<<$c@}bkWJK_%*3nT;HoxdMwGxcoc_=Upj?c3w8&&{}8VmoK zE~3dg!AD zqt`zB!Cj8w_C{syrWSUYx64u03%R=$HqD#>4Hw)k$Q=JF?ewDJ+os~;uaoI}^^s>j z>G>~CVq_zZegSj+n!NRE``B2Ho#U;z1T{qsu5Pw5g5Px?OAg@}`&7%$p1T?CXsoRQyLPYMWx;n21Jmmp`8dtf2#fAQ@~xp&p%}$j7_sY@@nm~Ucs-hk_Ms*-!+@33e9un<>h?x z$<%5W_)lb7ZgOjR?ICI zves2gE5=JZovKPm`K~8FVb!?8$^TYr0M^h z;oZl(-GBa0eCg~(;PRW*TgD8M!`4jSCG0M#Jq`)(oBrNZ<7WBOCi?zdQGA;0v)#a0 zN)i)PlDoDx2c?4k6}uFo-;>(a>SBcFS3A$X?J5?Ak%ZKWm!9yA*yoPIaKZqm4*p9E zX~_vzR@UX!TdocKn#tT+lyx$^$<5jgo=d%cqd8J~E6o)q)laEj#RdfjZM=JBq&xkp zd?MY*?Ww+e$)}GM4s}u;9!uG?@##-0jP*T>2)fi+9AseYb@X?`jb74Uy%7znGCXH$ zU;LHw1mtP1EByX#V9;U0nCwzT&?W zm4UGj(zw{)GYIjVZc7=E&IT+(M&`4`mTkN7bfjGHxFa(`bbV$bZCy z$eUHEr+4Mu<1X70s!@3F6Fisl{p>|6_3>V8x*oMD=TGfwc~SpfpL{vd{0i2##C6ME zMZ#;2_7OZCsWgqZy8=#vmS>EuEl!PdT&>AU=O#8?#*23U4Ng7nO8x_cj@H)}pJM39 zWDVm7FC*MAHml1Qs2@D+k|&`;YxC1R0Kd#|xJP;4b??-Gr*A$u8{YFhUdM-nR4@x| z9ougCdV#2v?lL;}MKP5rtqlSp`|4Nxc~t2WH-d-z({k7E4vOUDYo+kfh%L^z94xJ^ zGD&Vpw@|%pa2Q^8mUuK^{|THJ96gA^uR{QTTTv?`A>S9p$3xr^bY@s9|N9|ZLFVV# zc5f=O=ciF-Sz8i5Nhp(h4KCDC$mE?xIq$`VHt~R8s#TwASj}sQa(q@lGpG^Vs7Y=* zaoM=0Gjlk%qniA_M~m2^T5vxHf* zJE9LOl{o%2(owKfNG~YA=yr?3tH@{0#OBPFW7a4Fy&>>(oNCY&BjXh9@Gc-N z(H}0i#HGRJh^x!KRrU{#*l>Is0Zu2m@_VIAUKtLO_J{EntD{;QLD%799^m{Hi zPJNhmP3GZvg)x-1^6}-={f`nMosN(>qAx^#eBO=RsN_Ljcg!SKp zsG`Be0SU?*`QKXF#}n6o+DLF~D2a^sQAyVP?M*q(P;<1DX3w5kneRw>Lw#i1`IfC> z=HP1gZtv6Z{~_wEqoQ8huMgu0Lr8abNQcr$w@4^0T`C{~N+aE!1JaTbQW6SM(ujnC zNDB%`3MwhTYdp`p-hYm3opaQgVZQf$#onK7SM}QBq>!fO(1G;V$xCKV-P6~2Pb^#q z-$G=8xPAS3<`ap1F2IX` z+0jUaYTj{T#Z?qGpmJ2pH#|$xr0A;-IV;AV;k$?`F4MfH2KL-&L7QJLcNZ2=Wq$OI z=gLkbY`=!YLaoL->ULX*-qigXi2r-=_kQ}P#*-UU+no#5qj=Xs&Z9r-T4|pDZ8Hcj zQe9{koK=X%j~K6}CMw{p&fK)9zh%FaD42ASAnwMn0I#{^n%mkF+V2&wRcwssCCRR} z)7w=3?l)$QC5+&sOxUN3SwC)&ygu+Ab4;Qr8Oc>%AA99`x%RUk|7ht8Pi|squeOpQ z*Ac*NB5%6^howV{;LI*nlEJU}YC4K!gmi^!vakIQ)^u)frj5^yhcR142fHCGz3 z2*Nkt#PuO~H>MKyjqiL}zCKAXP0KsFB4m)=)a3MmD2GQiH+1KMORkW1tQP6e5tVL( z@R5Sd$*1kIkpaQzSuE6Q43GF=)Y9edvXg_vui_6kZuXAqzMOG-v0+CAj^OG8*LR8r zD?Hbhcxu$X7=5o;k-Xh*#H*AaC&%YK{!CN5Zv#;*bL6DUN2c?QS6@LzREF*70*mNW z?=EUXq*6$cSN(nC=uv|~5x#04;qe%y`5Rxfyt>-m`5imy$7}ICVIQOp$+~$@$B#R_ zZvFgK!F!Rf)ld4c;~viU!)e`X_`boo9vUS-_sK{zdz82JdFYaG%vFZ8TH5|AI@+g>HNL5d{AqK3LH59MVPduNSl=~-^XG(1x{-K`&^%87 ze_@yS=I31bREF<=YUgRcULjW1IyBShW^=P>73>+$%=AbQU;i*Oyr3Ghzuvb`E#BT| z{qhkT`s)2zmHGMmIx-o~alWDmKP46(o@g*p=qv>6C)_dmyxQ{^RFf+}7K1pTlsJd# z0O9q$vp)-lAT+?sBy}wFNv#YPU-+F^_z;j8<|-4uEm@sI30-hqpb~>Rne&vTO2YkW zCok|GnOwFK;=Agz9+e&AO~+hiHyx5(7)|z-J7P;GOCap6sL0lv%CNd-;qG8NLHXO9 z_H1V*3VhFQ6-B0B_lvFCYz-nr>EwvV$nWe)y*MwbO5Z6|CM;cuzM2c(H;h%JGJNqpSKkW14K{abnO+YWL@R zZ}R1pUr%F-ibA4O?&Lo>MDw3H_Qka+_!;72+;8{TU;5_c?KVsG>6KZGQGKdZ^=Fos zK^yq4pPRDrDSYfc(xz3n7eIy!(wZ}*A`%@Up}kAm)=#YHE$TGJ&vkiqnU=l)Y}N+W z)`dPT%B^N%X&D~%_CsWM=iA#d0voaw4T^Vs)_E-1lF1Ia%9hUmK2sr&TxolBydZ9= z<@ji0g?AXhHmoPs6h+j4IAD~1luY&`gW~D|Z{U0fn!c2nzuccanDTi_&1=^C#6o5r z9#@`3w*GPV+;L$Mc9|B=kq?wMC0qZ@)APz!p%4&ZY}Zh10nb!HdCh5pD$&l;t>Jq3 zndrpAS)3oedhWX^Db2%)_vOz!-Gq{H<3_Y;{uLk5z?ol$M*KHv=td*A<{GoMZ?~%6 z{@IeQ^Y!Yp@yin3s5c2%2z{M(vHaJU7b5Q7e5QNyXWOZd&273hoSw}cEYCqq(o(g% zZF*mdWLQ|b4q`7xj%c;U$`&zcpWa<9G56l(|!16zF}TUpO+PIALtnZB*8}?j!n%XoP zyw7@x+x!&M>&6YtI}BeHlb`ioB@Jdq73)6~n9kt}xSAvVVoSp*Y>aG_4qmGmdlw(| zSdu*a!|wat>wK#{w;~;@lA?TjA7WxKcXBaHKD?XOx(}Mt!xyJlN$n|r3#M+qlF@K@JrRrZ1!gFF@eJj7$dr*JQ zmkrQ)e8WU1&qJcTU?%X!i_0HNAjSse@QKlg&r+)!R}h0AD7faG#rid~@;~)y^%|US$NNEwk4yQm*-bgsvak=2qse z6T42U_m+^P0r#a0TzkJ6c`gTp;o8(jn(`fI0kwr*{!hwNX~2 z)u|9`lgQ@&ZIqt;7LnUBkH`Jt{ox4D)*<~kc&Yk2={zaI3sMi3!_ye%pMx0BHAljm z>1=<%gN~Q;mH*$-GpCoy;#*JrIw~S4@(7FcKa>GX7G>CUvr*x1!^~Dko)mVDe(0zOR60utjHDMpfS7xSP zKCG$^_%#}zK|rmBJMm~G*FhPz`0%{;sq($w2Im`cD$9b@Z>C&tp;OPK*1YN1vlS24 z=hP)#o>7WjljbpdL4TV_?i^!OOkk4BQL2qX;ePSbJax7hV6JR??0WqA!L<}q`Xd80 zO~@LGMNE<+mWfkZ@6~3)onOzjD-{!nF(?eLm2uLInHN3Rg1smbjEDx37Mwv+LKp>rmBu6R1@Ows&c@9AG>VCiE(B?JY|Kj#fsBk;M9rGC zjV~1{aJ+H+yBsH^@-z2Y%{~1r5xGI`jso4%1ap3ShQIet*>X;jpuuK9u&}=C@3yB?2c? z<|YQA1d@7&=c89Fl)ALqrW>7z6TlDedAk)^6_5cgi|cZ)*A#79-1Msyw<>MkQKgIf z8XVE-g-rdu^2%9jXAd*`#yMbCz+kQUjYZqI5nxg%2f*4y|8;sy+|R+<68p~sUCpCm ziekhnzW%(yMP6@fd99RKPimSiLewRqB2U~7)X9ma`*T;7`O$qXUW1K4tAyW)l|}^*A8_T#P^es@ zyINQ6`yz1Q(M*R^i^=CgAC+tmha!s*UtaZz*>v{Bx0%DA zMr0S)TFYC5lsVGM;!=-_r-=&KReWAPK4I#MV@~=>J$Qq1_*Nn4x#I)?wIRKIDXt$y zLmUF16uHqE7?KqL$t7%w>&3DM{YHco7n^c_9Jibo`ld4cB)Y5>+UmmBZ(b*J5# zc@6VNSMvJRcjiP(0`&eW2}O;=v|()~n~x&kC);f~N}cXh_TPp>j}cndiMdidbtWv7 zd`3F$n-oHpxnhp=9!;js?5w*Wn8#0J;_!92#`#^%4LmFdOHs^ZT+W}(dZY;RY8*-} zBuAouR7Q2dr-9)wi~DZa<4aSjd?r{eX-MFMm3;?nYb+64mM~F4aJ}vr9Yd6P4kqC=%(okA!nV_GNc{2WqYz6Ij>}#yqdJcmb z*^}QiZmfIQmmElm>g80g^IXsLTFK(Q#Hlq}BwwQ+*!X=fOr7GI$bZf2@`BKE23Vo2>|Ooj!Mu%W8~aSP${BpC^r*dGqEY+ zGEz_R&InqjaZ~7>aDwCuZ9mf42kE+7lDbaBd0cPOYHw9RLopLiC*j5)#5ZX}n{DPM z8@(KCvJB)~tk?z6JD_4pNK#3^VN(mf{v0ZV+c|QP{cWvXU#~>iHQ)06qW8A@lm+tZ zyGP&rFJHT_mCI58T6#THd{E3oXj5>m!&iN#r_br|ymNuPvv>XX3q3y$UTP|>)h$&f z1C2K%ye&uyT*)|FPB!psgx6JKHo1kwNQ?$oX|9(ydk8F$#>F6m@;dqLnvyjtB{=+1 zi6|Znco_c}kkcHtt1Vhll9mC0+bz|mrj}|mVidB)cz`fcjgMPjoS(0tlS~)vnTB|^ zU%RAMY8nlrx#JEH-?%NC113*Wqq4X;wQ|s@9NJ1TC0Vt`LRJ&|gH8vh>teA16ub>0 z3zyoIeQerzKU-PJJG2Auza3_N=pgX#q`SJv){yN!g7Qc7KQ0o?iqzzAsu(Df5why zYFOY6_`k7oGQh!>WhO~~3Obu%@h*!wDr}C7&AC@C)3eQK_}(sHs|mNS1G90~74Ga@ z+sClyZgTl7TJKRH{qU}o>Ahj7Y^awC0Jargm?>#>N?qJMC2$8n^`#tm+QrO>~EG z`$T|H=K^*fN->kK{R!j+@E<8-9S-N*dm>9c!#@PF@vf#!x{y&|z2McHY3GfmCKM9zMa8WC+YReM_|1y{`~wO-s$4eQTk z@KTa4)02Da0G|x+6L{OBGq82))>4E&V(2Bb?XU|jRwvC%046y}R2Oh&8r z>4aJGfMayIxF>=#&s~&Vo5QjULp}u>Y7sc&Zdc1%|H2mHMZZ;%)NF6%NOp9dODW_4 z;dvM1tNy^_YX}X62!R{?6rD9Vek+Wh3~8tiA&g7m@U1?8Dp{NiqjKf3ik<2-E2I7EQ#{7UT z<}YrqSNeLYVcUn-W(ggII*B9{x+X=V5RJqBq18nk$)9$`&q$EPLqS17d??{u?DgKO zLRVs0S=kC$??qnn#37FZ*UVi4ww5teML3ob(WX9sE@e4j@f)u3z@?~;X^V_F$~SBt zk~a^#|Bf`|Nb^K>xd;dgkGBn5j5GmZ*>vb6-8A@0&v3-6#E9#wh_O_iZwerBcXxsE zm$jdZ$sg{8igImgf92W&*RM)&eSi0U9DdWmT_lgQ5$`r6(TkN($+;}ZQ#3a*|C z-~DfiKbL;~`yFdIArKvua;_wSXscw2W@d(QZdu1yHxa}JJ62i+0B%VFYS0$SU4wko z!s>a#Hlg>hoK3z!4+}l4LHNqFZ9{%oxbYuDK9&xA77HsNAZiuvm#oS1~zpV-B0+(v@- zSoCdd!c)N}2Ri+VEa1RbGMR1>Z6ihi8L)^^=25bxSi^9{0oY)9Q!OyS4#7;KeMKI( z#SZYOeb{%GAq3~eGKqCEw!9f&8< zH!(pg|H>8ETwmAJ@G|ZU3Uo4cmx~}p5fOdM(2o###Qj^+3T*oCh9gEnxTsQyZm;Lj z5LGUgl!12_ED1*pgf8%k7T;!eDZ}`nX!V4d2<)xhV5x9)bVPf@C)30m)#Z&#+qD;3 zNM)LN_Tlck&yR#-Y}9&?I35uo{`<`mJcMZ1fl6UIf7ybI2r21g&Va@1gO-c5M_{a} zq7MwRF6o3Omp;L072)f$JnkaW;+gQ|R~Qq_*gTKR#6-W|ohnNZw6|fU!DeSq2I>an+f-Fr z=5NsoRd3W83ddYFW$=JmwY~>{za0T zMjC<4-}sO7v_=^MK~jFl2fRloCqHhfWXAn_=w6X@Rnb(V08^q1d!NQCBI;O1?3E?O z?PfXu-_akYrnUF=rAX_{+Jb$Fd+4DN#}pmLND?!WA$gm-z4i%MN_&`p8~lJoLE`{w ziQVCdcxA_I8SnCpIOqtbAm2)mU2C|m!9XzmPGs9ZSPcs^0?I_rS$k+vgqRH_mc)Vk zOiD*zHLk{{@}=>xhDOsZGXrCyIyCRHsscQ}N1YuL8G>rHC$B4b>O9LTNE4p*m)n^c z9{SBLrZB3>u8nY9!dT5`Q31d&YqIIZ!6zgXmms<7mxOZ0v@W?qlq+M9TO?r7Itw-AIip9@FH92dO&Gf(BUH>{jDXc=k$R4q`NV6(*>LVp6R(Y6=8MKxrz2cJ(P> z&+bmNn>YIcCO<{z!yLnEfvm3*m}x)#LOcj|PA+ZeD4Q(aIZ9uTR)PLTz z6T51;4DUzb)=un2`zF!29$oPM2D6{S1HEqHP+N+J&<)x*BQL>U$Kn z%KE{<0R`THI9a#AlNZQn4RmsX*Lx;wcz9I+_rD!U@Kpx#wv>TN~W8UP<1+i^SCS@#h3Nr0eE16zQu*uC3pooQ-@LX1e&o|7xID2~ymS`SX zSmNiC277dK=?}*ztG2oS$sj2F1myufKK|r&T}HmuVtBUgV=h&Xx{DdBDOjPp1gBD6 z)G{lm!vx>GVkBSgl}NZFFVHbrjF1pId>w!-t1%5G`paQv?C#*yPI;4I zESOG ztOyLhU-$&G#Ap%1F+cbANZZVHK0dtl@&xn)6GVK}&$`Ym*{dzuaie}*!=y>*38Ujf zf}!qa4X+?o%A>oWKTsO3X$Du(L&p9O`Id44hsl7;vA&@%`1Ar&*hnjC6^>vbn@4~Q z0rXF*o9ze0>sp;iUY^(xgYeKL6{VBW#0U{T`cg2l8z&;{>`kg0fV`@3(<_vevQ?#2)9` zJd4%EA7H$W`6iYKu2`DRvj(giuuzsqemUMOHQ! z7}i@R+->6QEaV6jQ+?pcqtK)1iLdQn{X}h>{RihN^x4e-u&DF&F5y*R1ajD){soE& z<^J30mix@No?Fh*1lOfM!wiaPl@M}9g6Uz5eAMui+^WUS^HekF0A$u(Hcmcj(cVPU z%TL39UWeoJR;57%341m~DIl5AG9()k++(*+zF_g{ubu z?5mn65u6FGIRSPGM~}qiUkC%3jVl!!aH+9KBhWV$?IGSYR`uGOPI#_1hgBo+DOGBD zvKPIg6EZxoPn?KUe~?5*3dEcI!Gyk)VMlHu3EX^;@x}227lCbCQwe9^1Inq@2WXrn zPW38NK>`pk=a)fPO2uRdoG zRL2HF2b-hP+XR~yr!gIOt$%&NzdvI%UI8*RFu4mDDu=ZDVsLiR;_H!UEF*!%eG`BS z(*pQs4G{ZUypNx)x$f=!A&urfGCBS8SMYBi3Qd4LsteQ6^c;!%ZncS3v*xpF0QM9& zExy%OHJkf{H8|RA^U0E9*fSe%5?Snbj6AeD!>m}$t>6@%054C*y>O{7DqVM1dmMS^ z*M$+NygO!=-eMqifmgb}4V)Ti4`SvdrJ~n-Vz-Tdm z2&r6!&Tn48HWH*z@P$n%o78P*=L*DZcIhmJ&J#DwfzGpshF_B425c;oTSspfEAxh> zzizdC5%UzcUyjRrd$xX<+H0xj;$_SB)+Mob19Sd@ODF{N|K-4NFoc$ev9jMGPhk=8 zX>7C4$#1pA#-Zvwme^iP2dn-+7C>4r(WUgV>O8PYeBQT#;W+zq(iDy<1-8kyt+B+p=8IzzyghSYGME>hD0eG2=;UC%SNHHyu0Qm8rO-2x zy0i;`Vb11ysLK{{8#xF>f105ieCR0Ss=`cm(8}a2`x?&ihfs8uK1VUOrC2drdWXA6rbH z3$}oFYytd|et(cf!1~GyxPlM9CSAk#^g(~;RRz72LeoC=l?eViuejx*W z-bY;fdAm}J!O?=xn5umU1ajZuOLzU_y=_5u5*dVW1g)eyO9Jz?e3G)vAxKvmAeG9P z=FsYR;IyhFSN*ibzSJUJz&tZk@HSls!}&kLD~m3YB%~Vdx+X{>k{t1XDqIJ}aU(w$ zgMlCP)N69q7kij@a+%c~NWCuuPpDUZ?vL$M@t?f0ma4Pm;sk@;bWVJf;PY(^b%x^ z`;L!@eiSBoCcLNUr3kcHVDh421mkpKP3Ujc?xPqjZ3NY+jT*VK$h@mC=Dt+J!0(}Q zqSNvC8Vg0nnH?^pt#HET^|u9?jmCjzvDq&(9a!(`)z%%iT8@s^I`#`TF&# zz2WE+QuljEVX-#50iu+*Gc_{D=VPo#lhjrLHj%Z`bL!|s)}-X8;nd@p{j1YW^&cL- z@K74++dud&E4h)7)kXT2*>S z-oC+R`|1kPJx9zbG1|U=IKnmF=Xn^)+VN7+9!dAV{McM|tWP95rZBgz{v7CAdanHCVHR8D+? z+(F|tPaiL#58MR-O0dU%-;vAD!oKK+Iz8Qgm(G2I34W$jyi4)xZa#ND7XaH3weSQA zjyt@O>ofhs7oVuyk=O(e9oeZnf`1Dq9AcK@gTXpXa0lQlxrKwMus+e3j0hOwQ953Q z?xzgK0IzPJ&sHk$w(wZJmR;bx#dMR0dULc!bWJ~}(Lm7p$DR7VB!k4xe+~F9RS3MM zaaaxsE;o114O!_VVd*G9&KZ*Iix~8Bl6%sSHMRfuh?@pta>3&c{ z%%=#jSH3v3gx z7vdu*FW)5j^sc-h=$V%Pti~*gMO!lM5eie;tEBK5lte#LLN__szx?y_r|6xX7dvps z;3svXMeHgd%mu%tXLD5ntFg8awjm?kiM}vdRKoh!1h2aE`Rn81n`eoYjr0<3B@l4i z?`5)gCakY)83AQS^k>gL`W2jNxRmrEs4KKhD(xjq4Xo1?zYD7ym>9!@XiOUaRNgkj zPAymS9L18t(r{~-_J`?tha(>Rja(0f9}D}Bp@W05z+IcdkW80900o6gvullXs75db zyoqfFoT9=Enle`jsDUx*rRfAyp@lXBP}ai(PksoFu?WbW-cfn`r>f3lPoAmaTuCqn zz6<1*@&{^z<6ApBH=wlr_{2G9Xdhy(Vn~v5@VX$gH3SgmdO6~E)_@uwnO+c)$s7V& zl4z2WpPtQ0>`DokglEk96Vl$Y12bCFT~I7q6h`~Wu6dxrgz95!t0t%y=YIXoN&`%a zAvweHZPu~zEquR6?^RuX0P*jOzQGw=>mF-NK7+RPhW0xFQN*!gm#A+taK6q^eW}i| zUBH@qC{mdYYoF^6gztUT(1|2zVj#NpL@d}?Ft-;|r!41v_~2~b?yL^v^TSZI=z(CGkYODZeWq_`%6F&yqrXg6kuE!(1Aqm>& z(d;c`MpY7}gpN3dmmf~+&Gm^iSd*fd_}fU$1pd%;0%B9w2iiC4O*jIW{>o5IR+?{k`m{LL7xAg zkB1#$eA{Z7p zsSV|OModWTdx#-Z+9pz=v2RG|f_DZN*`#5PieILlME!16f zpRJ;6U6#RJ^*{reA=KZJCkg z^sOKge%7i`**915Q;XtaNQ^9ycHXj4{py$cc6W&8O$cT=>@#w+W?UI?)F(nvDL*)@ zh7N74lH>zJ>{T4S)%luFgQSBzqgR_elA8ApS$(;d=eRYJg9|N0q$05#`J`l8?9grg zcUGi@>A@;I+w`d$CSxeO#Kns`Gf-@Q{<`H*R34y)akc?aueT8;<3X)0Ki@rqO8@-fnz+ekO6nd0asT zyVW5C1HWB|!4E^M(bxTMt+sI!Cb#_mc3oX>NxQUQIUKkKBNw-M!~KWELFSGlBJMv| zG)Z5#Lr?yV-aYRZ4VS*~-m0_y3{iZBvIH9=%Mv|^93LS`8X1VSbR+G5i-nv#M$l3hMV+7-9(dh!vL5L}w$>OCNvPBPPMn2qK zMc|3mH37>5O&d~vqkzoz)pOIv{!wA~1(z=!p~b6AIp?i8e-pvawx{XJH>s&-~u^yu^+C0@g3spgi>gl9}nD2s!4l zqefs-wyT3gJCYP&MO_IR&z=^{Grz+uz9@9C1X&4hxb%-{WO=B8@lBc*cAPvYSAyAh zqKu&%Gij@%=A|8muR;>k60ID_4=r14%c)a8Ke(Y|A$=U<>KZs=su7QdAFvuEtEh(};+O0Cx(a7kl*x z1_cLj4Sng$!@Mz%1ts_iJcS6eKU*w+M)5L#BVE4dLIeuwr*`#D>Vt{_hU z&}~I-cYau_gE*(Tx4Y8$b9!w~iuO0Rz-i$BDAAvi9g#T8VQb%mJI{tmXKYzGit|KS zbOzm?joY=IuQcTTj&>Jl0XlOR@D;T%rIHjuVa0k1w4Pefv~lW8imYD%FFfoWK1B(0 zW%j@qS~~!i7-AwE-!}Z<+`((C{cri@9D~IW2zvt^SUY_%AapawMX}5f2h2m#qY7jO zOoga?WMeV?0*trAz#%%i>wn+`DBJCHorOQLQGL80!&b!%!J#i8J%p=4CG^UbFDuOl zahPm!FFuCQA`abLG~|7ho72AKnueLqI<$?Oo<(6HEe3*KEaY+jTT@#rA*U_gg@gIV0=oOhGg^H$VhCwPtg&rOl@Igc zAFwYC`|k|FLJ`u6sz@3#%Lka;0cqMh!lzY;M&SydiZzAkD450W9-IJ}cLJAg}K-tjHPV z{B8z)8`Xb=$-PjA+ficE$A*#&FF$!D0kbN7uZZ>MNW`xH%@LN7I^7@FQh?JmdGA=( zcjH+IglgV|+ioccX*e!x`scqp=k5}_e|S4bPuam?*fDZrl8?Z z76G`u3YcO_MNG_qIINCTefZu|e6}kZWD8+W^FVcdG=7_Hxa!xF3uXCG8N|dvam`>o z)6EX2)T8v&auh?P#QphE{o<7nLnA>wK^S@%Y1E**bJ*c~{y?|2!NcZaUV}`X{U6}^ z>ebu!!Q-gAJ?MzXtu^{r__JCUBx#D3D}EvS+}Q#*5hl6zvPEm%Sw?F~FUo!_HygfP z-`wSDNhA=$oLA^DdB4$wz&YO{O`b}k;L;y78s*ZTNxJ8SS7=cJH^Z^pUjZ=$iu^YG zRVHl>9;AG-!wjbfQ#UFB{5ve{oy?bdZq23k&Qy;3nlXN;3-=Eg8{&y2LbWrLdKL(AO>A1}VExP^ za~F9*vY0{eFd6bAJA?`1Ypxgx+A0qq2du@+O0_7L=}Yl&p1i6+A>1?7GyVA~#fIXr zEGKQ$Vs+lCvCZB9iRDo9f;5dd`oViR4Rjh{v9`b&Q#aX(3Hk)Ff_6Y_gDA?HEc9)0 zdiO{D+P#w1otkd( zI=C=e!i<5ckHyvruvZwmX&$bPD_VtS>(NzJ5q@ri>H1ikppK8Id)B|pY@8m`EPr^S z!JmXp8XQwm6~4%HiB8CNjL|dd?hrx?a`%2^xP#tGpf)Br@!=4|iS21LA#xJPVrPE^j zZ2lXjyHkcF$sb3PxLlOd0>hB&TiY?)G*Ey{=AOG-9o>&b27nA50+uS|6s#*xgc$4RMUEl2C z(M!X`-_4V0Z#oXZdEf?#>%qVsRrk57&dt#w=e0)iL`4->O}(?oV)t2u--U+9=Ru$& zzZZWAzM<^m!%STfi+4?A9XRc`CrB|v0th;hJ9e8pjU1v%FMwL;hZ+}EIH@o@0u^rn z9ktk_x$!`?X*2W0g+bR82(=+?o*QTMvRg83)AM!*ujO@kyo+F9@Yw~OLGYxGr;}V{ zk916z__LD;-vv*)vT0HUqhHXezp>Qled=x>W=Nm;C;PoJR{aiyEg1V9?iTqa^@TQ1=aXoI`)_tE!v~TcP}6q()v_?S1-?T)O1I%u&Nye1)-P;Ef-XhH zJ@Vm=y(7^~n-WfMbNWnkZ_#*O$$`wdTTf}-*zpn2WtM@`C$(`_y<27Ic_&2ovdV73 zF`ePk&p8kKeJAouStRdotirwuZ{!ie3ZHzZ z4$iTb&pdq8S8eozxXF&*Bt0iSG%VHX6H*%{mTFlnLzx&clXGgVl*zp}ZYdmOE$hgC ztM$rjT?rx#CaB%{6AV$-AK0L4zy#Q$+xPbOpJH;TfNzq!Jdm-Xjulf-Z#MXE^Y@0d z$_ISWB8+9>B1}sc$`rAhyE8cjr@V4xhEapzASesoeYm%`mlDI?7;`BEUhyPsR4QTB zP*3^QEp9)qQAmWIpUC~j5VwZ3#iiCf zHSHiT>^f?xs`S2ro*s!Q10lrlJrzneq5JpN3FEN>1g!5iLUCB`7ji1$E;)q+(qb~m(j!cCWf@BZGwJK3rj)@*Eh7qImMKLWNKLIAFl?nSz%MNJd zYDG(6xbp;JL#=&W-p*jJGBM!bT$2L@q6ZSSO>(O2pX*wlk-tekm?^v zFvY@`=T_UTAK!2;(Ww9mzfQi(AG7^1LXzNi!oC1Q?HOhbcA18s37KQ;@QY;{r|CIQ z1JwcHDZ_P`bP!m@fxM50>*LB$S|v=M*iKq(6VSmT6a?+F4IV{*t>H1xz@Qm3RT}g; z7dia&C&m~3Vd;0p`NPX07?(toV-}_#aeay5dUzAv1!E2=^ZW5@JdeA+e0k!|JlAxG zJP;H}!xGNw1i(OIAFU9jB@Tf+j43z{H1C`QwiEW=CmLd@Kc-6ajoBl)X>5yD34x=B z+PldJRwbnRZ;eK(EvA+F!cP_p1O9B^CjXuLY7{k{q@bdL#B@Jo+`9Z%M|HV%(8*t` zz7Q9&aSkNVtDAI>6G+j$zA5C<$iQLL1)-Uor=Qc`JO$?rrU?rb?Tw8H)Uad2eH|&~ z31Hi7QM@#39_rTLXJ=HRgbmS!)u%AS5P!63Vy^1T44=Uo2{!r@3#mD@%6E1byQR~|XW1A*T!FZ7;UWq> z4Q&R2%_`ZB5NNAdcp|13HQw!r&gApKjmeY`848VcHXn?{-SYmGw1KRAjxX&sNwDiRBi`a12Bnzb;*h}1v)v3@ z=uhB7*S2C5P7yaEnsZMyZTG2Go(4o3;=}kV%7!9Ud|8 zu!_2G8CFcTr4&2%=xJdY8&ydt{C2vrEDF6$CCrDSBHD$4LUs>}7Uq9b|IN z31o@sU-wa=rWLUbzq|E7`6CLT;Nc{fc(_7|)xMAA`I^de1^ZlMFH}Y*XW7F`dx_6_ z3T5?@Rj_?c8rStn*1H>V$i|zqc6MwGClEc0=c|TpQtle&$1heOxA(uYg%e?Zl?VZ+ zdl6>(2vQ%FW*1J*fnMU~5)2_aKSQ5vecBi1O*yBhr?!`ZF@%v}*c@%fA7Z3kYdF~G zFk(jObhr8T%Bn>yEL7q`bmYqVdL?~(6WI#%7}WA2%^rLTWsu=U!q7=t5~(SJbg8wd zvn|>V#gLOz*&XF?1w88%~| zR(pkVD%{Q+p^6pBQG2*P*|&A_{5z2n9yF}dklw5+CUOqxku#9~=D6kAo5<=8JHWkD zRReQxj_MNI7xY!NYTP_#BG-fceSF3t6wTp4kWTF{Y*WbdBL+W_~Ko!Bgcm(+4JRgRKZ*Gr=-1ms)7U zWyVJ*=hvjMQGV-W7QkZ)w4)FpWh5A!2qrfXf26!Dmguz)+n{;(plTaJ4*JVb(n!B^!90j#)nN zsc+LnBrIW(4YN+(f+)ahEe3d6U6Fbw8URHfd=@|Ps7qGzm_UHb85Rw0O|>*;mN2|b zX=d|73VFq55abaGkaat2jvqs%;$VClB(S>DOpD#Y61=)tp&N0yUF%5c*F9?~1v6b< z%jQi1A@8=%zsGSw!w3LgrL?aLYPO4K-XsuEX8vf%g4pcRH z@;Ua{54AG5N;;hcg8yU{rHaXOX<$k{AL!e1A-b!E?gg#D@H2`X;fshtcRce|xFpcU zowJ&1U6jbh(a2~H;4o6TfEHODj95N9Mc?rs|I2C{X2`ML9>=Mj*)HKMotE`q|DYfNn|Q=Gz3FK}HHqGyP+`O)5V;_cHz9y2 z|MqIaO;njLgt9gaTez-Y&?&K=!K`3M1bYK|o?i;NsW5nMW1n_)0#D+U+A~L4Vw2w+ zGNi~tVEof_sRI%M8$JiI;o>u4xl6veo~|i{T%5)uF9(lj`<(T0cr=Y3(=P3qOh1G6 z-r&6umDuRR$XkSx^k!E_b}%}?JW{U5nkdQEIbp!hcN4ZZUT zKBT_aa8pMI)9K0Au@+grrzE;^;`F<@IzUiZ1B@&F^tZbW@FQ zKCttz>N9f#dE)!$R@an5Rc9r$VJb#kfoY6k?mn2GP!|t{hyVRo{yTzmlqcJfqD&Wm%}s1zn|37ndZw?Uc#HIKe?jG zJ9OUSy^t1d7n917X~eQ@7;s0M2U zetK?setvRqkN%`URsN~djr`cH%GLe7CkxX_fk`{pilhFptxa@99hl7Q9_hJEzQ!fy zJ?@?6dm&M(`Sk{^n0*PxQFVN<-gm|h0%u!C;sP`E;y7up(>&5~0tBF+i_kZxTOT~N zR7q7OniZr5vH4>(liQ@IkDR3{DZGzJWO?|nr|0eHYBxx@2E z1>4%4%yU`WHTr`C@3KJrp^+`-Kt)eM#AAukD1$5-KY~;1!QZD@Woew(*2WdTqvch4 z;gW2gf~MCiSt6H8b$8fZU$3OiCmEFgVtp=NIW%IGvy~-ps7Wj;`(`#~cQQC?@yz_s zyo=4`@F+l{m^`AkBWE+^drRNRc%7J4ltjz=eR)WHV@O`g+-NwPoqBaJ|7=T{j95!n zd~VJKOYFVg7=S1asL21?x*>&QaQpmWF7Y0OTrJ28eCxtfZ{#THN3Yfw?VtydtUMP- zllaRFeIBUMIO!}aq)2*hS0DIhD!GNXXHO?@cmAcFOt$*gwDM#qDM0L4`;YCNk#x0y zKUwk*i2GIICl8j}(zUrBu$kuga@qu+Y}DMd@%nx&u+BuM)>3*SpwsiZ*~2zEtroZN zjy@LUenJDb&e?RZ`WLxF3U6?-6a#)vHfpC@vOMT?Prb^m5e9ZKk7R_W^Z@WF{b&;{ zssADkqohbz#L{{L4y(RuIyWuu8+j#$KN!gDcEFL>X(>SbpOhsv(;Mld1`!o4RRJ=g>hJcysQN3bKW_IeQyM|3bj%#ZoBi> zi(hn*s+goF5>ODmo4waRX=%;*QxAA zHGh;vs~RxYp8oh=8~g6b_fpDSvF-x}vooyH-@l5m?tI{>^T8N{ zy^s6dz{|k0BYvLkJdqUJUP?ujp@yux3y)AN0RCUEs;oE~?C%$1*@8>Gs#h2!-4z0u zl&fwisnDM5g(cLY9!9oUnM4#z0Q<^AU93eH&nFV|DD?m`_aew4syX!X|eA^TBUTE zR!aWU!`H|_IgglCL^U*D^urGKtzX~CLiW-0ABW1-T5 z{=XbtqlND2DO~i!nY`K{FDZms{L6$JxB+xaRkm@kIH$VIHILip2%Ch+>$P^vMN{oR zgdLRLr$7@nH8v-+eo#5J{0;x)i$&l6(R7w!QFd(?hM|TYy1S85y1P4+kQ5{&q(M5R zq`Nx>K|)HUB?L)nX$k3W`1U;S@%{0ahfdvlU$NG?PIWF2206+j-QCgz#J+l?$ehwO z;3HV;drO&$FlUxoh`Ppc<+jig%4FyhrCYBJrE{`aZ#)%!OBjgrRjcat14{XGR!gvV zDg#G{fyJS zt=%cj?ubJ#tAgCke%Z{YLc@C(lbSX@sF(u%fgBGtb6% zyVh|2L0A&D1D72t$J@-1wbfKn9-7k8)d$b}gJ)%5ZjSHf+}b$UC~a_a(x{VI*lb6Y z+vqaU608DEDxB%6UX+s4B_ImOb{whJ;VLbzvZ;NI$cGHGzY;D5!ZLqyp!J7H8w=T#bmP3@ya`oBAY6 zGgZso4WW45&CEq;*cypRmrT`PU&%D_VX*DjpI9Ws4Rs#OjDS=5I51cz7;m8H>guup;0qW&qn4D z2ev`tbU_tv-rvEpDaHs)eObnHl2BH^q;tMhP)+9~A`my%sgTiT*KWp2t@UWms1)Tsf?-B!nR*s}$_mKT`p6h{Hv-ft(5GKOD5T7$A$;r1 z1C|BHQm;CR0&5Du@Bv2n;cGBTh3yEw-d6>-A^kE~wqWKhA>OxmXrPp$098Q`#l>V- z;H669i*8o`A7@i5p{Q$gQO~g%xbIJ4?Pw6myCWnZ?lj%7;(4<~E9WJH+?WZZ!co*= zJzR#G&z?KzWvVFWv*~;u&B7aq{f+Y>Y|V7T0ed=jpFJz|Tb!7=^6#=rkgdK`&%bl0dCy*R%twN}qUH{(*Q zMH74<)m}yq0Tmdlt#=2A%>59}`E!ymk4dRY#Q%poJW~c`nnV5h!cf>-DL&OlHumBJKH>rL40$$&P49cZ4r z>DrxU-s?5^izn_XeiF!t55VhWKf1|$9LpLEM2~R(N^y2LlMjJF&YmnW#gZMe zNWh&CHn~{ji&`V~cdEn%WoChLAgo7+Njewr=xO!`rL*SUTE|BBVKI@0N5bEa1rpe| zE*Va4MZniSIj2UK4cEM6GY_BTw85_wQ zl4m;@WE9{PD~tgRd8Nuv@~HwFw;|4OW-Ab$tAIc@DdZ zD(~&-x;mblgZ9I1fM;Pb<=6H}Iq#uSUNgrJ=h)A<{>{6(wA^fFmgrU`mDxRYam%IG zSaqMQ>vKTylNlGHC_qynEL%2FDc)ZRS~EVMDcj!-p$t3pOZKlYjz@;V#Vk3WkgyUv zSHwp~I&YWiy`am;T%fP>K7K{cV~arNs&en7UU95nBR3$^ywBsMHUrIBatk5G%4QSh zJt%2A-yPR1?$`S6H^kcNO)FC=#gz}H+iW42erW~0=B}NQ%4kH0@|Igpn!tD*lf;^+P3 z7^drMrH_jne0Hw25HNmwqKela0Fe9v3zDHS)Zx*KWMI+!g0ZJ5A_9%hvN=f@2j=(` z2x!813f1Nrf>O=DB6u7u8a79xF#A-^R7;zkAIOi~G#5&04BPa{-0CV`;!9okwuZQ& zZsGi*%TawZ2Bxm?p6IDS-;n%=F2fto8|F?-6# z5S1#T=TA)OVs1B>`6Q<2H$!Tksw#8>xCO)OvovKs+hgaf23sy7b{!=vU$3~A!%YA_ z!)^W9l52Gy@9S{LnTYIx(cuSuinuYb1t|n=z&{;t)?)b>lG2sBCug^bb5n1md{r`7 z3%OpuEyBEyuz#+U+~p3hkRUs517d<}(!Yh`(s? z^8Nv5#=X!mkg>BrYKaR@{s6>og5eOAy+vQ#*dUzejo(|Lr3}?b>WN z8ZJI#3o_=x!2ux~_(R87#f23od`;_Xi|qKyLD&kHvUA|szT|3|a-{LxpLW+@myQSD z+8Jyf=7tY4Es0*fC=NS_vNJfBAmo3e2l!qezNx#26VUU3<;hUS)8b9!82B)uX1=LH z=4aJZfR0!B=%?_? zORVMHTpfhA^#95gSLA8Z&ko>;5dp%&-_?e?juI`B3Y|w#uu2gbUL@KY&1Zq4xN4me zj|^_b(%gS>i?&9_`~bwxN_wp$qZ@f_MDicbdQJ#7$3_ptl~v#GE`Wrj5KTk6hWVf>sP3tWCJW4Y@TN;YG8}T?3=Pr9?DNW30q+&ehR5-s8D2>Y%jWQ>( z?hEdB6o!knJ?XnbDcgt&0HhKLN6aj z_{^L=FTB_*UJ`BM*s@>F(~GE`*yP@vIGiG;*tkL*doiC37R0Rz-_e2M6F8*GXk{-> zPVl2nL3e_;@l{;%eLSVc->e75`nGx9 zd5ZyZJFYe*Gu);R=1XC1dApaB9&EQ2s@0@=O|qQq#x+9XZP!6JX$MMLe`pe)rQC@5 zNdFd@W|kxIS-;G}`820Wf|FB-W_sD1vG8G6X1w&KAmQp*!Z-J#c3%BBnW==aGI`>A zYWvPkt|FtU`K=(qJkgZ@^c=sO(N_(#(w^N5<$J~QJ4vF_n!r1C+)vBJ(Me>e2iTbY*LUF$dG#S&R3a>~JAtqK zO37b9=~LeSjxVL+m!QD5lW!+>!h>7^wcFHo((Tw&$ZUml$Y-Ddw0%Q3X{}aXq%#&W zWjI;&W2RAKeS3GS_LYCVdvufS-&v+^N(}CzC|#!W4#F;-kZxl$I)kt;0^wiMUK-*2 z!kres^7U5PHSe$d*4O6ga7y=h?&Qwms*1m?^L$8$>ZFZi!VN+;;>Ee7@)&Td4|P2c zv}4XABEiig*zdZm>8ssG|BkmZc=;JOwc=&k5MLGRC$m(1zXOvmp$8`IvQdnu!+sMt zW>o>#2&S~dd#3fVMQw7WjlvU?ZOe~EkklBhV0L+=p%u)eB@6NQWoUYB7Jam%iY_|A zk}dtwzuDDWwx%?wuY0B~z=gg86l54!$=QuWh#AsAr?_Ag`_rB<7)T{20j4hu+u51; z7$FDPaDf(2@QNurfZ^h-6f?a-<(6FFck;)hadotvJ!(6;RBC+U`383i>{N$MRaNeq z^^4(xmZ-JydwVaY)oz`T?If$ig^%8%D!C)F(YE}dNoC3k3~w$7AV{VOA62@Mp%D3Z z%KrJRuU{Sey7zQ7TCchOYqupme<`LW1}*a-=l-;+=^Q{Crsi~-?aj)Ss#Z-4l~1PT zS+Q9y5aw&xmD9dF-jA62=4@|qxa@3>8IrtIFeGl!_u@Y%?zcV%; z$jEZO?tZ`VoP5E%!s>)DSmA9xyIQ9w*DS|BDTB;;3(8n0AvrNI&MYWn;_bJ6+kelB zxv|Y8^A5~9RK02!@jWmejYD*-&f*VHx(b{<9=9oD-VY~+g{11WI%2b%wqw;~)}^!& zJ1BcwNSS=F{F~9&=(1@*#wT;THS;6O}4ez#$um zbbMu=|D|qvW^B5wZrZhx246DUrl!=6s$V%<70p&E{F4kq&?tfcLBQKviYjyrdvS~% z;lxVw7lYE`cT77eMm!Dr@r$=Wj7-^W%K1`xw z^wsnnnATE$72ZWl1Q>8_!?<;jsv7pW!QW4wuOut`YsFI0WK-pyop{{K_fD z0yL3eu)AJ24GW;s8njN?lgO1mFZpwtcZ#Z0EX^r@S?g~)j>2AGSLxt^v^Jy7)?F&5DznydZ;bQT-s4h}LiZv+eC6!{Lw4>}O4AbV zJxK;+?7gq0LrG~#HqPZACw{hthnyxCTT%bxX^m*fVOJ&3^02P-b_PV!pdzJq&4o=T zS~-o(1>dUe@;A3eDNRZ^Z^VB*GQA#O5iU9i`rDy%k@S#7X3LkhXr#%j!9^c$yc14L zt78>d%F~EzSaL(VK@^OTBB7~pyl`T3nN*V$Wq`aIivOF5oS$Op#on_upUsdt*0vIU zI%QZ-%kjc(PCAV8hMP)yt^|$6&%iZxkcE%HH_eGeXoY-$Kktcp>W}HbLFYG2`v{PA z)rfnq{WPp>7Q2^D+P>)V;8v6d#Yv z4_Gt6nfsg?#jBhT0ygwS_KtCs50Abomo|>KV@8%Hsw58Bbzi=V|1A)w`sR-jn^ZK} z=XpA_R!!ZJ;*|59S@_|CHp8xLI3)ertkoLDLU9&$RgK?V6R1Ru4YXnryJr`dQJvPw;VU1tTlYsd@O$KK ze{P*+$-K_iOKbf0JM0A-`SBi;D?0Y+FZq|OUYU1OnnwE4ho&9HlH9d~qlk>i*lM$T zJNjkG1DxUfq~tJu^vf*oG?nfzl*p3IF@qKc`{4Q}dSUUsj0>VvgvL1mdPz30s{iNt z>)Q@~FjxjglE}Z@v;IrTN^R;r*EIH>NOb4^qgTquvp{k(C@GBINriim_zho6Di2Ec zuZiF5>9#*}I{HetF7u6|uL4nZyb5yPO@tiK@7r3EP;*b?zFjQmrg@zFin@gsTOh_7 z%UEaNxuavH#OC?wLW%ugm*rC=Rbz&WCCU-hEWU(#Fg$-?D?Xo*-C)4KhO>XnmIrg}=xP)-L@ z>6FMY^MzWy<~)zepE_jCwe`Q5M1}uq)d%$?tM@JE9?a52-#m|FD9Hr z7lGvm?$ir>-0ygAV5WPW1gvKSxl$SW$uL4X9t(YmfRnH=k5QR4Y_^|9mLoUOZD}WI zp4EmwLr+87#yEmtdp$C2rTmx_O8JuhUmV3T&T@hRDH=BynD$4)Lv6yRS57Z&kcV{_ zG`5*D%N0sYV!q%{OTrq-z2lJMb5I&sp`tv?RkKBJI(ZSYmAsi5)mFKM#Lm8R&v!MH z_^t}@d5_qBe#f9ub=U7&J4nP~4gA>&F&#?P(kY{QbYpR^ktLAXf^};IyHofVhIfus zrdiNA{=C!d5G9mpvb-xPzsG03`4x#r=3M%avZ#5XHk-1ezx0W6SC8ZDs6X4=@Q|s% zxk!W#yeUkv1GJ<$LOw}eO-`JeLD(qTCc&3(p44b2bgf`?a*~+ozNG+2Gs~qH)M30N zqQzKQVK%Vc=>J&&TOctV^fvYN@3zP2x{)%%F%2!6+MY6As^p3^DdpIBFr{KJt7T}N zZQNd=^eMyiOHD&P*Dr_Dm#)5+Rrk4~wwfDSa^$zzj@*#9i2XBYPb+rtCiEVue=Uyj zi{S2(*4SJY<$KV_ADo55;SI`02deJw>(Za!mycwcPK2=FaTtan*`^@remTtO>cF*X zrOp`b{QK7INYFk4ys4gFw2St%Kfi53ae01Ax@}_d+fQ>WTF-K@8%O5oBeX|={VfA7 zF7EG4V9LO`zdH>S9joH)q?!+x0%GT0uA9Mq#r-O7FD47(Y65dMJ-BBRg{#L@3PkU|CDIv%g!r+k~E^D_kr2R_p`0UgAnf)I`=@>Np z-Xi5J)JDwZ4=c!Kc=k|k1lmOwvyQ9L#8T>Ex|MBCMaeXQzc` zgbf!J;6Y=nP0f&M)NOyxH;FX{$cn!G=|kw}AEY2A`!J0E_7VC}&`R3oL@6JQuY5Hw zos5LoE5l=fVj@9#)|yKDj^0JW6uf-0C7O+=gKT#DxYhHryX7?)wJr2Bg*9E`5x%3^ zGOXtS&ZPyG3O$a|;W5s1&ukE(e8BsZ+V^EH2P$GS<>cf9-!~YETD0=`yP%TrXxGEk z;-$*H^6PT&^SuinE`2w!aym8e_hdK|h3Y=ygk0kcoYY<6Z(Bq%%9k^C(Q95q5Wn>D z;MPy!5tT{?Uw_WQ+&TSP5}?T{94&i}O<^MUHiV!t9JXWW*RXn~rUD@)rz4-HN$NnA zLFnNwp7CSzN!i#!;P7=5=^{_$=tEZVpLbjd> zpXp_O-aYWj?lKR&lN)l4rO95p`BoVCFi+-;%}wa66nK1;$#IX&$I(Nw$4sAPaO=7M z+E%@l8AuMLvITENkDtt7HdEq5Fo`)}j|3p37H6k^a&+}?wdcM_2D1 zdQEmQgeQ&Gi}j7K&Sb{<=_#d~;;0JV-Io!V!d5%|)Q8M(cJ5h>MD1F~kPXyaRz3#h z)?Rm9buji=pMwau4d!oHp&AaNG6pL%8V^r8+59>PRfx3AtehJa~d~(85%s9vtptzdNsslQoz|_D{?5@(rSdA=Di) zi}C(&J)Fe;?};{MudqW{&YW9!)j9vwJo47xqv?}`E9w4#<(c75`)S^vSxhLXpRM5q zIvpz0J=^aBNM9s@ZWwB_+-|+?lYlq+43TA+U5c*hKe_35P~!Z&UHi48b7^r5j~c{~ zU@qJ|bW@&Q$x5k6dyihkz+MoQJ&CLe-d1on z{q^#5fp-wqANNR@D{LM`&MUPX^qSM6YoFN14k4Rvsj(Mio`7i;xe9oNSL97ayD{4%u+_IvmQSy+c6T7@TFE zTT+O@i~XW9UPy~2wIWSesj|#aW|h9@s*TgizM|#rbLqks@*Gd57q+LcD*w563w1#&k8v! z=Dr0xi@ona%LgmBsleqB33CEU;N(6;M(>EbY@q7B!!!8eK-OeC9#U^J zssVJYiTA+(Z?Bof>xF}Dbe|1H`bd0GTVj%(kVEvjZk>q)FK|WA*6@te&q5J1g2@MO zIOvE%TUBZ{vo|ElN;6nc()@x@Xx-yzx$8Q3vsm?kSm)q_(Ugya%`r?VjY~d3fKyDm z0g;X_lQ3!!-=oKsvqbX$YzQKAcjES;f#QAFrAhA;xCJ_o!t2=HwyN*+or-$P_@1+Pn_)_O$t?C`m8-pte+q898(z zNYs2#ZQH}276$8-Jmv`M8A8EE%J0bkojFx2(O8_<_SpxA$2c|I*eA+wD$5|CcQ6Z3 zu=SNoYGw;M;b)s-VKKn)A<&t)9Ae;|EWsdR57U!#+scy+)R2!RDhw=EEYV&7@?nH5 z%OtW1Du{%Rdqr2OuTB{5^7gwH>^Bnq}Z71SP3x!TZWO`K#$##!j z1+Y%g-75_o&Uo5ryGm~);!chIP+6|EEYev|SM^FC4S58TEPxl}=vHn#GH^KUCgQPH z!XDBG*jJ6{sBT1{`OAv16$0*0+~(>R80MBcM_oOHyfW_pOzWC>lBY1<4o67f1yyn$ zFfHn~G;cs9?YZ&6*aAp}i9Ay1PfxOcfdp*vdZ!<3mecbH-=yibjvqA>60694mAd;|m@mdwcV)+#f#gGhHJXi(o^boG3` zaAE>C@O>Nt<;0AJu?9tn!4bfX78>$eqn|F0*$?=lkmY{VtCZQZ2OP^cjRJrmnI+^> z6xl(;prBQ5%zu;q5x08eo6jY^7h8~gx}iT((Zt|yiDpYRUJ2wPIFXHh}PVlX_Zn8+M ztxv@96tk{B-Y24{$zdt&(!F0WW(bWnY4eMIDB8X7w=%6$KK`AntWdtg_;>C%8+Ol8 zFwH|Zl7Wq{p!x0N&+%8>)^L-oga-89mb=5}Q$$!e2qi26rmKWJ6wF~nQw(mCuX5B; zy1FwVoOVTo$NPi11#}wcV*11FU!f~Yo#G+BD3@3VR7}X2&fi}DBnHd2gx?`3|LQg& ztlL2=cbF9ZP3YXkM_p_{3O{e+7Kp)cbaiZb1Dv{c7c_2iWfRdgAGQB}k<>kLKft&g z9?2#{teD1A3|ye07y#39m2?TJ^TP$RFjYPoYkS7e?k+BcU{&&Sc(QtER=?VS?tsGK zYbo$vr%L?ZM&@?A57&1#Hd8ovwO-^sT6z80+wFhHs_+!-H%pDeO%808C*opOyAc%6XyCahBxU44EsbyOs34Cp8VZCp zV;gK8s=}B!$%=7jdeRfm(o^sLru>Sx&{I2q#w91P*zSk6pVg7+WZFXaS0Rz|(f6!` zoIk{xaAwWhpk-{W+BUNM@^E=~Debwe^ZP*zYI+n5!UEvD0m@ zJJ>O$r*v}C|7Fij$I#VHv?uU+udv^a`ha z5yZ_@g$bQ)m$(nzZeY(g2%MuD`4WyPbm5|KSiuC`%Xw|~bEl_2Ua!CvkNtO@7*SHa zIGSbviIb(VR5FydYvn21?d1beyS%{3c2d;qb%j{Cu`5N?%Sru&5?_}Ng9Jpx5M*){ zx&qM!DdIJrRMHSUIRdx=Lc%onet#sX0p=UvAz^z!`+GQdL2UNjkBvUnaj`KG+w!CRvFI9l=fS9cO^f`x| z{F^kw`As48wV@>z!3s*q0DgeqM;59-&OsXcVhRJ!aXFmo*?dGs7~-f;pxP}#Zcutc zI3q-uul2P;XD<}c{&L~C>n^u++NlLNraiJf{;0=@LCyvMULps=+Woh1L;??TBK`Lh z8IWE-NldOM9#J-o-YAeeIO&_6CP%g2MDtu}=JuXUEhZ@)p4-}<=yE#)QS;Gwyr|NV z6?&iXpH~|s0x6%Bb%ysI1_Oxp!7ViGWB$OarbI-Yr#`|k+7GOCcKdBjaNE1gQM%f7 zgU@h0E}@HYmu>naAjci>sDQI~jh zL}Zd%g|lwfO40Bz|HJJEAOk^~dh^?5VhNm+=1-R$c}qvLqh7W5X~#c3P$ouR3ETJh$NopiKlZDJDO0cBkm(&ivuRrloH^d&;?YCL1<#06=E!fHX&lQu=5%m#aao16{0Unn}6V`yaPobz58~Y ziRkv~eFUNS3#>tOvGT_2;<)aff@4~qPmJ?dtuf+Vl zZ}XDcS;|$2*rN)Ffi06DnATlNuOIrTzs#(_#~(HV>ZeNy7`g)~?+@(MuM2Sxw^sCgTOC$-6Ul%5DPl?^Ao z2SZ$C?IMNoJ@4V}*@g7C+Xtx&?w2Q=ZA^B|PWkCLgTY7pADfdp)B(v`n1E>5OzR36 zE{6c6?58CZH4?v_U+LcL6=-qPfvB<3cJ%6ySBW`PJoBw7P9F0=*eSF7FB zQ#l4`%=X@fJx<63aAv)P-<1?FMi6Q2>whvXw4W(8p{}*3A4Sz0D6kzb#w*fcQqKN> zU#cP#rL(4_hF&@v#)gl+8rnqt_y-ZJCz$dTxwo(H%g^?f2zIO^CH&Nk7m`^;{VrS)tlu%x7S!iSPu*{`RTC^BH{vNs}c$zKL6E|TMJ*St48 z@~M@U!FLx;#Uk)Jxv^EaVXFXm5_Eu;KipFgoVROjOAmf2^odN$Nnr`gSkl&ebi`bH z(j!qG8hK*MwPqd6LJt-41A#$(l%Aigz4CN1-5M@J`zR9|6I@8R?XJS!4(0lK^z6oIbp!mt@Gg}&A-w=P^$FnE~0r_|`6Ou7VgjbUC z#LEsr`Wt)+m#-K8^4$5?*EE{`JAZX0Pf|Tc3*H5BhlLll$`unHu(3WL(n}F}{)UcE z%DFT64&Mli*nIWRT)q-J8B5&#gEWs}AEq;iXU-KTls%8%f`yr9{2c-G2`$UXdGos9 zcP8^k!DUEg&u6~XUAyHTEPiL;qgo*`+G;^xQ_hwgWDE9{QK5CbqD@d->~(r&631CH z;)36cw_L2Uo0ztJ+OHV~Rj)PuS~RnES_-4jlk`^Lc3uoTc)Z>wyFc80MBW{fE73^K zdS*Sqij%=7O_2E1QUQd$x2C<{;^B^j2?Ib7;B>abp6%Hsgk$<@@fuR?@mOku3HgUl zYaH1QS!z#>uR>P4E&&NCNYhU-rG`bb)a1rSv(L+n#_WbsTq=Zs8V1ddVE`*qLQVy~ z9p}P|Wl0O_pk=P7_mag*aq)@t@+FZIp?p9iM5uie7r`#U5b#K)J%o6oUf*z^zcGMg zbMdG=ZgjNscpvyMQ-U^Cj+?)oT*B3l*wp%Xsd;vN&*JmAN#%ci6~1yCr1j~o**Cfy zLDEY+rP)sAgcF6%Lw4o0l8HnEjt5}vWuWvPSXIj4q03G|3DvC0@1y}75%Q;l{}EZ` z41NLJZq!zyAE%q@G<}eg-jPSwd)(oP1a~6CBD3#4%9jVKqPG+CADT$)&CFdB1w7VN zy5D~!oMP~)-rm69?tE*jq{UoSQuE{HjiKdzha!$pgurqAVW1yTewqjImV~N3hk#*$ z_SMk^l0TOF$|Z%vFQsm|l~~fosPje6@7V^Tx^3l*lmPSxqo|tIYoh$xiqTljK+IL$ zGdNYcQh=Pj3)G1O4kmCLq3EnqmR_B?H1VE87*&;tTpD^nr#7}Iud_siu6P_6m5;Yy zK1BSbl#mR3;E)kr3tUNEezJ;1_EZL0IU$JWSkeXiJA=2-Y8Mis9OYU&W290C=axx{ zl~SaB9E0_+0U<&D(^t{jt9|Hnoo*lzX4uYLC$#64Y3Ly^t`qIgL6J!G$qU_nvDNuE@IJT$xhb3`7r_yd!Zy0en91)zr|4Ss|LAwBc6fL)M>P$Zw?1*OSw zxzTQHNu4noC_BedZjIJZQr0=sp+zsEG8EAMSCY3Po2UK2tPPQh7(W9>u1est@1|5m zXA$2!v*`B)sRUVr!_=she!#g%6b#DfNg$nt+S4T;!=!ZPK>HIlLa`qED~AE&c?+qS zcU|2=xGWmn-aQbCuosS};tg&mf&ClWWG^tR`)d*M@-rr4#jT--6N$SqpyN{@hV_I^ za+pd0$bzMpsIm3}v=f>1!Cvi$6zx=|Tna)3EgS+RC4mgjP@VZ?LJ-o&vNXL*D9Fe(GD$I`hI)bgUl%72Wax6&2Q_UUzbr45ng0 z4vo{zRJxaQR0#jT5clbW5(b}R@UB!OI+ z`QBZ9HXhNgu-2|W@5E*cK_FfP1C6K+!D#o+*#CLlb&$;=5zuE)t8u{4N-kQtv%sYg z_+b4jhs|}7c8EM#1WLQ;(Ddeam^1bhVy3&{DKhlSSDX;gJC*}cX3V0FtL={LZ)1a> zuyi*ZNTeMunwc<@`qqaq-~YJqQZJz^t66};Bm$y*zL4_b2|`iC!G13>Apr%`gN3@# z8t=_5;Fob2RA7G7^^q;RfD;QpzNoP}ePXrR&8EBKT@L-J4?J0LnB#h^3ntJE7APZg znSNCQ7tJ)|TLqgcaBjdYLD}!GS9{PBjj@=YtHsfQU!_c9AnIvn2^VB+I#{$#2VS-a1c!J$0a6S6-(?`gYzzdD z$`c!F6jTV@T%4l?!@^oE>uG`kn>;G#8BhW@b>Mxfv_m#um-IR?RzDpWo{LAoB)PD( zY0H8}M6TK1<^7C2Ap)zvm6*JU@hGOLV|iMg<#qU&hHm3=4nXnTR?C#XO#L= z6fw9riU3nGmQvVNsL;#O9bGyAtc=zkaYiYa*Xa;+HEl}@3ST{AhBy$v9)1VR9yF2x zj3gu^ee+xIYZMWz-+&z;;gc5}yH8w{vtbqbwbWg4)Hrc)hFXCb1c<;3funjX^@+aR ziP&FXJ{w#xYalTkbv%EPgj54qllapwY?r`p_laWi;`<*ksBTMV%*Ofd3>#oKlUrub zSs(+swP!&xize6J_Idv|VPOS1fxS;etXiEp2uBrUh^cr3xuSv~PuDc8Pp{Q}(!Ci_ zatg^p6s8O*SfH02iUYcnYFWQf$;%80@hfO4mqCl_A;4jsjyFaP0niB;dSPQfnHwQF z|at;~*sVwNqv`_{H%!&DPO%?M)i?hJ-gH=$9|^@RPtR zFFjkk4A z8ME>*JLw=I7d#I!!}9F=@1ISGihEE?n>(R6C?)&1EO3e?6WFc7v#QfvpeKV zoTR^>A1f#?tHK$>AK|E!!9Ra;jZI8EKI9x50D{4HEw6()(B#*B5d4P7;B&f(-8JX8 zJytjcSVKXlck%(q5`hnD;3jCQ0t{nZT|oQ-*GzJeV!YFHDlR!V0TvLR=)h4BvYLR7 z$haamRRfHbgH!Z^Jy3&q1beRPew7BM#6^t}V>AGhViH2MD34Np32k)xlLdgx(qIg{ zr-%S)OhcEs2VQQ2IxUN&Y9fx#Xb^~@R7CQWUec&0Hw*mACPBY$Xm7!3{vstbXaP=) z<-)9i;eQ96pVX5*9vTdW&L;)k2o#^+eVv8F-n=sTL|Op~jgC?cV5ZF81N!>xau5dp zI~!w^1jjuHy22#D5P&?S&!5ZQ#l(~GzOM&-W1ipUNoa@;0-ubUqJ1t8I5k6YzCiYu z0pwZ;u{KobU!_R4{EpohzTPLqpMW`D3shkIy+jS&P@y;kdOKSCJABh<$NXa9^a8AM3TL^}4x+O}9dV87e@F#%@uMUt(9`6RYhT-_Y zFaL3sssMZA4~Qtc7&4aiK*ncOl8XER9vyc!! zhQ$B`Q;Zcwq*nKT{|ue;?lkB8*LS?~ag=jkqK&h_(MyULO3&l6Ca03YQwXhe7^6F> zBSloF4cg8-VhDR-%2}_{G5`|N>26R+ttPl2Ab^30Ov8frR|yp|*jRAMps=W>4OglG zq2^%az1!9h1CQf8-<#RG!V{{@YP9OgoL!mk%8CKR8|ynWwfVm-0{-rrJaT>ue9VL6 zfS~^14TeQ@g3oaqun9IcH;GNP=RUn;G6tVUNWnK&t%4v2DmU2^2868^KzDN`WVtT% zsF0VF?&|5x6zBuP2>hp`iH{+JEl>b)4dLW4Y8+?@b?+X4vH$Mr36%)A+YVcbongAm zgz+Itkf?p33(`7*M~8ut_WX-P)ogr3hY#kV1mn2Bu@W6qorB4u@EG2+u_$PU{sOK9 zC5QfNmPSGQHxIfId_K17M^fERGvG>>W$)g?3X+!7}1H_t{c$D2+3F0JX}H zIrHg%xGOy=^WI3jMxXOHGShZtGic7XkYgE#(-7x%}bTz#;B+0NCM!l)C);s^bp z29Ct9C2YNWr)gJ!A`GBnn#)+zEs*(Q5ganXq*L`s6F5-!q*uVBos%nywF1OaQlCD3 znga4_9+hLJZwxEz>#_hER|4APfBvk)9l6jtXA2f*;}IIf*<-iJSKlcBkpHt~KXmme zc-Ovo@MuWs?4M^0kTD~35{mD{4mjrw5?!O6{ff27t3@D-1EgCJe8UO8_Jkn{#JIww zKK%ku%nvDq)f-@78Xd%meVQwV5la_T*8t89yhk$*<1leyzz-wDX#wWjVmO)0B%}5j zgj}wZc?9Kz6);*-dXDX!=`)!e>4O&Wxy`;$F}n>vhasclP%44>HiqjjB}pO8&_rup zGt1y&(MDnzk3k*O_W&fP%PCzWqvLCV{E8l~VSTb-7);1@LkVj0Tl{shb1=*mE`L83c*C0WVL0dn-S2OmJZe88n3fNn{g0-O*v9&sH6cZV-)l@I!xP$saIWb_zVs}U*hR!v`!SG|5sC8a);SqEIe#S4$&gi_Zbr3ERHV5fv zZiets5qGIlJ@eX~Q`W<*W0=Us5 zbAydk;ltsdc16H+q_^7T(*Kkr^Ub*d9M7H5476Z)Ebdnc%J$_p;fQI7vI*F3YAEIX z$70=->SS(Ub(4DaLAx8T8eD>BfUZn#DJAwl_Fc{gglBIV8u*eupdYA2UtAfv|C<_Y z!(e8A-3U7X%ZRN@Y=S=>tJtahB&&42Gc__Rz$fYs@X}zzlK1>?D-65<9*B=Dg0d{$ zK5Jv7B9C||Iym`%p9%2IUHji%J$(m|NMI5nOyR}j4&0oop{UqJV4pYJx-Q+_f*f7UP5Qu1@}9DqbgUl5l68ZjKZKkXLd{ zOiZxN;zi*-V5WlLB8k-qd^#EA1&T5ASPsMb$fMRVQ$T{33K}W8l1RWt?9~Syh!9;P zTk=zwlxY}%MNWjEV6uYy;s>c-o%Y6Pem+PWR|BFCOwYb6Ny&gaR?h`+u)MwNs^XE! zh){^a@2k7ksSd9Lixv6|D1J90F5JScg^F0?WLvi>lX)%h`XeZwt-HF-dE@UWLH$FF z*Z(0a2wRvoVZG-AoKPR~4pmVy$>suYdafdhv_>9I2X6#z4~R`-#U!2%`SF(&Q4}`Y zWKRMI(I;Df^TZ?pr$r$+rREXQ^evEwgAw+wSk$xRBqSu-sCXK+L_PNk0E9mwjQ~*u z;tUEXIR#zC`7!%H;LS}0AtFIrp>;?JK3ZQg9yUJQb7+~}epg1lz3%Hh-do@{9 z8}lbC1{|2$;F000j6-)}R`UaVK_R*78^IuXOFYQ{zjM(oYBIzpc#)LC&oGH!yn4;{ zBl8!h67oGIh`R=PgM*FM@1@p3$+-3|hL+{X?iFwnOVL^nXYhgG+z}_^c~0Y%;IK$E zxC|f`f$|?nCzAzJ;c_KthZD3Qpr{=wAW^&c2pGWh!lt0a z!xnXA908PKvaP0Pdsw32+>8_K;shFxJ^ED(QZ^mN=LX^5*!Eh4kqg}4;Jg7R-<{j>@TWWiz?(}$$!_aH@-FpV5bt`dB(%<&GhF*c|ip}=6V!~KdIn% z%LzXQ9rEB@cdt9%o*@SFX4&SGY8PA|IfM`t;um|bnj9Bi#LJrJIZ7e4r$p{|09L`5 zg=R!St~YA%fJDw`I@z3!E?{lA4a$A@vu$ll4UTwrC}eB^{I|v#T_~-clBB_hP=NGU z#;B(cGI=6&Mg<5X)1IwQ9qD;q4-xv@-|z!!tVuT3`>yAJm}dvNH;WXW!%}d#YsHfD zhppI}5<1Wb-{An@okMNKIypQ87eom@>znUTte|Hil@ODX!$HM#u4^dxVpUwad@H00Xv7&+u z9GZXGb{175**Ck9H29jwI&mYpNxW{qWkCefa(n01rN8+ z1@GkY*-p|rpdO6D*O6e?*0yR!yt)QZ_j+&=<1Q0<1v>3{%+X3tEkU+{BM zYN-HtLl(3rNuQge-UA^DfJbILMeQwycN2zq3Gyunif@=~lReIu9XLSfR0^M+4qV8E zu2_4J5Kkq_JX|!j|6KH2YfB3>dk+w5UB%q@)iB-Yksx5Af!BTkmf(qn{QmsMn zo?k%8P_`sCK2t6HV;}HE?LgLE9DjF{dN_h&%(LI_$#q2XI2d7SaIAqGLgwnRRERiO z@AReaMK8R-L(I78`#?51m6`EbIy9^hYPWZA@QMEB*C7UX7Puw9l%S+ENK1*Nq@)sxG)kA0w2CxH3JOSy zh{U(&+zfs*9G7c-vIZ)G`3JzsUG2u>$(&{i1h*(}j zK)%s!_Ex!0VR*V{@3+?H36ZalzhwY|rz<^7WU6H6W`?-Solc^njH__8)B+gABFUH6 zUGKh12zURd%F*^Rum6ovbNBENS&;y&M_|uuFiU(SVY%j!9#Fm1Spw>(&zX#}#mT*pR zsm5K#tz4Q{tX0PRt9g1w#iDR1!MJH7^R)t0Yyv(%1#C`zb>j*|nFpm_OVceJDbmusX{vd6NEIpB{3#>w|aQRnc?60 z9(t7vF*&7j@Dd#V_$`(RccX;UVbD#S>10tNbi2%J9F8K&HN?erh`N6Lx<=6k!!q$bHfmb*Wi84ppfV*j8sHa zTzU7zXT>mC=lJVA9*eur&Q5C9uPGUlP&sr{tPFOe7yA zKnOM|{Gu%yh5o`D*vkn3|5|`hMv3};@W1==8uy{N#BB+Y3+V^#SYRNq6Ih{RU^Kk<1T zQH&WDQlirP6gJrWMzLB3G3PwUw06ocn%j+Zjf^5XJO1r7w>wPy-){&9xx-50Q35d| zqcA)@A|)JrM}FlLM&}|>>Q+bJMu{A6&4?@ZEdo~h#5Gk{2r-V%i~5d8WHYX;e2B<4 zA&kxgsUsVWsr{Z+S1UY68qlyIxHRem9gR__cH$j4E75mEmNYik->mYi@SZ@hzbf2K zFJVfMjKw$k*X>Wh_zrIJkD)4DeXLaXA)>EuVnRHHA|R6-#f+;WO1tPiz+M~L4nQLp z1e`i|7N!zd$zt~`y+%*R{8E_NF4J9ObXq8#s&~VDEzws>PgOK<5e;o{h9jjEMx!o* z)i|*x^s|eZuK{nQ6zc^u{}p$%EDsT)o!*qSF!HaFvIm-IZ#Ie>1-}Vp>JCKHU0B9$ z+G@{6yOCKwW;q>Pg^ltD>{CSq-*Icm8L{i*2-{Tq55V3~Zhs)L!%>Nk|Fu0b;fRJ{ zmdq<%!vtBRo2vuU6*(fZT1K+?(YU$9Mei@ar^_v*^Y364=RbOe@!m1xZW7zv_Q5s^ zh*Y-lSv`qbo|uWv`TaZL1-*2xs_RKz(AXJ!g?WP_kOBpO>um&`2u3S3pTgr%?P|60 zw0)H>#FebwG-TaL@WmA=PX~!tzkM^V(H>`u&$(HYn@dXh?y=dcj?WEBrY%BJ=qI4g zG1-Dli!UK!Wel`7YMBd-w^$M|Y9w=Q#%4l;WU=Oro*KTSly}gU!2hfjy zLwqTzcXb133!RTHMkM=^M=^-=L#wvNW@H8}{*?w9)Jx6O7~y+;mbymz7Z-y8DARG3 zfGXV-#XPv&ku`!XlwJm~0`cX{_%RFsGE@Rx5Q-N#rxk50!w6EIF9!1UH4Y6EmLd<`me~y8`|ma3i6P3kofD!K9UZ%kR`9) z6*uqot+$$erR|HGwShD8FX9}?o4KTAbz$eDG#-bFwcVb2d^iOxk%sa(^s;1XhX%OL zze33c1rae>D!9^JT6P_Z|Gf1f_16*7)28hWFEW;6oBHBE;l63KBhSB2t`Vcrgn|Pn zjLa)hV+JK}w)o@6#}S+dy2)j;fv3l*0Ds;9@k&aa#ltvqJ(YU-Br%RHOmJncPrINb zxQriQB)p~K!>joqo-P=UAYB+YIpyT!Y^9gSw-gtEAcQJ70VyaCviMzR{sWGi1BiU* zt8t)q5#)q)GdU<{Pv2V`aRDG1`Z{U&&AE_|kr>OH7qDFn6lIEos;^PBc6de=%Ma$8 zC4X;D8Km#8KO#0=^as#H6oAOLl{Ut*Fj}i)$4_j35>kp|3q9K6@6B!Yt_*dxOy(Ek zGK%24<>4D|#m;7s9ZEB%-q@B%fBM*A5@uSS6YC){uWLQAmaM1)%O1h?*GJ|hLeaBN zg2>wWKf({>x>aU;Y?UdArQMpygBNHe-S0JRIMpppR@o3_d?csVuL$Rz1qy+ZU_S`T z4u(ka&R8aq@S_?2U6hhA+o?L)S;UizW$F^D{fXkQ^!#xX1SX6y0yTR8t^xK?#*6{JE@%t3v9J3i;MYLi?Fhq9r0;H<`w~t2Z)5Yu%PrA4#tP_=XLHDQUI9RyskJ9f8Ho>04kIoCr%E}s_^q`mP8Mwb9%JuZCkQa4gLo&X?zwgvRD$NEDnZ1Sq zMxs2i)MNe&7vY<4rGqlVS>n%FSRf5j{rmG(7m}vu#*tuLa`{7t@MY!ZbL<6y zGPuCVL8^g9VoTCNc1%^IO85wERs)I-$|43BscqH#ru0@L|7Ro85sD=oMxKjtBJ_rC zzco!IqW~z)l?KswMFvm|DQrR;op6_lvum4_3Ja)kk#ZN_eu-z2b^tg{6c%Y3-db6c zL1s*z2h!LBWC&x@|6FH*vrhME`0~#<{V@PFJ2h|<7Q%Eo)kSuPjJ!Yuqz7VOmpi}1 zl2r|L(d7B@7Ut$C^8Q-Y$-WRf60tvU1$?3RAH=pFTqef7ab&)rf)jhU*5MQJ1p!jd z>&U!@dg%G_={}Oez!oy9xPae3?m96U0=Jm63XYwA9>`+#inKGZh4Amh@_l^oy;i&Y z8M=9{&foT7S}@sOTkDl|T*U33pu=UtA8pVVUYP!NUi z|MTk=2p&Iym_9Un;+S;S8_un9#w>tES=lO7xNh1Tfao=%h(3f85VRI_@Ke2ETdTP; z$Ii7Sc#N@2552S(FFVxDNDVinyhfcsa9u$gik!7%TEM#p<$xOkgG7w>H+98`(Zb5R z_BToXV^pyvWk8Q{O3aE(J;5nZMAQ8Qy+R30n}^p}ZQjD~DCZs4ipQf~{ zAm^~+0(;BoUJ2uress_`&VyHT0}K*HFp>5PYClvYMYUVae*J317VsIbZLrXb3~O4k zy66kRNLa>yORy)qFA`_`iStx7fyW0x-TKGN%`c&~SAi!l0WrM$HAe$!b;T^eNJ1-X zpzR~;r@IwQt9ijY6WHRw__!aFO!*o^_|-M6(MclNpBTfEouor?+!a}pdBK>(gK%Z- zKE+EW=CEN6{;Pse+KiJ6JT~+Q3%3KliA!ghNW&p988ywsvHxG%k0J>-M=$whXaEs( z3m>6Kwm~`E^aw$tKd=oF!wj%EiE0po<5CbouZR<>2V4G>XVwT=dS)qU505Qkt(pdc*j2i&$X z_V*(1;%ip`idig@64%S$$%0|{14x-KX58n?n+<>&UpQv5%rWt-J`{iex3tS1Ea&D0H6GKD;Dj*J&FjQ})Nrf%n=~TuE z!Tf?A>rj<+u3Z&-mi9~{`@HgfS@MY%xO7ATw8XYJ533at|MNi!nAht`L|y%Y_x`mv z5{OvU>|L|%nsxItK06`fCvXG4;u10blV9a9*LV%cWtC5@^=|O7j*26)^Uc2PuTlL< z45VJ1PFGZG@9tI+aobi}ORA8d#HHcQj&5xZ#85p5MG|lU@3$w$sN>{19|YW@dM2%o z{u2SS@zcJ2aA{X8)ZEUg471%NyGUVa*H-UipNlsxDZ}s`;Q`eBe_v(4!$_m;&syG?8kB@(F zf~+UG>SBMP$K?|uyz2SRj${{eMM76x9G2E# zRr)xMEB>7fAxQaaG#_tuFe4dG=LJ18yYjMbWVUKH=^Tm{r|t3inQAuP6)b2z8I3cj zKbY+Tx2Qmuosif3b>Ukd7~yPJ+AZ5qe|}+W5X=I~&%NoM$nR{0v@y6J?0k>U?vt9Q z`F=q|9Ein{(1>MMcOq_dFQFP5A{_V_8cnyuMgshOxK8PnO_0_anGCaW|ik zD+hzNWC5SjiB~%<9+Hd6S(OEUS^D0SV*xD#MvTI`H~#d)HQ5f7!zJ7~2~Vd>65o57 zLx80xP+oA=Rfi*C>_am|aYrmjEj9Rvj=Q6pz zRXT$0>;M3BY?$UF>d;geWb4U(CLqZZw|3}G+I>+tfL(KzJ$pw+vD_hf9j+eSua!=h zf))p*;p+mjun52%f|PL&1v2^2E1RNd6Vif4 zV0hz#k5Ne4X0pyY7%Ij9tD=_+YPKLoTa*4YQvxaGD<7wV<49&!BZCg zMA>1^3xCgA%|JImq@qU&r|CdqP<<2!xcO+ZR%%5RxWfqYxx}3(EyWq5N{}}qu3?8$ zrx^T{2LC_&3L6ABs1!Swx*gy&MJAVk)N_uz1Myo4Q%lNNSu@aux=fBcdk*2+EGw!u z9i6@+&U1u6uAg;o(&<|-sF$f8wF%NK#>TKF0Jc9jm4dMmNXmtdV@-W$I4W7LBQ5Qc zr5@cs-g^HHCH>cqbw$|}Xhrz&;}QQN<{$gx0* zcQ5+!MMhzm zygI+>BiT!I?auh!W*>9`uZb#tF0@onX|;=74}Fe2om3>_Unp@whezUDv<8@<)X7p5 z3xO|<_B}j2q_IPHoU0{>yaKqo(=dF$I6SgIY#0ua`G{T(XV;*i7G_?5H6)KnKrbqS@qzy#Z;mQew{%L zm6aD=JtLzrUuFR68g|2e`o10K$BK0Aef5+lFFQ~|5G%1xjyp$0Tlq=_fl1H*@8Ojs z3(mj-Z{2v4jF|8Grm;A}bRQOvVYN}HrS91H;7o8v3*wMVK_6d#gR6zYDp-j-E*oR@ zS2o;m4aB#|WS*58)KgyR>YX;MbWL6;T^B7(-NTqpR!Yd=s(q}0Bda(m2t?>TGQQ_Q zs_ogr2<1kx*iK|m40S9%QEM_CE+sm6ZppekhJ0`}eMM;jcy5Xy#l5=q?B^^#J#b3T z4lj;jb(^jO5w~z!$@2B#gH&vy`swTN?E?j`C4a=)97y`ZAdrwOR8^gxULwT>?(i|$ zzoCa<6p;JQTj`s9SnnyTc68V3vDw;)k^|%VoqE&*%Yb=Fl=IBH8ejz<;fG)359AN&&G-Dk;S@TgRTEAs~kvcpSstjI;@%%QSfg0`pd~-vx#KOZ5O2HAaJ7h~6V(4HdrAeV&8UOJjKl5}rkg}iP+;P}hy8mo6 z5@m?hLctm&;eylQr^;dDU~S17Q8|j!Lq2N!^h%tm8;B+|e9m#3$86gy3F0;bGxY0qvsCwM?-G z^mC1mc$Aa5w5)2MmaV{Hdg%-9+|3+0I^4IIVC~=VjN0%JT+&VX#ngB$j<3ef9SQd{ z+KZv?u+w(WadA;P3dd`}>gGMP1jRBt({Rry8A}`otB_;#PN)p9KUi{hfwE13ti*CT ze(te90S7OZ@A(>ntKEfIwz<(XdZ75v2E7pFVAu{HPtx-*3bLPyu`%Tz9UQ`UJ81Ey zv*7Zm3iMRTd=fiyYpKx_!5N#$#&bUPLVF--eZ$nv{w zI#`R4z>la%r2pd;qu{@n?pULJSke$)({;)|AE9=mGO7~~=Fpo9SgjICRZKz!O=x7- ztq#N^ytVf@6`-%Aw6n`6pecd zk(B&9OC}(+RJ6MV%KYFGF1_fVAqm~@frj<7O!d8oEK|e+jbl&|1~p{QqiVzTil5Uy z5)Dp!RMxSh609ok6kgWi`taS6raa4ZlXr#Ecg2t!g*%-tr0Dyp-RLGTg>)(tD(X2P zt&oW9S0w!=;q=M_^pfsub8U}vfnVj@{48nc@1QRA=L8Dg^Cb-w*H}AS81QkGGF3Wg zNJ(5>^#*ffn)JWN^rGS0FV032Du*X0`1d(H>R~wAZaaH1C{-)+!36LnT)9PFY>s!1 zl{=#@xT-UYKV-E1n4}W$7|JUXbj^FUu7?+m%8EajX8YORi`Pk0OWGmlM$&N2vH)P^ zIF{VeAeOZYj7G4nD1ie3IaJJEp<&o(mR?z2S0olOQ_5I?!kKYlE&*WDq<|53kRXNixpXGjfIijA3iN;vJjQS>r z(O3S#7_{u;xsTft@KXyL^5}B@PxU-GoID`t-?7}euG_E22R}Y=9v_@x0t5bp$hP+G z>T_>wpOd$L_#@yT9-m@3GQ^kDH$(o7Pj<0gVmAN}T_`U0Id4dXt+d_r<@V6d6IJrd z0KsC7SPsLyiUq1Bj|j~saf0nGZV)dor(AU{G5A18OOoH23i57w50rVcPog`R&pH{` z`ct($PBNnN0Zi5d0m=tYj&}9LTOJC04uQq9E(>D6-@M7bV zAqu%>L*+teKLii=VeBJuWXn$7H$uf#;^{N2>w6NyJF**_21c;Zpf z_oqt0LN*|tRVA92fSdWp#@e~NndNLW1t&!ib*Vv7kzwi0Xgs@{*gThuwH(^h3OLb> z`9s!(F*xJ2%Hm2d-@}4I{>zFZ5KU%scT@O17Mq8j*fnNy&&jb*f4$p50atct5Zpsh z^cqa^tQAP@P(@aWs1IXnz#bucci&8uf4;QM;JN)v=J>e#oL$QFz#&D#_NHb!HvxNp zdb(!Hfc8U;B_f4RzS7zn0VJWg)pM$w%=s_0lBTO(+Uggo)M-`X$T@nvBzpRmdARNI zetbc13^@x?2%EpY4U8t)z@$A`UVXQ55L9fi*=iPJCQ8 zBmJ>E^rSOjWAW*~gPmg;KNhA|-M|x}H0=&y`;Xd^)vILF9q=WjjIU2Jp`pYq5Q98C zJeML_mfUhtF?YX+q1?*lsXABG=zr4xV>O&kd@q{CwP3s;;^(>p-lz~%qopKi@j6b@ z3B7ufOo4K&qGPq6-cQCWDcnw`kX(H(Vilu|E4`2V=qQ#I)$ITK`7iTU74KKe!|Ye{ z7hcVH?q}1sJlC$2Fa7+oAoMTMO<<-865s8?3SuoRr;@&`iv4Q5hnqBiru56jW$2-# zHhFCIxl?-hI9=p>5_u&pt^=PX=8{LG(i76ewM5LpP4~P=U0fsqWagtd)UPyrHdy@0 z&Q-l4-TqPr39&u2)s9d#VSfEVc2D$?^e>}^0EVWaL zO4qEH08*|BQ*!)=L7Q!me#;8JjE3NACm}68d(bQMIy^WnjrPH}))!veQ+(ku?_K7T z%$AN)>Wp>zZ;DFWUQG)7y!NpxG+c8(-%va%>e1Bxbqh)N#B#<~`S%sGR6+E`qDlFVAmyu8lLrP*0?~rw!>!4y?Q~f3<(Jf6$R}Biza9N=Vws z>3U~~h$qBIdwNX}_;C?1m%CXTMC`4W+C9z0`uYy1670y)Mp~)dDkvEu?esIH1(;}9 zv-cj`A7$zP_mVEMI;$pTqfE3i4xY8c@JNt}%QwQA#750$ngV0e%g61$$RZV-AR18f z7h)A`-x$V|bDs&k8p??$yd|KDAH=PhL1<^YruAi>=?w3Q-SXJ5ji>V$wAA};(a&`T zK%188e^A8&io$RH8bTVb1MFtVfoP}z$UNcy>v9Bu8ppjtwW7ap8k~EFw9z4Ozo2N- z`6Q@6T<3(XG&22q4e1i}jHd|Pm9<*Fq^872VZDd>@(?UEwS8Ci!7l>ULY@3DuF>$B zD39b^FC4_L(cks6Ej507v(uY(us`5aEs90)E5x1tSjcSgbe|zlRIh}!WvtQ2>9t;d zXlduX4j`;~9p(Ckk&fF>7Vy;Ewb%lkVR$}0`?pRHZdqmvtICX*RJsid9a=k zuwjWWV}*n`5Pgj&zviULW51Pt)QlRPcLZ(z8J(H|Y)XkT=8mpP-%8<%j&{djr zKi1^C>OLwUp*qpO{F|;njZ0z8c{a$FlCf<@r9jW5K>PF1{s=L5SUWr`zBg`PFuhUR z-xYnWf?zFpf$DndrnwtS>X&rlJOso$eq93rR>!rD9h2`oqkn%zt=RS z#pfZN=^DpnTvp|L$VA86I`OFKQ=Wfq%ky*QeZ^$AT>$p0Apu`VtL%oAL_tKPavq56 zsYL9wu@Xmg@7kt9;MqwA2q2Z(&JKA3kM}ZOt0PpNmj9NpdQrpK(|(FsK8Dn1$GNVy z^GHXzq-VJTP)e3?|F?s@khx9&48i*zRCp+kVy)jp3HgD@A$i)&%GAt0mbj9u?%XL=kw5FyeGC|O}StaQ23SP!NMF$g4Z%x0)plFSx z#|rE)Gy2ZrI*~gNf$`3Xp>1DC;~I`3h2|H5fdi#Y zacPcm{S@ht5k@SsxE^f>ik7Zc7#8?wm^S%c>IS8GHvMp$dBq)>#Qb^aO$Sfli;)CM z$({XuN}c1r-S7aKoeUdcT13%=xurMq9MyJunAKo6uTgj{%X0VUS00e5rS>^&vh_8X zsVe&Gy7(T=G9weqX>M>eEgu75jV?5KQHOHr7fi_ zJnTSsP=zdx`VZG<^%q8QL5+m;BR`b#HhU6VpyAcaldHTXA+d_#x%L zb;T-FW6(vXV&2}o$d;;|CnjEZOON{ZH-Cr>GD%S@W&KuaLNHt38&Hr~^`Bkw-LhyQ z%3Zl1s`Pj(w(ji_@4?%tOLzPdgD)-O%g~5}DF7E;w;|W74PU>InL7G)u&840YYe zu>|l255)b|Vu(Xsgb+o#v^W0+68b2Y@8_i3buNrHGG=#u$b6WK>$0}-HUf+at{L>C zF*s?Z9ra;2uAkO21pctu+55Eq*XeQgbRZ*TQ@pUr;LPoZtfS{*E0^n(bKb}KuCAz< zOL@)bYey9fUu_(b40K;yn|r(f+58S)8&jX}%xp~RgFu;b9@`kX z5I{`}J<^69{_Yj)&j%$wlUX;J|;@ z;+V=_DhQPeSA9x9W#bU9fmq*v#Oy;oOzHOJjX2w06Hm9RsA?<8M9 zl7C)a5oCP&AYgCXMm<3gSJ-xh@l)5(2PXUh>gXfqeuih+IWC56%a4iVYL9hh7JUu! zHDAbHy7}tHsj7wjaYC`NY=Cfm;j}L$r5z0g3+^eUdf8+0t7wADYL=jkm_-Ycq$-Ua zuNOW8l0XLBF7xP>uZ0O`XJ=S3NMt#^&F7(}SKo!JX1JPt4iPQIm*Upg8Wk1)HepPia4onft!<_?9YNsW$M@^?X=n<*S!@ z<{zgp!yh-7S7T|@4~OnCo;k^#`TiyiI`<1rQlZM-sdSxf4WPuMAy%3u{^5F7$?PRC zQ$B>j?;ooOf7B+>``ZJzWTqT_|GjTZ80IP7@SW~=e;Lq(s;4?P-1a{|I(YlZD8oFO zxI=uqK!1WX;f?y0%?a)PcSGO8x1XPpSw-&go?N|k!xYgLSiE!a(O~83^V{sIg9+D? zllTrgFq>vg46T{Vo)ZLtW6g(e|Ngr+kT^H5p2s!(wVLKLhgObr$!I;d<^@VTa?2afo^Fi&?&)Xd zPqyX)jPuQREwmCAa?Tyuf^$yDvFQzpZc4!Cre;%-#5La^5%K_yirriXS6i^nLWRa`!gZqgAdMSnaxy% zGsLP#%dY0v{Yc~j59P9@eN^kdy|bi@e0;)3L!3)6^)`XEUtQ@&M#9ZJZS>eD?T<;t z+VKPys*XC#->$B+PG#&E{boeFlz=>XdeNWpF-udM%OLd^dr*&}}QbE%OOQSESs%a$9=CuEt9n4HC-_kn27h|Mp5n0|zcQZF$45#`NEyFJ)Q$XROY`9Rf=I((qV{{)FxT1faEw z@0ul9q>=CVs|N(#Kll*e6^F`?9!PX^Tih}6Jp0QNCHYKZYkXs;D6C<&GI!EV+5dj7 z$;T}tF4F4N5nQ<~)!Q-Ybg$G4BRIZ#CVIb4ixHHTYIe+>mh7__GgIm0HmC@7rx2g5 z&EgqR88R`Y3O)09QlS3ITd%@~lB&YQbW6fHqe)>4Mg3aOaLbxa<2f>dhej!}zgxdb zD}^Nsr*4sjZTJNw4CB~TvGMtmmDpgLM-p^(l=ul`bb z?<2+Lw>D;Qn|L%6=vluZPvErPlkQuZL~4%n|LZ}HpS&ZctF~1%)NUiRIn zp^6r^JcJyqOuK=Rj8y1O0x_%-dp}HFc!DPZ+z|$2IYW-&34w`Ev~oao25zTNP1kLz;^^y zEPtTE3y>%OevA#QgIezCcJkRhiI`jSliRs+1^-D2=XPR*5EnsYp?Nary0za}@qOA=d$cs8 zzShdI`wa?2%w73E!Vjvfrnyr}Y5#?xZB`q~GZ3$|vj~$(hvI#1{4tB0qD@;~ZhcS? zW4mW>P;gIUO@EEaxBVjhGUv0-`MnB_6bYe#vq!wN%+J#`4NYE)S3LEWa|)Ke?PBGc z+)VEAnL(*)tZJ~ zx5tK?;?AuA5|W3rPd?3XEYH2WlpxWly`-PBux;9CGiO?T7+j8adVucl%x>wxGdRs5??3n$dy>6|c z=AZrnO%CMK@!S7X-uz>o?dMl8E@0SVqSUefxr2&A)yy!E!Y<7Xt% znfM2Ojq$G5Z+YtKQMcUuf{IiGq~5ZpiBL6!q_Q^fygcIn7NV4CtzWG(g}Qr5NGT>; z`NqRacD6Ul_k~mB)Q0rGYekx0S>okmcbE295lY7$Vmz8*2-LXu8gl;`BHtf>$}_zF zOhqVpj$wQ0ZwfD`H&dWe(f*T@l&dLLvM%Oviys8$9(T6Pba2b{Z*zWs)2jRHI$>B? z5b+Rikl$*T-0sa-DHukjWB)wRe6oZ|zxu_AeJI7NObb!28r-kK%aa2z>HBwSHl@Q_ zQmibAydXKPAc|I0R2&mKhF_2;nl!?d4&UJSkNs8?|RZ4aG zXrnqRT85&Eo*ue4Mo5g%X`-;xC{-VSk0;;tvJ{Vt!)vB zb4W}b*D)zqDW`wsuH#AfCzW0)Xtss?W%jEumkTP{$(40C5qJ;Z6wa1tMQ^@wQyb{w zTm95R_9%t9+JKc^a6#@d`Jcxh`mPwaT`b_@WonZD8g2T1!1xBo&{vyPfmk%!@s;{m zsgtRXnZ{0<>dah%yBxg<8&7?TT1N$QcogFO{m<5q_}}_V*_y5zNxXOOxy|`tXBmnt z)5SgMRf(CwA}!7k*M|-d=30f#lY^K-Fjl?*Z7TGJodG+6YQK=8HYn0o0FmJq|0{{| z0WUv5EcC&s*EFDnYaSasn*g!|N~KoDm)a3*hHv;{(#83hM0Dp|wR#+*#SAMI$?H7a z3=LSthKvi{Wz}m7XEJZJg+Cm++n5&l*M=TBKLu4xydYVcuO!<2O!5|NOyrH?P|3K%iMRGfYc9&E$KK|W{(>&2yb%Q19 zZ8OzJ1G0uF-=rgabz`GajCiyDhWg8rzKr6+pdg<=A%w}?x?`7AytGzq*R=+VR%(>< zk|>ycSMI)ItD!Vu&&cl3{wVm4FWy>;qlqlZN3fiDsbCiW$ij$ZzM+oIFLK?msPC#Y za{*P1Fk6to58)#5SH8_ZHCa1C9?*XqrH3v5QSAOF}#5fd}~NUbsdczW5Bex}g<*fe}4( zvdYCmGDhL-|1`TkX)HWIBvGH(^Kq(ug_L5?U5ZI5h1W+P*|iR(oOfnEH}qscsdJWx z_oeD`A+2Nc`W~sK72a;D@a`($_#;5uy!3Z}A~Awd%kNZc2An?5V>yWJGY{52kf0UgObK5#1Tne2>)a`Zt5g{SnbT zd@~VAcwyapNYJ!AgVX@CR-40hL1aK+;8!^>sfVU6AK@*9ec{wCRL@ad*MG;v5xAQ? zRCsRe;An@8e^+ z8k73-QNXD}!1q(!@0YUTE7d9rsiyR(NLD+MUC&flgCFtCUuX;36=_|cWeO@jO?jLt zQyF#D5UdGRU?5N}s_sR&Y8RvBbBRQr?|spgQ#st6GD5P5WY%a9BO%Qh#+`mkVArr# z6XH6bOr&~y5;8}|zlinCB?cj4-CQ}Pu=1hN+TLF7<0ST8tGjHLEG#;QGqevotA&Ub zw!QHI8tfiLD}6mTQewLDR?8BOBnkgR-qy2Yrwx*>d5a{RRQR_o-nl+#v2cn^Cz(>t zxi^I&j8*C^7-h3mKrFROLJkj9D$;xZLKgI2yf&(5AR2S)s#S;nWGWA3LICqdad}sf zqm$v$oL6q0Qd`hz0;E>o?y=r)IN$j|dRfAi1uA`T2xf*A5zjf~S35r|PcaiD;1Ea% zhS0**5F&U0oT4j{hHW9-#A`h zCLd3u_g1{l3n{csPn=gmloG#5HIgD@8%7gOVv0&aSSN0E^y~aqW|5G%494o41izEm zG^}Yqtwi2+tH|JRQa6R$%Aoi_4kn0@+MxhZ5*H&ok#F-knxH&p4q5DRszmKGOn|8oPwH z$9~)t?zaLsu}BE7Zg79ZYDTph6Ge%6gK%?8i;(}JbCbqxAS!C_f7P67oW6k~(MIgD zvBdPiqE+d>s#$iYiT<|0U6r2)j(PydG#k`V#Y}KTWaB$3G3yRCx}34B%j#@i54D*g zSzNz+(N)QS|MN?&SnoQ$UP$m(fyXjYwq;A$FAL!eCbD(FhTR?PWZ4Y*s`vr-fY;-e z9rv1=p1!}VQ#G+V@D)g6Fo4T@Sukr5+Y8k2&QOX!=z`ItWOA>l&ia82S;9BI=$&`I z19q=GuM_$r{|wUU)$d7j8J2Pp-uAobt3@Th#1{RVh2K^%Ie1e$`%orc#9wD=@E2vh z`}ZVa7jBc+Y(K|dhy9uNCk4hqd8zC8&KkJR3qg;BRrf zx1G6~i=@Bi)ROLLB)s>*UEwhx!>>*A-aXmjPGN}l48r_;IY6~mi>R3EcvZOOdbVSd zE$4b+jr8y0;@m||1zwZaRIhK9hk}~=b=v&ki>2)BTULN-mw?|-$cU*r{^&B${}zAg zgAP8akw{)7+?s@Q$=Km^CHM7YTFm|qs38ARiRGBHvu)Yu21Wwe!sS2baaMYT#Hm#HBC zP>j7}0up-$9fmmK73CkwL)Azm4WB92KpD>}wknz&e4>*Jix`-%I>6lJcCtc{{h2C$ zA+PCm?@H-Shea0;Rx{kKS4=g2_DVF{o6|zll>ah1%vr^wHNL#n5+B>`iEyfS+!^(kJL<_>WLmpY3;%dvKPNXM&8^CquLkT~R8D(O*u%qqXCcy~j= zk@demoU=b|=ZnCnse{4$B~O4Bf^ESWiNM24{fu688F?Xnv8^Vk-gOs&IKg9~d+~}* zE0H+oj!A-0G3WJWVT?iiU|#||X13B4?B?f=Jf+Fr7MtyVpQgFWe$cKk|dl2gCdUR03tZ&mHC zoX7}_xt191nS{A*d61_7GCy;PtRVgwOD^-Af5{6X74=cPS9-H8)ZuHoxoS~Y_YeCv zLqPJ6zCCQdv$uCsfZ)RmPTecNfeA2D4s1o^0xar8p671GSG}2rgd;SF6H;LAmgTFtLiz}$31@+l#(~1n$PuFAO z(sYPce|~>{1*#ccy*<<}{2_+iT0HRfFZ}os4Y^cr(v&*~z^b;jfeFc$aASr1BGO%I zD|qdde7J$VaW#BI@!wgIUBhM%ud_`@zl{wyr_StJDy2bbm8OP2dKD!5&)5|bh zMC|OiI?p%L0Xy{UT`Y`EvlCSCqET*tc`Lwc+V&{rn+d@rT3|{gF>SGBk!y{DV?2eF zFEKCNI@M<7PoxI~a;bsJ8ZC_yVp?^vcIIFc{i(bC5d z>S+)I4(P%gb{{UVSrQ>;8!GW_&zZru`h^)e!s& z@+nAP9~Rv`6Z2$w5Lklqi%vbt$({#0Hh}&@(0Ni&!Xt;o-c0;<7HKLGLs@;-ue1lw zQw6qFa=6iMcbdE&tcqsJJr2quJlO4Je#4o&R0bD4n>_YU5!vLs2U*j=`#kH@k~UiGr~TMuh+U6I_FR zZo;Tjvp#}BbQx?}Pn;dA;eR%Bu-Fknw2K0y@I;6I_fTPL}#( zI{){B)a7|`!66b1s{J2&Qu`{?4IZ43Oi%dtq1HSNY%PX}g9g{J)WTWpZ+RgU35&cT z%Y|PTniiJMrELi~`7@^BP>zP20I5q+Aah(O&JF4W9eMWsJ32M;*-@Y1bGZC8hFasl zcMETL^Tq<`?-J&)@s!hMRPnlS-dw`XR8hg}eqId2g5AKP#4RdBl9e~`$J`<=DOxA$ z|9!-HN{FR5D&2}th?El57w*ClwRvPj_T-j}i;MGQWk>wY`oqzrkAK2QWwA~J=L<{V zQ5YP3ATrGu_5VMt=h&wr$t$G_^6TPny)`Yp?ds~9^5(>W2my`*c}T7nbeX?yMqJ!O zX5Y2l`fu27*^Pz5RTPU0cfwi6bi=t{3sFUpfGmgL+8!{tSikL!Wm{NBQb->o87M-4fA^(kf8}Es_B+`c$N$#z;c4?gCSJu5q~CxibZUKa zGzSTa@F&%_0}yviS7KWCT1I7Yl7k{0s^=piU{DC(Q+ai?>qm8CG;)PQyK`{bUHp?W zxyf8Vvdi4~OqTaf^JTy=YxW#AbKraP89QiXa{a5J!__EA!Q!8!eRg5)c|Md5Ah3-Y zt^;GH&!27*cw`_>7k=9bY#J17I@$vd*32x1`>0Vn;uF-jrZm{mY_ScDvE>PWk)awCx0A;CvD<0bld5W>R_FLIUG zV2M$k1@fp`ALVeuTj5obFOdIQWMD zmWB^s9fy#&(Ml^N>brB2_I)Oa!KbL^fWH)AF$BeujA57HKaI}BEfx7zJa}phB$?q^ zw-pfLl`2;jgB-+6!WT}T;-}~cB)D*o%_Y`5^_@4!5>AL<((i_w$w=FMBMuQ2_z=AA zhJzj&WnGGhaz%Mo+)q{$Mz{WL`L_D(Dy~*S#3nn~iuS$mbKuf)^+CkZtnO&_5f~@Q z!vkXEFauvh)9RV`$4Mt7N%GsE`*N{ZCZKS&Lm2XA`#xIE$qG-ifKZxQY7@YC+pP#j zdHb!>;zQNgSu!fNffe{&`!N?@Eg`f6C(mR|=iO=XgZvEfI=YSj`3NTyg4w*N3|awq znRYcKQ9;CO1>5PzLzU5W!jPrzSROSvFMCxRI-mKm?0n?R%>&2o&)nK8EBVCO(~OK3 zPU7Fg(%W2e*LU9}JkjNa|5^o4iOObx&({!aE-*bPc+~)k#nafLFNmQmmT-XycW<71h8F+UA%Zj-FbZp6!Y;P!SI_LCxvfr!1^okgUy2oN_R0u;XcIFKbT zx*q5(9+43{K$d=+87^_G24r<3PehQl2k-ZwpIV6WjpfmCGHp_g=W%s1ekb& zoFqYprpbZWlL^>J7p4a_223!V>77zaMMPhDuK1OQHk$~+*bv5b6!?7nSA&^Un>~o6>BEQa60xRj7AeiTH zKFgQCG?XJ+w7UoZkV-_3ID*fd!T( zSO-mDNMGJ~Ii)BuFqsT4&Sc)JrBqT_@JT}0sVCh@Keh)*!8R^}9!NG0ljJ~W+!ILe z!J3I*R!+`YfI(^Wau9R{ErJ7EOY$IX&{M+4C|qgTOQO$*30Wm(bR z3mh2->#^}vWUm>t$yQ)WN+f=6FzOZJ|G?}CwAG-O(^4Mvnqj!H4<#~3g!xS>-6PHV z+Vt^Zh;6f-n?DvD{!WM$(DHkJyuxH^pRM~+hCHGk7xCI8SB$57ggA#iY5e|_`=SeC z6C&<^^p38}Lygqc2nqDH#pyK7&=_n5Z9D_>Z~99d-g}}3l0~*ul)E7n%pOlmy&`7` z2_)y*OLiX+y4R_dZueATuY78lx2;VG%)wvcd%kh=*fC?4GIdz4 zs5j(DL)YZe?ruCdR)K_t(QS~WAbFdpdO9L7W5fr{$7`xDJ}iq>?djw+oOu?87s;yt zIh1b6k^k|5(TjWDN^odtFh84iD@IJgWr|i{xO$bCl%xzYf&I;hNB_eZv>~tydkxuC zRsMoQeKrRByVk1Rmo3VQ)-X_u_1GKlcIs~oMj2%Vo{TL3MG_nma%DmACo|>(G492S zy?`AUl_;l}7)0s2wc3GYNw6fm&+9op`4eAD(g0y#^bGR82xTIHNte~2-|w|7)`&6# zJ3B5YS2|&!v?Cx;`0&14@7YZel^Gp*m#95+H;0`*HeOP$fkh;<2#8)e2^BT)FEl^7MI>a(8plaPi;EkWDK7j} zt}v{@0%fzFs4#p)J5h}7D7Z+Ous1xcNMhqg`{71H@QfPB1`ANbru;PuufW(05{T}Z_Og__5Vwoaik#ngxtLT$@l8``5NF$_nPls3UIXPT*rDDxP6ztsGLoDq!YX{b1 zq}3FdDvtfXkMbl+x{m$!d%KS<%&ySPzbahgJSU`_#6r164Ca#QwF_w*#>h4Eoox?F zYyM}aht$w{0IqPMP?DRmc$1CC^2gk&i@Enc z1m)+`QxL>xKWG);+vhZ{-e?XS$VEH&4I0I)GZ2B1&RCV7rl%$pE0sfhS^4gVIa-|&SL(h{X3TA*bEk&2-^ zb!2~5=>GW6nz50%0&2~ox%*L>xOH=EN zyF!BUCmXy+PzJ7YvM!S1-RjGHzGwr~qJsY#RH z_s{IusNPN>aIV7q(HG6lqS4coi8?u^U~p%tZ$bKi1xjG{4UZ}l8RdC$FBKJadB6@B zCBenT?SRa_2E1|?&TA>>UKfN27Xb<_nWW!vPJ|ahJ-f5+_KL&_eJJST*PN*VRf3~D z!IpmFUd6%Nd(fIYc{%c>*p(mwhY?EeObT(AaVq@8Z(@kF`pVCfDDA#U?y}9 zT)gkhzaRls#-jpC4M0&Dstn@drVB4%%G5b@>*FS-_AeF24YJ@%!1&MxQh>4S$dm~9 zhT+IJl$(wdhp5t|dyPXW@0?u|PY~cuW^jgi3i4zx9P3@wqAX(PfET59*mY5A5rSJw zPIHDwG}`0%z>br$#gq|W0jrUvBbQ+Az`Jj>TdNup6v!kv;Ss+Hg)X4zMiS%glW#}1 z6GC?liLq;*F=U-ChU1q_a!;g-ViLm^v{05+YK)#H)b+)z>EtWQk(gWxg8D{DV% z3P=dO*kjP-b(hf#u_FKa&r68~Rs+&wnn46~f^3=shJ;k__r(d!!_tj5I-A)2yC|Zq zqpifupz>bw?8F1X0dcI?SG_c`z-F)m+!8M9-jBWs2l_Sw?rzQp^(H4&03G15c*)Sf zQ>z0tb_|vc5Xk-6@kuew#mK-6!Fl}-(PyuYg0tlW_b zA_3RciS_v+76vh5QtgRxrX{MWa-$H$>T)*an@R^e7J8a%v}=7hyEpWfBFWgjV&LAa zfUD_C<)VU)vO<`ke7r)oJ=YX}@up|y*#11h2-qtPHFFdTwUTrU6 z;(-wUJcu{mK-F!V^`*SoYjJ$)0^8NkNcpxyAs$laCkz*E#CEMncRex(S5! zpuUPhYCWbEv#lR_Q30No^Yt(&U6TT0urB*oiVDB{!Yr~_v ztI!RP)H{2CtY4m^m_u^?Kr=W9)m#sW=qM?}K}94hJ9*u&*>wpErdvF*3PjIht7K(xtz6dI~KF0?v^Yim}gm`e6kce;_9>3Lv=i6hB zSO*$U@u?@K0uNn@Px?;-4vQ0aqxwC)H}YvDdr){8DqorzG;433W6@R9@|>rfrxH>zm;(={`XSj1ACk)8{_4y zE-gDA{ir@YJ$<-$E$}W`!aOD1HU^2#rW2Tga6;jJ44@%;JDrNmYDsCo0p;>LoJy{) zSD_Ema*R)u8VLs*nUM5+ZM&XwLF(60k?OcNWLodvVPZnRjqN}RUgdMc$U>7EYlT=E zVRf7`8IsVzauuljr_lGb`F^=`-VRt_mm$Ir1Y?J%|pEMoED&I`Lj_W9Bk2CgLJsN~`TkEXsTTE35sYRz-KMLd|%N^$#Jr%_`A)H;?~6BdW-|82qm880*#8Z7R* zi1F%pjV4>7XJS-)PM#dk*A#{sPV+x`c9V7Oamckh)9*^6!z`0%S#`N-zY@sR07u~| z1NSdaT99nU4Qce;UBKh{{s&RbW+J++;5EGnR{-1#Z8ukmE?fvhUGrRH5~H-nXC6=h zpSc|0=PjApR_mksSk3@o(PXRZ>keg%qD08feL;(CvZj?0L92c-hW#}b`fx7t{$s%B zc-cyClWfBwzw>tLw#H|9#h*m`$X;vG%t=chwp|&&6U zyMmL5Vra|Nt_roNi;W_41I&5VV@<6zi;B&L9t#XV6rt;KAK*X{7#{TjNjLFNA}R-B znhcn4R7g_d{N5A>h#Du=Am5|u65^@*T;3Ot6cy>FrtycsBGEN*FL?+rU)S9+ zz2^9?!<@<0l#Pi&_=k1)8kA7w>(IK$6&Q#eiUC7fBp5Kc$Z!b>rnA^*jZEU&6f+J~9@dSbJn?_gQ?4`I_x=lGz;;5MJm9BUIs~Zp*0T zqy(atNFzL=4!{;TncBe!Y^5R3p#r1D_!=VK?)<>%`8vb&4v5hV!Y7UF)|SvJm)dI+j>^+4i8Q&^bJO0rq{vmMvsnAy7P$OA2mQRVQl2innrg zn|7IaP62T?7>Uf9F{Q&S3^NryQtM#ni>R2uNWsO$fVDx)XO97}zKu4G{-0E)nkcAy z@>AN&q(DjB$RbC;&m^?8gi$`WRZQ;nx2HzTyF5%ENkinom*d>h1Xl_E8mu4yQ$>K5 zp_3PsIn|xSquwe#Ovc>ezv(e=M%dY{mg|ojQ4AW-;L0gei@PIQ+0cuRkFV{$W!*kv zO^eAl!J$XvFW_!-f|!2qvo<5d>)^bfu!F02+{Aos8|mK)jQw+NE%dJP>?QAhkD zI*Icb=s_#4R&NI@r5>hVTpU_?ceJ3$7Y^v0ldKGkJ7%nv<79rbbe5P%$izrAO9-sM zTN%za*f9p+FgGdZAx_0oZQX;f3+C9zy3<%e@)}|=ZdChm&p1>)f5$~WKpU>HUWF;B zz4Ms;>5QYLsO}2En9#$Ny%*dlhNxYTwuJ)wZ9@14k_58b7LX>#e7nwzT$pg>$74L; z`P#&q(ieu?1{Ct-h8DoQu$O93?3C4ga%JD>)?|5RS2^O0wYamhbM@eQ^kcUN29I(h z{7YHZV{HAZ^vWnv5%>hiyCnpEA%;8WOF4^!HGCLxiCgbc0>*FJRp6NDy%@>3Z`|r# zF|c_GBw@yL!*7Ti3F8Rb8h@GH4z|?Z{{CwKIJ6435s72kj45_a)*XSEdtblSXGW!V zNtZlEmUZcP&u*+c+0;rPC%GkXTTciq0;(9S<< z*7!s8l7%-fDz=h2MJ|G<09@2KSE^BZHhgh`>{UoZWy@#)j8SuYgx2B$#~M!V>I|c( zE}_5Q$b-Z(k6_(cW8{rI7P=_`2EIN~&zcUr2Da4oH`@haFXH0HuOLdujC_Uyvzw9| z;!q4mzTP4*m`jTU%KHeT%OHsju7L!2#0&#>{0AUeL|nDGEnd+APV9!N`tsQ2xj)Yl zs#%}^RI`4gR_q{1JdF!!AhkLV2bEgTDMg!y`#p!)Z+Qr*s|XTZUa%A)Ni52e79lgZ z0-|B@-^|VTzaTM+`Lnks@ZJ^7O|)a?w0Dsu;Hn^~pzCedG#*fNL(|WB%;_G=b(2?k73xldT4%7!1Ry#>#vs zled861#fEsFsCjnykJyr!?yDj>hTbiX%OrMW5F^c1|efz%{c``1lDbr?GL-ckvEsd zszidx?Q#K!TdYBV$JpzV0JLV$gnY|Z5z%PIOeq~qV+Z?r1FdrNSN^x6K66h-*y5*G zwOfpbi+8 z?3tDDVg4rxQfzU*Bfle1^6-w=FEFwu4tqNhQ$7c`6r<|hAgq7TkK{<_RJz%JmqZ|X zMDik>e>%gQm;|N@s{=ugtaOQTqU=ngMz+w-g-UQC)BFAkCzfM3MmH?<3 zMxYW|3!$PDR<6L9-|O#7ZpJ`;?d7P(_(hm<5+qLmBPJAKG5H{xDcig96^&+FGuGE4c{!&t@P(gqdBujBKDETUj$Vhd0y}`izkL1Hl;;k=kxR!`XE*4ak|U z!VC;W=(@5gXVrLR!(W!TFTa$qzsjobt)QC5eaXh6#1x{d?wawn9=@=w)vbX;ZYxxWP>6|j&0g_miy=BUpvmfsOcdmxTNWSz0iY*a;y@epxBe^EMCAB{Oadt zF$zf`NsrXrDmWcoZ{PAxx;!=Pj*g)EQ{@?A06anHct;lNJQmvLvX0|8SZK3*Fxl*& zq0(IB($Yh&Lff?Z%zfN?$VFjA$c=*vZNQkBYQu%zyYl@sjT23OFl7-SL)wZbB}XD~ znQ!#g_|rspdEuOUsYT_!Pzq)EC4{bab`gE>qkfhG(p?I<-YkN|oj-?S2!S(qJntxD zNdqG>J|YWT=||ju{9uHk|H*!@(|Q3gy>5j+fI&>vX^?kHvW~i14Ye#;0J+PwVo;If znHn4pCdkxVFm}OP77VgTtfHCKSHSPlz^oifJMK2zj1E1Q7AC;cgsgZAas>o; z&Wur=(L{-`CN{Kq?>Ko&!H4+s_}7RZzYP-|pAN@nBEh|ILC>E1VB9QPeu+cxc?!Q3 zsQ^7P2uG`xbW`q{lbj8Vj$g85WnjZ zR3pj==_iR*i7l~OUi3v;5zVj{f;lm}``LG2y$0s)!=|EP6kh!Hi85NsfeZY(!?%f| z>Kbi_g_>~L_IrpeDEe{Fm-?&ys-03a;u ze&8-Zr>DioepxHGwI{Y;(_rY)3dbQRC6;M#!%=aBN?&(eaQ!e)y{Px8#qK7w;O{g- zW3A-~PGINvrTB*U)R!(l3AXi#zrvp$q84KPClV$|=OA|z8=ikkD(jMA+Sy)0cNmGfkV@ zHU|pAF9k1l9fcQ()bN4;9-Js&E~UuPhNXuR##MBe7)DwQK_E_j`$&Gs`cH6Wz*Is` zNTNY}?N+d~vNA5r&3){0wdgaeJf)^f9t-9Npn{q6s z9OR(xioVY=`xZdSG}zi9gE8F9$T8fQ&R}j5^GH99mMe;;$uynD*CsWlPh)~*Jxt!S zR@WEiV!~*W)A08;8zv%A&_aVtnx{{j#^RL1E}WQ(l6!E2&aPCY@P1FJ@~?~ic1)~x z`)=Lonmy9n9iU$zqiWl-9qKJ>>eUWkZ2UT#hcZ29C7NqJ%PSb`zjT-M{&N3P`yW5q2L$|OG ztVEHqjsL407yVRWQkp5Sf8Ls_kbU6aY6RRO_*MYXp9z#Rtv?pSN~*E~gtuw`acat7 z6&2}3iLz2zR!w6SeoES@@6ypDWI;LtGI7<+cgk4TeSf?H(n9&YBxt0S)5I@z`-+IL zQ@eb20ULy%#+ZFlUcoSV(&;F|_oefTH0DwDhXsuBDY0~UbeoC>-dBOoi>aUGtME>F z>rS@4wpNltIYYtMCLa~WGk6GvONQw5ST39XX%N{tQ7PtqUmwaGE7@XgRfL&Mkyow2 z!uK=Hu|#N$%%zl;S{oF<>aPnPs=)K=edV=6v~a+t4CJ`L_4((Hr;UcE5@F1PzZD6j z=u_Jd(6U>?e@ZfUzj-O`axiEZIK=z{0s`b8n(em-2L@uyUucPe9*0xm9c<|=&s)TF zlyUP4Uznhc9(5y?Ha*@VP)S{byj&BTL%X!YLSU*uajV63a67JBg_KzfEl*MvnkD)k z9FU1bU-|sP+InaabZ~)-*=|-BH{b-@D!!ALj**C-YhXxBU%Ya`rE?^&69^G_Hq3fzW+TBe*N}t0`fID@Mz!`285$65>*F zF@(;s*DtHnjs9L0j_i+S8sf-60Rv)B=Bu52CgilcZ6DZmtE<0rKJ`AgQexu$Px-05 zMWGA0-xzX`CfR8l1|G0LU{=dV_bBJ8ZGgPq|x(tzGJF`0ac@rD<}R z*FJ?dqv$pk0tO2XUyAXejJAf{NHx~@rPvF$rK@jxwxI7qoQGC2=7v)Ja$+6sgFRe4 zG8XxQ&M?gecRh-*G5|oFA0s$uZ4H!DLLp_PSy1!lw`G%MlJ3#(CJ_fqb^?vJh$at1b{V}|#`icZz}lewT)ul)V%3w2 z@9mY>T|MebI|`pJwD`+$(3BqL1m^Fmn2LL=A4jYl_Qzju*nFCO2D;21CsnVVSwXw= zUNFPBf!S&xCyDjub0#;$Z9K&unvws+VALK``a0oE2qeMoK2bOzE;p{jcTmqA^}m#~ zl)G8Hr+#xsuZa90jlH9ETG91d!$9)8tBB`KZ{y|8nkzVNGCD%tjIZDqI?g`f0N2xK zP#P(CauLnY`|n|bJWS8?c_aaA8+du(p=nAcLzfIw|6U$B$mLOLtgikh2^;l(v@?vL z#&Q&E{)P9sysR#dV^%%QMj00JGGi>2hUzNrMi$pI^{YhAvk?z&doRCx^T6jogm85r zU1=+=Lifc75@tntk;H7~Om=W^^6CQSVEERE(BJR4cz`6BCkBBSOk@cO#gJ5XjepXa zAW{&5{@nL%LeC%S68Off$D5|^?x-!!j5&zZ9%D4}PKScA3+ES(83tTsOJ2 zw|3gMop~wDAKwa6DiYr-YMI{vxG(MOIIEIPviw(ue+NX(;#5a5Uf=NgULtd~*B8o_ zu@lyRovf^G*O$kva6B5kxb%L{^ZKz#dX>%BQn{t3-TD&4Hu?7`mDFGo(Ow1HQP69I z7NowbN9*$75d|EtQu#wkA+c;<;&*gH0u%{0DUDWy9vCFxD$_;(I``^zob#Mki;HjU z>~%aAr!=<~-xhRe?Mi=~TWjfa`GA01VwVQraCewpDX=&kSxA)%JJJJX95Ev!<0kuV z(ZjebvMvfy_mRIVZb=0BC?W!+P3r))!Dv;0)KIP%6^o!<;m+R3wFmCg6W7#T`=9Mk zZF^-%d0y|N53|`=WCX+$Fh@{xmJAAM-@%2 z4f}kxC|*i7BJQ$gyNQmPC?5z?ZFrv&}tOalCSV|FAFHU}mPm z;t>o9`~Dh3m)(S}OutgL)>bXnky80T+#VDFs;pp(e@aoVP`66qFraL{vDR~#WzERS z$BiPs9K^-Oi_V^v&}{Z+DL1VlCCy6v4@fC@h-01bI?)L?zUK>qo|tQn;}~?l;$Fpu z$|BSJ* ztZF{?Za~leP6Q^+j(PXBJU}P68ES9mJBNFAzmo8CJF0$@{{OA4EmqV`m)GY}k8Q99 z4S1Bk>F>^Va~yunBg+stx>*0K@?5mum4c`Lzm8D@!cY4vLyoT8F{#E|iqC&+zjdft z)YD4h<<0xC@&_IS9HRqUxv)2S$T{tM^b4)!q4zRhELQr>Hl3qbJsINh@o3DTXzR$_ z@OaSLRKg1noU7zZwlf*se^gEfdElrsH?t~r@eFGgXr)HhpOpLp$=b#MY z99Cc&dq)(viRWW?{aB3avMk1{@^>Tv|K0_RBnaCYgYD82w;Kd+=Z)N)4*9k%wqkJZ zeU=$|G_oxb<(8{xy>?uzoX078UU2(SxdRW^YN2fMbK;&ZSGl}v3jDDyV&rpFC&fQ5 z625qpuoy8wkDiy2njbx3`k}iMD=F|wPV6Sf(aS?0!Uyg2CAr6BGhf!bmlHJYtW(={ zWe5$gsy#Y&Uf`{{yWn%hz)k=k&|KNe`R`zymr?11E)FWKC{Yu6uV9pmibHnFb|x! zPk*fu<~`qWH&tA(v0${z(q%t&0Ss({`>T{!&i_gwO_hCFi<9u1t5jWX#%C%{y(?Rp zA8#$MDkQ4zKHe?g-9M6He^XBrUOpQqeby|e=cRRxbvwHtrSdy-E5{{;{v`L_v$RIJEG1$K15*%bo*?7vWt_;W8_qGPS(!wojFmH&tCW zd#*Vwq)==a7hY(&x$)>A3ojXZlS#g<-VDah@+NskJru_&SW%+Zd63TYw6gKvar1Ay zqX{SA%G8SM&ZsOPmV~QGQ&K6vpH-YFz8c^w{pP2--kMh87YX^xyehS*L`AB@_<-s! zZ?CvcotQEGv?j06Hr;tOKQ0paEUney`~(klzbY5FC871$o32aZwpGQwH~03mzmQe9 za`H7Ge|avfYh2Le#E10X^IiA)DVN;{|2u}P2>V|$;kn;zJCs4q!#YZR*8tD0<^|(nm0R{TL@%UwO1m-4~~_Lh9AlH??va z(&6V*m>Hm;ue{Rsb+u@xQkjH4WkXdr)2>`n;QZ_1F|#lIhC$xPlHGZFt%bLpQaubb zOl2AKr`C-0F9qiQWqJb1faO~1j@gRVDbr~c`d(ST@W|2j4f=`1$@1M)k8c~!$@`V| zg(-%u&B7(@I&2lzjsBwJm9M7++IyQ~!kOh7)kIFF9uod^?8sh_pK12_f(B3cD+|w4 z$Y!iQNq8~1ANeSllK03ByuRSKk3PO#$JM34w@<3H8%f&h^Gxt8F*J(o*#ec8F%wm0 z2)WJhM5xW{1BI)rNB-mQa=vt3`}ipTT6|3=sm zM3GciRUI?D`$au}`32>({zVa3M(K}Zdxrl=j3+e~ zy8U5WwfU&{r^btWVuo|CpN4$Y7@ATmJXl&8DI$)z#c^PJmg;@(*FjGr%IeosV$FB6SDo>o~Fg3wPP=7kW(b;4D_2RO^og> zpD;>AF8k_^@pOHAs-!Y>=$DVjX`FhL{54Nf^VoLVs9C^CFDAfT@*xSo{Xk^+1sdbF zyWanCvX#>C)ws%Q35IK+2kWWv%YPs4?AK`tf83X;(gyXM#-Wu z6y1iuJ~@>B{;9`6JDT1ygz~=ka_q$36qAa?_|UT_^N>tkz92=YK8kKJ|AIx4D?##d_2~M#C}PL{jPV3xMQ>) zc2uv86=dH;t7wfr@6lUfeJgI{2JQx{U)*HO=(m!hAvfS z0VWW6i+b~X2(HK)Yx=ssQ%J(DRToS8ZP#wD+UtX&^Wkbee^k=tP<5$q@@oOflPX_J z7q}X20Kui6vpJiU5UGBB_%ZTEZz8uHf8@C)a^c~iABeTl{7%s#G2(5ssdJ+3PB|2I z@qUwbD}L#N_~kar7X8jnp`961g%18BBY8UBvU}dc5zOQs5$ZZ(bZ9Cj$;+Lhbq16x z6K@vy9VEPOd63Z)XgOtl2fHA_UiX>Aqfd2bj7Mu`I}}g6y}FOhI>%J$g?Y?Fj>?s# z(6?*M2z>pP(O!45Y7|8qV#?2Fj<0YZ$>qGjnb-;o{_qdjNTSLf*~lc$Q2brdjU;KT zVR2c%^PmQUhASZFjy9I#-#xIVzjlnaDZBX@j3JcM`A%t0W1i_guRjx2Q99*LU7b?T z+bng{_^D{t%&KwPQ(z3-oddJenYPlKRM&>{U${i6bVvg5eJ?S|Mv12Sr)}tmGp*a= z$S?`2P%k{~N!jl%=j_;h?!Grt!7?f!9~M39pq9^qU5WZaHdfUt9GrqhF`*@1(zupG z^+7J{YuB*AlV8da)0m2RQHV7Ng*QeB!TMa@eYpLU zykTxUg3+eoRdK0j2{G;~FRH>cM3_g$Z+O5Sh%XIVw=Dn;)uGh@E8C;j>e>sWxZgv_Mvxogf!VM{m-0NP} z?&4G@Jgx(OfLtTeOa)LmiwF;?i9^Y9;)9jv9LCBql4 zj1TjF;C$a*z|pv(KzSBks?R1SOWY;*Xs;3sWK`ZRW zY%v!|CDBjn*6RPht#muQ?Z!Q4_RPz8N5{ujP~FXFca_AdJkQ|wD(C6DdQQ%3&!U4@ zV;I1y(K}_NAtEhyJwoVn7kH<-TITY~HoQ&cGX%ZUo6z;#PE)jZjtJ?wj}2x0 zs2fUem;2JriIj6t0B6<#`YKH$9rmo39|>!9K+iz5h1Zeyze5s>6lNTC%aySLw;jtZ z+Ud50%Ok3)c=N)g6eh)IwCauB$SY5X5EmR#d>;}$aa?fsAVe%>>UZ68W*%UHXQ0AH zm!|F4zQ`7@%|cKrJcZ$SprIs=*P2fG-xK&3c>*DvKRxlV&_#^+Z$PeB{bkoo#B=Yl zwvcwIMuyR&bT4A7VPl3Tsh-vf!y0U>ZPu(PPbEkjLKs(nGT}> z(<$?*lgdtupC^z!qHd;hyo@@qT;-r z+jXp@>Rq7dM8J50|Jn7Odx~OtGVgL31gB8_NetQdh`RR# z@k^v^D&-ukQNR37e;RqKn%`1?Z+#!tdY%N>uL!A!!PQDuK1@~bsv{4Rl+aqPxOCVH zyN@gzlGGUO53Nfz&(Jbdbvfj)HE_gb?{5Oat z1SUiG+LA57z$X#OAed?_T^oUejlBq+W4QSEvX+*X-COFKkG*+_Yq<_KwAkrxN1ZXH zkZ`FG-^^Jv10d2Plc-lN-}WtOv2ccn@b(CGY3MDANcz(+wgFy?q0Y5KYznbAbtV&r6Yx212zWy Or=p-PUny%D{Qm&kjp_3M literal 51442 zcmeGERZv}97d46kfdmaM!QI^n8VK$X+}(o{+$|xvyF+kyhu{R4;1DdhyPd(e_db7} zy7%GU_fxg1Qh~YV9M*ept@Sqfsvswh1dj_30Re#|De+MW0s?Xc0s^WD_BHqkYKVL* z_y^KaNn9ABd=!5d0zwEv^5ch3u8;@mZyM1>v4fG@u2(#4CqEQBeJqA2{YISQ&yLDV zOst}S;zREQg8}&g8fI7G_~oLC>r7iaqtBlmVm)eTQtyAAlef&Lr=Q8WjE#;YI~oQR z5hIcdLH+abCP7(bYGOu5BZmCvBkSv*=mY)le^1CD;dKSOO;!K9zH9gy0xs%*pPOh1 zm?@r9Q7-{(QX*MBauG8yyVYZXq;1pn_Rk_&b1i~k<^_V2aSuD3-0 zJzNkiYweQE+En!4(P6YcApi5UC|@Etg_Ao}&KlBx2MVwI^yZ(ZNpwa+|G8sf0$=kc zJGnVE(rEac{&2gK8N;aaes`g+WcBQ=lbG3PTC`4!%kEgNcx2wEBBi@nJ~s!giL1j! zs*b0-lGgj9mKb^s)8U{73CQZ#@uy*Z|}k)o0FAe*6)m_$BKqw4Ysveef;6!vKF@4(4=kIC+2 zezo(yDve^!$G{s?Lf6O3Ig{={xO(#E+a01JrTlM)Twf%J3sChVxdJuu+nOD=h6Zab zri{UZ31^ zwQCRRdSsHAuVX98^q)G*nHb$R_rZFb#XOm0R#eN`Y>_uSE(b|C-7f3hL7JVWqiOS4 z*I?0EjR#P?9#1#=ZTbVesMN}}9S#>bcwA4iT5q>gyL7dcheHguzO1-I|co zW4G6ITH(~UVxBJMOFKkt?78i+Ou>V$fH!g#aN7YM*GEWd*c{XNPQB@@CQ?c6r!4u} zS2l^1vMJ29g(X?RYdwE@!^}3O8f^8{6lHnKb+5WV+43Fi7G}P^UwwYUF4$g`l}?3= z_Qiw?^mz=Qs36W(x&8i*P3(sP*d#Li9>||M_14Ls%C&ix+z=IMAyJQ#FunCx-Y4}0 zBjM%QEY?@MotsvKxn2BK{NOJ3L$|H@v;0QGM*ZjIW>%L2^+f|jj{En)P6vsXy7TAAj|dg#D< zPY4BYzg;qe#Mvmg*BexS#A$7_BU2y<|ld zZ87nvNc`-tkKy$BZCYFoH!C`x&IDLmFz`cWNF-K$p1oRdM!(Rh6qCasU>Qfw@emSH zKxU$?Ign=lsZ;$EF)X0qPA`7SZarTudeXHpPQdE|8(a~F%~UQpZVl#AQ7F0VYBg6L zf4M(5+!u}~N+UDa=5b9f9)a(^Y~LS2Kp_&0s3NVB(#<>c; zxnLdz#*wm>CP(qJt>MDN&DN7{1i3{0>?H=R`f1!gvG>0X%UImI#LTCEL}M^&|GwFs z!0Kbc?`c@|E(xh-so1mBC7N4%@O$+JpXtlp;$nSg*qgokDS1K3+q12FaEj#4S>B3; z)YvoBH+){6>{B)!Ny^@71!F6cH=0jYI+=VfI^P-FH<6W1<&1ytya$`0?}pZD;>Xuo zmPxf`oJLHkFB;CAFwgI&=Y88-&{X{7?XJhb_U%I0JOkH0yljtO)E!B#_>FoJ>xw~( zDuYJVA>QtqI(S(3^s5OQcS{Y0Bs9Z%_83Z8+=4)@B(G-6x4flaj#uQSl!Do<=SY3T z%eE<`69W0KmmCeGDucs98pk)qbh5tg*f$6mMxgkeYrEjr`6fFZ6DNtwyfz_Lgg=QqyQ-C6)L z=`XhCG$y@v`~ql?Bvv%E;OCHNqJ?vCrtY3|0!t1X?FqF^y67fZ*YmjO56~k&KORi2 zwb#LQu0e>vqJA;pI@)~gwT|kd!;KKz~S+^+WiKnp(lSkx3SR=1biN%`^hV>6b#;JQ~4T?jy?A^sa+AcuNnGVGb zS|pRAx31W^T>XK4%V9YUYc-zbS4J4;b=_mf~Sv;((uWrhM_fCzrHXw{< zKMN-2Uw=9=OlKqv?Ca_G=5_k3khr@MsQDS=wSkp3oB4Pa>}i$hImqP=y&tssqAGOT za7h?vGjZD#>Yyn`l8L+DE076V&)B)(xO-i7rLT#4ZUhmV48<9^?uODo+-yYF^Ua)C z2mbiGwSXXC@{LUfsZKm}N8ROtlkP|MG%ORuRa&+2;Gg=8E0fcDhVlDPeZ1XqXKG2f zeRZcI$=Yzbwmcgq@<`7iMI`rjx9{+aRZ6f5N}G2Tu`hJvfBm&PJt5tucOCAQm|DDh zw`*tR^)fA6kwOkTN>`to^$=#0aweH%Y{Y#KlS!IW?+Oq%Lp*NQNP=`}5cwJrz#`Y` zlwde_PoupnOw4+tU8>HarM)@P?sX4qAKzEOFq$VBOPbaXdEtJDBt*>6Z?HZ9%>*sr zF4QRZ3)`w|jXl=~nHFiivQ(pH?`Ca_b}c)CJskNr_iOPkUdw6fernyvK9uH-P+vtt zqc8)t6MO6tON31}?*~^r?6{ufHICRC*2s5^f}2I9_48tK->VLx>co}Y)sqAQq_Pr zbr<20lOGgJ_oK@LWcV@$jgq;?5O?7b)}biuuqMiM>yMQ`C7`6ETEj2O6{lCU-$~(R zEAU$=wyE7KGhQ2*|Cu+-#H4TdaA)bNF`ZUFeA5SK{)7v;&>eBfIm+`sAApKrY7*-C zY7@4ckH3o(BVqen`Lo=2SUs++%RVgUOJno6of!v`8@-i+yZ@4g7l7Q@8uWhD!&s zo^SE?D~UD+n+6@19A@{e_}4S|K8+%4hD0O!r~`NNb3ylABAu5d`X_PG6rj+$3p2G{ zC^deXSt?}zyrf>ig!rX<5cy*JfFHZ7qJnL4JR-4MHrt0KMRO)&Oc7N!@3bZoZu~9>-ew z)dBdggymkoCr+G+=0tDqlYDBCD-nZkx$0_ro+@{t-ij<;3FsS0!mAc6CHFWv8S6ho zAgQgHKb_R~BlxsjIYvceiqLrMy6L$xIUo)V1Rq9KEOsYEFGp1HESg~E>(r=2TRrAL z2Sm$KfM}(av!Zyd)n;j-7$}U4!z@HcB+7ky40)k_O5A{aB{Fu^e|Sw0uN`MX;k96| z`_8fYssQ6~Ds)uf>=C5fUT;{uf>(N8mos+!U;1kaLd@dVkzcaOmFfv?&pY?GbLqFz zxlsf17N+9N;pVoSlXEr(alPLAR-;#Lq9&{KSY4VOKPvUL9RY($8J=<|>Jm+7-NMOU zvRSA_H8EuEw#V;$wSMp~(VuauRPO$JX;C z>8mCrW7HltkVC;u9)(JvoPT33)3Tp$=oaq992c2|bnv_7?_xuRev^VRBtO6FA3sQM zdH-q|j1S|q9%Jse;Whi~o&1t9~y;=CFG4trjCb6mlgXrrtC8ct?gpJ zx`KHRe^U~9-xxHWD?VoHezP`_&J<{?-`kSJjhHRNM+!)q$D&5IR>6CwvR?H#ZiDS* zk7fH_@q6cuV{$nZz2(4geKJ{AJozL}oDkyb3kb|a2(RA;B(s~&!Z6Fa}AfdQ!R zha#1~1w$(_uDAsZ|a^ z*MwTq#6=VaTlU~jeaK-ZvB-?Tl|xR%K7idBNoLc=PYU`u9BAZALH0YKULN`0^{YZT zq>U-7!9Dg?;QWWQ5OLD}HS;!v0=n=l_YMQgUu9FQIQD5a238Byczapevpiv@ysz2f z#XPNN#vP$s=1uEiSTo)`TQ>5=>ltdfeJ5b!DKQaRKMTlbRD;YYIdJ)a828y5Vx!yR-BSbW9cWo{I?MjMxhUfG%q)SGl(Xa)L@7>)O+_sQx4iRvwb7GB2^={6Q>m9 z{hX!192Sv1frqQuN(AEJ{(<$XiWYI(n85u=0#&)!-ELvHb+^s0Ki{JX?pbNK-X|VW z8)Qril(RV|fWlMay`FU%LIFZqvwA)a8<+86JlCH6w=JLgxX350@a}0`-9&Fq77PTR zf8s7?A;71Ld~oC0FR5m>z}A`c4-_#J|jJf=yTUN zmL{<8>b4m~&Ql}TXL_DV;(Xu1<9E1$d5v(-{X0b8KdTbEQ-88Vtpe7bprY4y`ms9* zLCm?^Ci<-|RGLnRk$K{e*SXinNt6#0#cub^7mOrJ2B`}Iaw+T|vwcTR_?J7)?Xuaz-$b4In?IPoxIbKy3z8uZvDsX-l2!bTsM* z_6fN5bW8OAKd_w^Qn6|o?)ry6H~K8z8`%f6LP_uOt~knr0-*aL63wo9GQ_lv!gwU| zAvRr;r&dpdpw}at)Hd9$N$l1!x-=?KdVXHn>m2q>AqpG6Jq))Z*zt<*rAsX&C5xyE zvg;ZSZn4`Oh=%pHWJx1TX8Jua$?mS0#3Y70^k5Wt5zcBDRs=0WKy9TPO<~20onGJB zJ7kz3ewbZ5WaIXZcVY|ZD?SZwh9AWlybyN!CNd(cY#-#mvl`&(U|m`03pQUm%7k>3 zNfTpzD}S;^BwyozycDYl*XtyH^<8$Y0_J{fJcT2n{UZgsIUSu(IaJH|IHF3MsErmk zt;G!~V}huGEYtUWqd~a6(6Wv9CTea0v1ofKDgf!&EDVcKKL@DJbJniG|LS8h zRGrSP1!N}v|7vh+DL{9#;^Y(gS0gI`C6cmK+A%<16rU5RAk zf2FuaN+7?bAdUSetL=izWyOb}CI8XxI{!bO4(jK7B}$2ZpH>bIezR#0+rNX+e-8#b z70vqL->2CD`EZT{FN5UY!A!lByoflsn1575Un{@~%I8lI{~b({9Jt&mPbTW$xoU&C zIR6SZ`%l+%0+;{4Q-4|O|998_za02yG#pmrP@tQJWlG>|j>ax_gm*7V5TilalkxN` zgJr`aiK6wgbldUar&rQ^#&VoF9^M>SczpU5*Ay*84o*IqUym}mJ|@)H7|MiVrrBC4 z`$x@Dwh(#6le>6sHb-$^kzYr!r+oh%*B4p)W0K5&()AK5IIe(0{}($S!EvSRHdgvC zQX@d<@e1?F{~J4$K;7m*rHuVg!TtgUu#Ec0?Y~HU5!AmjCu95H|7qt(upm;&exCmqsifd?bOTnJ{~}c!%!Pm? zgX-T%MSqD@*jUNvf1kz+=JNm7)a_+u!d|`|h~lSH(l9Ka8J?ipwjg<0OyT_Nvvw2X zvy2}7VxnzA?0uwkmxJhLI7vFW;^fb}hc1SD5w0vqB zDL%h7|NQ()mhlsgf)W?X;U!Jw-mRHZl-BO^YLJ9JfS91*Bd}PJS9B7m4^D)9~p7Pt|qOfkuroV;UdR zjMg;P7yD+er|t`O8wU5QD7l{yibTfyplP4nsyvxQDYHysMd5e3?#)E9M^p{v zt8w$W1tq%2a7}i91V1D*OOg_Bshzf>(W%n6Wpenq6wS6K@OfXTM;!6dgJt^Yl}rV9 z69(?xA;Ct31`7#AGgTO~E21J_zqjbF%jMCPMmR&Od^GZYsbOg1V<9daD*9`)D%3q z!tG#3%wel9><{Hs_T742dgS`D;IPeFb0wred&=n_A+zOM45ge6cf!4|_&FH+_mH*j zBP{ONhTyHL6|-RLvx4fVCMBq@wec4FG2sq7+cuAQ!;$3~E%Y|`9So;``oom)wTzFV zgkV8@B4zg(t4MUpK%xA=!ScQlvQZI|Z_o~qayab9d^dlJAQ?wr<#f~B^79*kH09+x zvS59&C69*VAuS3`$+^PC!8wB0(uv0caheqZVaoMulKz$`s9fvZ;x0 zsyZT1B2|eBkJrU|SMZvyY$A=^O-W1Dd4U~ev$l(CXvE%rPSYw zAb`pqzhd1M4HgD0K=$~z(pPXdp}r(ehp}9NrS>DI4UhI}J2`0mp~DPtf$)Bz71`NJ zY_kE-5}n7;<$BEp=dG@L{==>OUpLMSGl>0AUcv(^e1JAeh#BQKvro9^+##J#+%JT= z;mZ2Q&7s@j^>5pfX?EBrZ{PxnA>VR@=;u~Tk)yyuQ4}Y}RLp%xNOYcz8|;3(BA07K zxCww3jsndASWeoKk5GQZD1ru1T~yU;l4%3wJ3KFA#0SSdww%7lk zS|11nS`-TAqO*Sg%TJxGAbT-PeWIuSUv?9sMgd#%|I?R=m7ut6m*RRtki~IBs3be} zz>OY7Q*J>HfDY{35RcrttJ_z~lR(`CTy&|;gNt}ezkpmH+@C|jP8y0FB?j8C|0_ds zwQ`MpE{o*ePko4oLO@^gFmnL5^ zSIl8^0F_B;E1w*NA8gaaMqde-F}(z_bOJrA^WGgZ{Y3L@qprS+^K%10lIJ$XaEg@F5e}}g}mS+IhlAw zoLQz9r#9acF{Byn4#BVYI(Ee$iyHkbvuBU%kT~GAgHp01bbQVDOK;l;l z#}**1Fx^2a)8INaun2_rqLBM9vBtLFFkMLD_etOJz+!;mVMpAet^434aL#46(0iq*!XO7@ND_Zs$2Y9#y)MUyNlh+%8I$K$#D?d z?j%kfH-8Wwb68DBaNPEa3T33y2ctUC!45o7y?|HWSANSaUxrg{+iCV^%VI&xZ1=JQ zXk;#-y_x@yof1xinAQ`EBxE4+x`-yE5n}29Ye%AMr8-6g&F#02JT&qWP!7Z- zJ+2O-K@-(tt29y1TVphx*Q}|zva?In*Pp9#At0XwvI3UR^`x_8-_8Cb)H^oQI6a?7 z75ziY%e@(;9||PzxISzD4CNDsB7Es&qhRDl3ss^wI_^Z&4+L%rBW|Mu8QV*7+$j@XrK@t$X92@+Nj<|=8>%S9ZJnk1i zfFj-hoGi{JPS5Y(kee+MBDFOXe|h3~wA7TFjLQ@z3H^H`T(ay}b1=K#-b~5;$G=Cb z9eQI{cMj^9P^GG6nuhrqM*h%n#%rB^)h>scO-{Qpy4MILYO2K|600a5w0YoHh(F_F zg+u%Mbpr8y6Esa@IIL7NG2irT{ylwZ6`Q%;(5_GBOTV=4UK(Z#7sjAdT>I*^-d4X; znLIdocQymKQNHqUfU@Z=nns{gMU0(wY41jnh>(LV5g6`CgzJwa(p%dyf4sYN*yuxA zz7Kl!I;qc?7*ZT6ZNr(od-VnAtieFi@A^OPLcV$}k<4lWoe^2e{^d|~#;qs>%<`WN zVMgKhys7jA5^k3QMnV^iA2{h2W70U@?auoN^_PgLV~GDJeSDCEIC@_DX8Uk+>c6fR zyhaHz7lz{=+@A}YgIc&8mLE&hx|WT-=?GvdRu$OD&`u&CWUvjJhQJ!YIDSLyl7~2i zR{O4iyV&GdIQu@6-^1kuJB{Lk26FWD4uc7u3epiyzX;wMmQ6hpBCfpI<&YMgQYLCn z&qqh>I}XWU3`LEusJdYV0B7#uQU6PwzB|y~&yQaY9hfa9^E6N4`?zonu|O{GFTFCg zk?ceRg`tDdY%HVhqeiZ#MAF4ll~D*#^*>f_V}E|bV@9O%Z8k!_%j)pTXjih63R<;P zJsZ=XP{8wV?8Pz#{2S;@udC!1^924(5|P)SBNpNL{CLd@`X@!|RXOx7Hwmg^aM1T98BUWIL`gcv>K!~`oe$7uu569 zoej~Qfu1{j8daeC0O$o@xmnsz|vI{y>N=5J=%G<5tSE1KrTGr5d?A z_w?E@Sj*B-z%4S-raMw&gk%TWtWZyA+a~d@O|Jlkenb2q`2y(uC?R&>?QkbrzSx4B zMaZOaD}ob_{nwDiCl*wd6zG`2|D~yUXlAI%Y7^bWt;$3I99lx=s{ z%YGuC3v%~?{w|brkR~aBp!L=1Xi4LxIS&>U{?KS{%`d4 z!TeO%Cr?LUeUaV*@)neG4B|7YdZoS}R%_)WVu=|HJjTs2`L%xnz$W9INC;w3PZV|! zH};ZA%-MPJnJXRd*tjZSJL|8*nA(5rcms35a3Xqz`Q47;TaNn>Hb)Y_EUG9Fi15o< zw)vqjBMA(t59ecolg-Xn{6VKH9>=|~cyp+d>N~I=t+N(%32`p~hLi$gi`$j2Si7nE z>L`v@rC4I)+*lvBvL6rUXoMpWZmH2;q-7dmownNYgA~L!Rdt72;JopB&7MHW0!;)C z*_su40yt#lx^1)|^`SM^A;h7(A#4~;$PztcykUW)gIj(9 z2cT0QkYxHZ=kaWqNh#+eT$AUm!_QbgG`~*X<5n_YUWx%#W)L*U8JIf;QPe@r^4l6) zk|fT87K^~A0UD~c&@jr(2p*?3_35A9SP&UvmyP{pMP4sN9?JOC?kze z$ckm8?rW9+TafMV5}1@pPh$R&K~RpMZh^kjz%7sw*8&8xNi;s_zs)atEqg?pTD{3mFpnP7(#2h#J+#UjY^q zN8}1%DLA84i}Rl|&XS3E$u7e2LZCLl47mYhHpMxd$fQSDta5451}fyY#IJAYAV)TI z8|_Hc!}=Yfg~rh&;;5rXGWfLsS}PQ^tL{YM0CA|e(fgZ32;w!luE!yVACb}lTObzX zZ-RCZz~(Bn1D3Bk+JSd(2CF=U(}s4cP#(D&?e!OXYS(}kFZK6CC^&qsl_P09%E8D) zgi2Uxp1wm4lHDE{3I5@@977H!4Vr%0f!qL!@BXgJ<*bIxWkbvW?E3OMx=|2{9{}GL z0Vm9@D9YV~u~e5mr@(ngqO+x5WoXfVBnTE?VvbFn_RR`2%`GTlLoYmK^GIWOT(!n5 z4TmlemRvIC<4^(vo-l`l9%gATIp+mESr1>m9H^>5l7)Xs|R;C)|#JwM7-9)1iAp0}hhw(Vlc2^oKN!f%zj4 z)$pR>BBO*P&_dzbmZPk>!&LS_`oly#{Q^pvEz89iN(MxHN%#3$1qzAX(}P=?j1wqb zC`G6xAE0JQPS!(+q&mDG^T5Dafz@OmV@aOEByvtZD>?ib7Bs`fv}XK1Tmyt84U+jo zB99|b2gjk;mJuA=lFozvVAf{5`-eS|Y{KeGT!Lm<%!>zu)no|6;nFBbqLUUH00z42 zOIEK(!3@55h!)WruW~1fWMED-1RF{CFZaMECfAAqc7-QLaYT zS=vVUsShL+vEMia1l18J=r1-T2UByRzHSGL0Z2e+Xv}pNSdjQ95BXMKuF((7;Pqq{9>t|*Vs)G zT3c!Jhyi}fm!pse9C0k!Lvs7vW~2Cib=3e-bMub{Jdyd7&6o4QUz;(xxJ6UQnj^2r25bzqw z7}S5@uZ17X6!aNSoIika_%UytBQwSXP5cQkP|74;LpwQ$lw};tf^p~35b;+c#K0$c z=aWpQ#^{$Dlf;eLO=zqCE(;mOq>MQ**FM7 zYzw$7fycQS+EG!K4iBcnf;t%mq?=0gPKN~Gj(qLGVx)aLzLC!7R;_!-38-gOg&>UW z09REV9c(YcYc%FChzB&0>^FV)@BG6cWF)mQTxfIzn284u2wk+IJux}>Dx!r z{O1y#l3i-y)_!3N@K=p8f$~2gM@6X{n%xYXnEgMCTgupG*?rD`aVdcyZ?Jzy!VF!> zNp#Jx+Z>6{gO((G!>wk?agW0s8%gLF@CJDZB)oj*{n-g=;@5gGQ=wqjbG`oHJP=j7 z&)6h~FP`yW|8_Z}`lj>Y|_&`pqFA?m;{+v8;o8>xV%Vi22hTwap!g#c{Zw*iD zpuA8icAJGyR*V&T9lG!s)N7=a$g#f1u-YvyvVicF*L8X&A}DhPKrFxwZ^lr`Gd@3@ zM|QSlsl4)NRkh&>OT^nhe~Zse3@o(;<8`iDnoTz^!3o@zt3xWGEG8kHL9#m_KS}~v zL$70d<;t+^Im^hG0Q)l>Z*C!8f0scP0-iUW_e%nx84>_$G3X>FDY??KSpw7Si{%+W zAG1Nr{@q-E3h7Z{tqdU~JLE=v-xXy@PA29zkUa4!p0IZMzB3PYfLN|K)`n)5|gbWfH&U(371$l8wtR|!r=hq}O*qmH`oaJ^e zE#6O+nkLk)WwVA@bjSJ9iS(+5R#*)m0$wld%T4AXHs)ZfjIhv`q(9j3Zt1c<9k=vHwe39scflP`$SncTWd^qi|Ht2bcUFCnw zyyF^BAuF8oF)0A#-O$e8xBf5)FUEcnMJ82$5W$VGoqAuEo1M3S{0EdqEXf3V4Li(n zNefVwgw{zMVM}#dQ~_`wFu~nkGo}RukP$YG-$V5qHWMSj!?Bs(cY~s(YdkVtcziZp zF)vzZ5`?>K67Nq5q<}iuhs!xVD4h^ci=_c7^lv@^%wTWZU>#(a2hlYk!nnH|FVtE* z4aM9hNvr__UwpYvi)D$bs~`M!C!sBK!`opoKugu2?3%sTyI~Gt`YYbCu_?oLR=!S{ zrjSch2a!yo?t<~%kAFqq8Z+T5G#@!c&nZB{7}QD3uJTy`YeOOEBhHdT^uqyu84XOk z!{gZ^0rhBr(%MX^=jtb^z37>q7Zc*M)OMlq4t?L>olQzJ!L=UP>9YRBO`Q4433+oA zguEJ#$0Y@X&Ybj!3P#`@S2H#eY6kdjvixz)Bori!*)lCNt$NHPG-fq4SOF-UCTgwv z(w7$m2>D!5oM7_SkeVs0?ydGjut;>=yx~`2m}Hs?bWS z+g8n(Kgpl*I_$b6kg;xO>gk#MNj$$GJrnN1#;=M zFC`bS^8UcRU{3;Ib>dG3b~AAH^(G5s@ha~?#Y1f4VC`ep0<^7E@i)$=A>d~h-5yCM z1I)k?v6!Po3Tg$l(e1ndgIYnT^2uu?iDiXiyLSfP@(M~YhmS+I|3fIe+(0_?0!efEyFJY{2OG# zxHuZ+yE<+#9=fBo;R6sU3wgMFNGxE(KBjkqea!Jjt$^i*#F^=IUjZWHO&dK%tG75I z-@9%dtXbM4pthXtj1lp{7)eSH!@_}fgc_5`hlv-dm1D?irZ}I06vn__gPaRmU*=W! zH4v`>RElW)YI9@(uj}ZhNm~cR$p7j1fCS75=rE|bVkJO7$gF{vI}%NQrAPwIdx1Ot z@?HkW7&$?enPJ@rGNcd<1?o+wgH)MGaXDs#Kr8GUbf^NLoPtiZ)K2Rtf`GTWVLt(l z?58mlh)m2Ir(UNK5b?DXB>;FL@U51E%lUg>IupEdYIRg~!n_wcg;)z&( zJUs?>4;84p@iQfAT0f@gY2l!eA(Ot90x@KBC|(+wD)YUw6!xg@xbV%w^V^Kc8uAR# z6wCYi*-X9eObxIEYzx8dSNrq;j!lZ_C-p2h+UF91RN77sA#M2N$#!j`(qro2Kr|VQ$S+t0*doQ;;PJL(pT$nK zCfN)=4b58f^^(oxasodvwY}RVZu>vK36MIZyYMnXRD8PbarqQM>XeozxoIZEXl;#VYi{u zVc>PmK9#y5p5JISe4cTK^Z^_vRy6VN&o0k6WX(JVrP;$_z<%NCa zQDZzvnzZ4JbuydrLLh5Qk@(;8y!vTGzUB`x+nmUtrMfShm1sp&`SULjD{2IPdWE5N z{?MPu5e2?~nwNZfTZ(mXdWTr{j84d67??5T9M6XFkx{#mSf%(As))#xiZ&t85N;xc zz}s0SfcrcXR4%soJhv}19cd;DWeW#wZYAm^05j#yG%$%*#6MHGm~^-tro2ExZ412x z(RhFd%<=NC`WpcrLg9hDvFqjE#sML<;~Lt*Ad>}@Xos~<$YdVJf-%bvUHfdcPqH{| zI!RTIm|4lmK#t%Sgbjco@_DE#`N1a#iVm2Wd;8Ubm0`v12w??+Iu^fnmJp=HLT!<% z(xnO}cmFBjJYugzBg%Jm3+O8$5qak zT{Xnu%|Lg}0q-!6)UFO_EGFI3pyOueedyPmBZw?YC&Jw605w=$2@hX`#C`V)-M;TB zNE&y#ho<-*PFsG*#?!d?6B93!mHZrj5nKHB2D4XPZitB zJ9IT-=J3_3Q`3@&T$xBhftY?*Jk>?2`^e{IwYJSS$XiEcEqjhTtC0&GZi_AlqrZ3^ zHin#=>~~YNI|2`5A4{-jUI`P_AOpietV}Xn(PgU!Me3ct4wC?n%!`BQV!eCl@^*NHTgh9ZSkk9S)@2gY1G5^IcTTkg84)Qj~O}|&#v&B}*SE5q) z9QXHRFBNm`o}c!+T}9^(v63bBw5Xd=s|~bn#tZ(z<4-8HZsjX@Z}Xk z#_&PGlbpqR8!bO!+TTf-^mMKUU*W>i*9#EZ{JidJ)ZP;C8GG>d{)!i{dMz1Ck$xD9 zb)P&{yt)f>U&LAv{ZZ0r*Wrx_u$d(}Xrziq6+5U(I>;T^9_Y3J)NGBr)%MR9;0?rjmc zCilHM8Bn}b^4hFSy-?Xis;t3+H9sC>SE)F153?Bg>Qp$L z+QM+rIlD0+@fI(!VsHjQZEGQjZZFsU#?s?*$)w5iDn^?pSHNrrmM8<=jOl3(RT1ia zI@+kK;#fLc!%8lVQ+*M1 zM_R4=&(Vjx^}~Z3R2DIT^(gmIx6jo0MF%){7FBT!=k$8kw9QLyIZi%=_3a>UCwMPkg8FF4joaDr)!EMG+NJ)J~qu6}Wh5*5p3PyY< zbKk>4f@VkVdn>=R;TpZt@}*+sXr)C9ZZW{&@(h!JU-dx!jzV*Fj)zq$I4zkx_bzWZ z;lpI{J^r=(4+LfL&!&&wc?BzsG#@-qH{Bk2z8rk_R$X(mfBLJztNlmr+C0eE?!R% z*R%NcN9t*g9Jh2my;8X~4~<@VtL}S0H)LvW<|v_0aGm1_x;em@Q0j5K=wcnq-*P>m zb7>grOK4f|&qE>YXC0$s!?yFQSU0a`4}13p?vFSJZy(oJhzyuQFn4ul-#$)g=u7FZ)tQ(8=uyK2#92CH2C%R(Arjj$RrIIr%aJtCk)fJF_7B| z1f>(1;sfrH_r95^wEc$KLXdu94Iom25d?ULkIWl|ipj4G|Zm@tO_N{7Ja8+lQH5MKyl!zrO__GH0Vs zB_4@ClS>D_HFxbaYG}z_V@~)us7RhjE=icuR-pfx%62Gtg;i|)q}$tR$AG7xK4d2B zzDR`1iI++~Sy(EbmI8BH+5;f@Iu|>D%-aT8iRbDqXQU$gtQTqr;~pLvUDt`Q{I8BX zo)c~nwue`iJuUhwQfHG3{+@LOtk)^fmBcKlj5ELF6ym2Vzj_&fEo zp5m3$BW8YWoyn=#e5+lXwPGeh;iz=5%)31Mo<2~J(A(Zdz-vKT;uD#LCH~zSP8`k= z4KtpI5v{h_=S(*$Cw0qH$Tw8VQ-Y&8bFv?dTjaA*AX57}cpueSw$Ej9+^kb=HRCd0 z;~sLLNM?>QzROmSn8INxS(d_)53qQFC330;0k?wdcCy)pXGO#Aoy>{hA>DVGXVyz> zjYhldl-zKJe5=G*hcT6yGbCCX6Ij{<*G1PZ*T-y|dz0lQmFlZV%<3;ajBSD08nFfb z*|-lDMloW>%=M8CCfyEvH-(Sq6@o8+GQcu?pI}ghy&~?1hNjdfCW_vYcr-*ZwkTg- zC}wv5qEee-&cUnv)JtmI3)ycNbfjba_i5~w(-Qk}I0d%L`8)gP!1)vl=*?dWtChXR zBu3Y4jLU$Ozzi0n<|XCg-&>hpn#G(IG+UV^^j~8sPC4)jh(1D(mYB$tm@>w1DPs>F z)$u}0Y1(KuM1xoTr-S9}R^cfzei5f&J@bI)$uW< zw~zxf>k?X{&vE;Kc9ja}Q=95+N#gZ})H-_GdO>)qqlp%f^M>oQE{ z>cZ$k;O(-*=QG@dov*S9!JyarxL4~TSIkk-+n{HwNflVmpU+N;EuX=J&^M;iGqD5B zIJ42lY4vfCreG8Ys+l? zej%uKAl!D^Yw+I_Y48{o9cVNtvzm@lmuc3=0J{di{&_bL)X-(p4Qu;T*W%ZD?aqEE z=57wJ>%k|$O?&3IvwQBi4Jwm%aGCt6Bawly;^Z39VyNj*Y%7GZ%KxcMk+(y|zXdvn zg~AD8E+N&Lpzo4)=kIJUS&J-!;I_#{W&#wOp*tn(qBBF;qKV>itok&{P)=3dzZ8(A z47ykQ!Ws`7d_2wfm2o8sax1$h5l3)67C{}|KsUHVxdFbPT8hrU+jtCJ2)6j@3ZO9h zG+nQ?v+piw<9@L$XXyUeK-|i>jmq7=6DotpDv2eZ%oG%ckw6jMuf0j9Km15 zNJN4Mv~1h9C`~WqdL({<4u|^;`|og|92g(lXXOCq=ZHj`5kTR?T>0R{#m>Dx~P zxKik3f;IizFPSR~p}lMj`w}%r3_OM=oW=z^=ui#i%W-ZS)0WBW%3HH_T9b8-*oRN- zqTetC2FE07LG5-QOykT(|nVibE!oEH2C(0kS8k1}xIhH8CChSz=CWy{#NugS|u*z?|j{x zsmwFSj?=TXE3e&q4Lv`*i<#*GAd8Rz6*Cs-s+M@y6YE>iYs=LpVSLYj&K-8vb#_mU zZ24Ew(??v9;2Oz%4aNk;WD~219^^H8-Ng!IQc87*n_D7w_{kuwlmK&$L)D9nD#TNX z=L&XFHC3x|cVRJ#7D7SyRlOj}H67R#Fv^d@B}ifZ?2kdGI&y)w@eT$(hMDN)&x455 zc{TWB2Q%qf-p0h0t9@9(TUB=J$(C#|kKPtlB=x;rZI=~{yNTQwdg$1;8lzT}@{I;g z1ayDwln!F-a_zWsBY^~mp?HE56v&P?SzA(DM!RZzkV=QQlRmO1IW*WPI;j_rL+X(y>jeV{sW{ z&WU=jWiKqePE@bG!z0ct0>0SX;xARpQ(oQazK`=1rM$+o;ynsWKL~Od9B|&C(3t(C zl&^{DheaZC0Y|Ufs&3(*kQnDSIe5)m039^`STu&StGbJ15#N@L=<(>-<>~U{jn9h6 zgaO=~pQDfK+4p1tYX_$o*c%SJ+SB<`lC#lgcZr6h|KRep^SPR8^!Gf~8(H`Z&3cuQcEHcf)4ucLy^ByziteW&%b&e}hjx6t zY2G0i$1gZr4FjDbTzN^%4+4X9Ur8ew5Q!4sXd&m+7RrfA{xx@AOsTXY9M`gUIqguN{k6gIJZSszM`)Gg)a2J-u{*9E&(65wdi@2N`aVx<#L#Y>G1ShgGQsK)R-u@haCeJw<3p0C!a2dzYM{2MdG}Y(e0Zs{*|EJi!+!e(CppKjaylEp(T|S& zK)G@CV5zJ`t(u=V?a`J!#pmgaGOXeL$SH9&{L13l)I-k%y?WQswaKEUz;aLz!9uWB zx>Glc0CMbU)rm&EJX9BTVygw&#6-@6$M7Gd|BI%xjH;^by0G9;N|csT zTDn2HySrOJy1N_c4yi+TcY{cmlz?=%bV&2vJn#6%;3swVzGKCl*YbxtRt57f3Mm-) znOJs5(FPq+k>R_gDUVC=r{fn9sd8Z1Z4= z>7rnG=M{i+q0Q~MQ`OzSN!1(WInr#`@xHaJtJ|NJ4O63~G>L|)o`-DItywCYD<{*F zdH=ljUwi4w-KE`AR#~`jFxE4OCUrQAE3RS8Hv`XBp`GkgK%aCt%x2L)>e}jUC{9jW zM--yp1hY2m{F2ma=3?INWt|xTpF538Vm*|~+|*-#mEcmIdYyG|QgA0c&0|f@XRl_H z4l2iB^qMTOKu1rIMdR|0N#o(pzS;h$W>W=uc|AbQ%3-5Qhuh|_IdlX)$6FK(~oUgZ*N52y`%NZ&e5{Dp<4f?0>q5pf5CSj5CPtPEadFcDH^{$G_k+ zp<%MM?_-(JJ?(Q&ky>Ly*x%O-$n<$v01|&0ur>JfcT3 zTtF8yY(hsl9C(>GQz|FA7;zq3pWM2W5gW|c;9Bs+;~nKWd9I{UV_HqA@pBL9KE|-A z8FY0OB$)-H-&-V9Se!>+<|M>4#xkzd8c)3L%EX3YGbCZ%j3wGZ@61qNT&e0lcW=UZ z{l0~Ytkltb$v~?9@#Jhy>dfrHn#t$hXXEgt)>Xn@cR`WC-q>aU0pFCfznJsAV4+U6 zoywplVe4|`!WD;8uJVK+t51qx&Hb#%Z9(@S7D2;|AbP*-Umhy0B+Hm1m1X6=kD>>n zJwmV8u2#&w5n;lBOj$bd_At$~i^pr1bWgjZoIixTr(d!4i|Dee7H17}XEYNQ=XqnNpLwt~E_ zYfq#~6(f@2*DoL9{NYAtZQjND(R%Eqv8ju`GVjpTYt8B&HjswDyE>eDJ(tVWVlZYr zxhZiQG#Rm+=hXr|;<}Sllc#sCT8oO2pf&u-6A8yDX!K;59{raujlzuBO*TJBKFiXe zxV*rKilU$9=*X<+X}!Kju{@RI6DN5e9Xc|INVwi8&`%9-ZE$8d6}~*L`_4zO?CncC z{EF|9@&WoJ&Wzipp9j@?Dsna%oyC$kTou{8J0ejgC-LK#4?u4awVl1$9^0Tu-D<|bQ zNj8?2$Q>05W*<}OiseOlH?FPLaJ8~SvHQoxyyxpY$KBu_q z)`B6qZzh8~4xI@Bk_cw|Sp-|%5?=X5XF_4n8zt7EfkCf+se<%~TO;6~ZQ4EYt zPtY{7G32*Pmg2{QryX0Zj6yF)iPll~gJElbgO$Ip93;Jr|Nh`@8lF|rVWWIUxa+h@ zl_a*_8%=JUScr3RWe`Pc+@gGIOr}>1d!_Npvs1tM=h^DxOF#ZF1Q;Q8AVW%cE@+gt z9L!Y4uMer{W%<`WInLbAIlV(NmP{kd=qO~g-#qe(xQ=}=Ic*!#;ik`#5WPeTy~FKa zT$&1bI!zeMSFexzGfiUBRQS|rki%WBJHEtYq2EN5f9I1CEUA-UssK{* zmJ#|Z)BFQl*9Djl{ntYZq>T}CH{p&Ck0fSn;xZ~l8XOcZ#7!3TE@NCI?|iJ+YS5hi zfEJ9|953KOJ&R*P%}oe@$}?k>qP_azMF61*OlNj2 zAV6diffj}kPm0D4#PD2BtAX529(>woSM7ba_p2dn#*u83%7=3zNO&iiIDqy&I2cpN(JWG0Kgp?_2rrN{~%L5--PhjPtT3;86!|H2=uMNA<~6?!XaQgi?Fn~r#~f#M92nxDStQ8{)Y3q zBesea`F|2ja=CY;&<&q?l~2(7Xr7y~{flWN2ju`eJt13zKp6U4D7OWo3dJWMZ$2s7 zY7Z4o?-y*k@;D?~rrQ2W_~KvuDD^dmc3Bis3K9`@K1 zn^N?6b_zw{{Nv5%0kki@#L`zcs_Mw#EZOdV)VrkhO?6%b1ZeE-Sx~Vqh){nOgvBC z>dOaSDEm;`!bm1OEx9uhSW!JR zF0owxlQEi3R;&1h^N?yUi}Psk>Ce!L9hUFpDtEh#FUb_$0V3YcMSz$n2=VV^&$jeXO0h zG_t`^b}AqF-FPsrx6WvdAsfhaYHZhjQXnkD3yp#XO@c*o#eOzqO48(upsR(zpUPn2 z58X=(E{Td$WbML5KId|EKYQLwiBc|1B44CM8G4dM661#*`9Vsw8>dg0w_WjHZinjQ zXUPZUoINy9o{bDUIWA_K|AL(N{epcYgopclh}l-$f_QXRud^dR>+_W+Np9b1I`_>| zzbDm|%|L^Nza`udd+}sm(*4a7-s8I(;@2v6=CftA2aoDF%EtC8*m95k&_|P864#8x z;aro^Zm~&2L7N*Ra~~egdb2LhoBQ7A46>awY$)XZP zQ;)BI+~*S+F$QMPIN3M*>_Go&-Wch=y20BL@IqbO3TOYde|r#>Am>@ySk!-?KyAxMV0*wlU1z5*dB!j zVx!`CaPX%uMD>4gCDo)7eAMx~@edNl`uFOx3f=|zS-gj;%W-H{LU2dRqSMXF9Vx+6FIf{?t;VcVij!Vbcnm#}(2x zf(LGKooTRrqIVJZ`j5ErNd!NTqL@`o|LWzn3FZ zK66b{_GD52T&O z9}~$z)u1yQL!D#q@0O|Gpb#xKIm%G0>k!wW4O;gef40?qc`xGrnpM-xgije74w{X|?iDA>5ab=8Np(wiPUod! z@1xVKD=GsbxUcsND&}p9v-H2JvXeRumT@UnFDc7oT_3MCaAq1Z|H`NS7#9{g#$vPY zIulV&k8{uXIMXfYfzy!2C>H9}GXCY)cdpvMYC?76lM9bySo!U0g4AeMnpLV%$GpT& z^W23&=R+D&mzpXxjYShB(2j{8U&7H$OHH0~J@O*C+KcM5ktiZH;qhq5TD(tF(L_xW z3Asthpp@l_O$-;XGDDZ=s>88Xa851iNmGV`^!ZjPa{6I1V~12*!Afv)`&;7jJ+8W= zN*gjCcv^6VK2y2FtE0c^Ef>TL-~UKI6p$~5vOmtk>v0kB^~Ecc5goB4`uzZCy`fHZ zflat3V!gwbDo^s_&B8lKw~>AlrxLD?D!d1ThG;;EsZ%qzO}n6|ei~&!UbWS0qoIMK zfaK-`=_xY1@-m(%;G1{`r<3P*rCll^ywf^Cv&tYy8fIUig>bNiKQ z&CVCN4Lb?)0|k%W>bSc{KIv3(PnK7L9En)=tPeTJI#I|zm9c8>rDWeUpr(PXErqod zy}c7WqErU$k}Kp~)4PiPJqW+OROJorK{9l@fYz>Tw-T&#`zVm1Pec=-SRf2y-G}U? zd-i1^c`B!WZ9gSnLewr_bc;w%oE@J!Toz$NW!$D~VH;3B(`#2&294fPnYUXfzBk{k zzx$TBr)J7!=-s$N&|RlB@PWEvz-1};U?*|ro%~n<18d8b)odjcUn%1q1xHRf@qfXO z4k4e{!)>qJ{v4+WAg&CPFNYC2?u=#915{=rFb2CTQztKn0(@#tL`ib|bus#&Z>N}W zmHwu}zCwiK;krt@_w3X#gEA~pl7`t^a_5}TNFpfdVElZX!+qp^7L76B2NMgXp$fHB zeGkkweG@5=<$=cF8Qgo1QlKtv^Tx-(6x(ba`DVQs={7x^(KIid!yIC0J;?snAc9Ti zViy(8jI+-!u`)Kewq;%9H313jZ_LhYV;q{b@rH*pyYgR(ow8mxZbQI8M_jW^D*1&6&y>6om$=5+16aBpsF^8TbA=fiV5S1 z!3Xi~WmMPHPU^<_;3?bWWy8lSW1;>Rg41dFxfS!n@jDq_8lKhBH8R(mTz(TtgG22U z_;}Y;o&e&=$yCLhDSdW1y{Fer0L_Ee(cwM(N;?(c)-gWZuaD!lR@~DrD7RD= zst5cY3s_ezXhE5`mv&)z+r_#R(|KbGT6`egb<{^5P1~mwOvQXjGO73Uv5mSuN+9ve zCXn1c1?@u$RDSw@$Ds~ed=9@yj#$SL~)4vrU`~G~mw;ixkdw7Ws^)2rM=u{b?aryc5g@gfPtTtHy7o zs+XyjCF8gyNwpd+6G3BQDI$+HMHHxewt&9qLSn93)n_5eiYd#+Cxy|qrf&4k-}#OH zNdGOV(kDq+Ug8(8=H(8Yrb@NLwrFTPoR4Fpd2hu}a`wjLfi*Mwl}(;Q+b={f;J?LT z(D!{qz^KhdV>-QqY(?-}p4i?QIXc#t8rJoX;odaMmLH{ex+|0Gs=nD&L8J`j=(H2J z^;65((QevI;HPP$J7*Z_eJHe&thJ8`7Kt&=xi6N06c4n+u?O2k$&J{**V$x{K&o^3BiuG|%5IT$hGT~dJfq8TzmrbXj z@BBL-{Q_~dewEeSuihUtITDY(uVrPS?3DQlNbM#GnWI!=d>f9D<#rz$v*lj&iLPQA z59Zz2D{H`a-=4T|w&l)F3;6gFu_LTP0s5^(`q8b%QoT`N(ny+m3r}U{u~ce?rgXOQ zwaK6cRVg}q&3R0jN<-r8FWv0mI2VbQH+!4;+w>AZ4wvV7b0(n0^TuhmOhd^?pw)~3pW@Xi1>Qt>W zmdSN{lS9<>ojEItQVHJxV#@oXRT$iT2xMDwfMv^Y&w&AwVN~%e*lE+)r$^^b)o&1uk8Pb?Q{eo{NP?)c51CF z9zh;zU(G&*BXt|XIP;wPs>$^}xaXnV*wi3hK<5JJd-m3LJGURmeCX8)XDhT5>xN|Q zPxtdL;J#wH*}yrWzizXAuE#E!&zq$&-L!h`edWkXcjFgmOy3{47snRx+iTn=bN|F7LpE3xd~-Ds?ZVz(J(b? zGY2XgpAY{60DcfQx@3*emDO)-!)o8^GO1=QoyWx4Y_Y2Pxe45v-^bGZzH3gigBnw} z>9f^5sZASZ_@-_aL~@o0|A}pG0C4Hg2Xc#tp;hR+cpk|(|&>xV8U_wnY+$H*ataNUJ`0gWj z3}Ai_0P&r1?+|8<^O7cq$88zZen^+FaFbR?zV%mtR&yH}lK2@5<-HdF_O=qN`nH2K z!ko}hF><;J{=7Z^YC4Ry209hq37HCggJN$X=wW{SF^~R6E>Q7@DaxCbA4;g(?m9uf z@Vfm!7lGrTlky4JtZKeYOVWnethAIz{@d6d(JIKg8VX2bZPjeqEbfNw_q<+UhGppS zcD)nu+Q66Bg}$|3EKST=4P(>^zEIrTE6lggA8HXpGKQrdX?ImUUkw4miuZGEZnapt zT?)j4@$Vn8aoN3^+shvkkz*G|u);XKZpq(m%AP2r&gDiW~zlYsafVNtm+aPL!_^u^(V z8W31-*5LRHo^U1|WuL{p?EeXDzT=sfc(cPtI;~EX*n;m5g6(p@(x*$=7roQ=H&%xF zu9Jrxf5-3vQ!Qq4WDog>`v2iiu#KHAiPdNRViuD#Q;Pr!v1z2q;Uvn^*}}E$LC^{% zr94Z(fuyq8RB(R@diI!)iqla{`7$g6!$8NFU8T)Rv(DA=0{y>7R=pI#%fMQy4>2P* zHHi3=CWCiIX+$QUBodd~QE#)#>Ut4_6e`Xo2N8I~h>X^p4oA9__EYS%f8q0#d*ukZ zeYR*QQ>+tLcvks1x2gf+wBJ_!cg|4=lBzsmuV-Z`3NUGE7_61QLW&38rV^W(j8^BN zilx@g&C_^J&c!?90jR{P9<$+*^nB3A8MN+1-TAUTGUHq2wZ%Y=VUpib27ll{d1U++ z2)LfS-e%1F8bIFJwOj9tZM!*>tS0Wg5Oi!yS&x26WWl->EN?1wKgRgsW#=D8N_~+Y zpfoZ*UT(kD<+J5SR{H39)`cc=oha&yC)M;Il}VDDHw+b6S=sx|6LrUJmE;K9*&bEF zYltQ$w~u(Yc(UrScOi0Uj}djW=8lnhE;eR98`gIYvzPy@sM{VTxj8SP^lx*k_k+g{ z_4Qf@;#Ai-_o^-KBQJfX0$a@4xrc-o zU8drlMD>%87%ujei(o5@M*I>7?#xly11%U%zpp7cwDQ~4O|@p9P>O|$WN(w`)h zKB;6HnLn+RGhuxZc}cXJvaC34S4FzBJ)QP+w4xk!@IRqnY7Lr;j8$dn*tA3PR-C)) zV%yi9UC~R;>cw}ug{;ql>q|rt@~6x;^S2SeQ>H$2323 zIa?U+4G<8}7W120TeYeuVT0B+vuJ7O74Z@86Otxjdozed{99XFFWE}d@B(Iedyj-8C9^o$hd9d%&?*KP|1=EELfT(;etB-=9hr2AEXAi2U+PZfEj zJgsqA#-_bg>W;AQBK%e7wcYRJDX!pmIsGATZ4TGam2}~F>kQ{BZ5a2@Lh!c^&lhH6 z>kSpA?NUB_KJ3?jW>7%YtY+68=-{~>%UNUO$vR_!->lzBNBqLm@1_Jov;|t&wO>{_Q zFAA*<$M1BL!W%Su=jNri@OVM9+I<`=oz@qEDRlR%UsVBQ6USQ{k){*5#m8-)v+ec& z&S?6LHxDtFr0$l*R97Av$IjZHj(r4THkcgpI-XK91Ft0qEZpAScV_7hdyEb$zu61- z-Pvz_yzks;JkIRodQ?VAzMdK9Rg|(Nato#L)--AYqgaRxehQ1nQB+^0qlBWFRkH46 z0&;xGx3;7waaj+??`hPt@JV`IC;{y$Th++C8?U4<8y|z;<;Zl3ZhqrP=kWcS zPvw?|{{WxcnJ|x$pGV zF}*wxBM#+s+3v78>Fxt)huf?iX#v<@cLRd)PJDyN4wMt_#6q93~Np~ z%M@#jB=EVMBShI~#JR;_-+qdr0v7aWLyO1?7J69&#hz&Y-g9{IpYIvl(7sM9+F2Fo zODVdZ*x6a9@_00;xRKt7T&=!CRB=IqnG2wLrxk{M0HU1|Y1W3tev46|RgzH-8T=Qt zn6j)|#N8XucoguZ59bFc6O=3XgGZQ}YswC$%B~TRMPdG#J0REGFPNF50e&tL_>jUT z*J&*(mbRpU6Ly3qzaPMX^HUqoK0w?u4t!=&Siq1{)taMO679AKeG?t;iw8e2!%(r+ z3d5a73Im50^Dwc_Vo^NaJcNc5fS<`?h6dco5#Wb#g};R1VEF;ZbdXc(PP+IrMbBL4 z*Kj!FN=C>WNeNar^+FhFh?1!Q3;R0jbwB?M+9x?^1ZXi~YomwB!5)zj&KUs%%(gDk zpB75^0p(f@sa#!L-3+H%BlFlIj+rE&-pp33)TLsIs{ePw37k)>kR&>tFaX&IP8p)T z{@^JFn}h(t)kV<2)m(GV882$u`yQB=pk4wv3S6Y!uaLk4uz(*J5T0c42Buq7b6bEd zi7DO(mN4O>kIR;puuQIT{J6k0(F}QFfgw-`+@5;xUsaUA2xo1Ftk|@v{NnB+H*lJt|;+m5n z(4Kc=vtdSpk?I@U@Kl3{MZ&Uxi$6GVg~H$E?;lilelI0j-#|vHW*i3f2pVpy9Bz%z-OV_Ts2nZOoni$|p zD41Y1SlO4!uM%xzX>&g)Cp-h5QvwRrGHj#GAwlr)>|o;_ihSjg=e*6TtE*H$tDPNobuA#5;$P}LuTwnVE(HJ4%Of>-`w+V$bZ8sTzHy$0amu?KP z2%>mu)AJ+^C4Naj6wQK9=X62@02vYB%##R&V89^Y+yIGiuw+1NsbFtTWnNc;6m(>!||d9O2L}vagC0MG2h zI@*S)wp*~~;+7HAaSy{5!Dl<>As=wLIWuOln29)0kkz-aNkrooI-}AWS~0U}hk9n_ z%^4LC$~{7w)Lf!mmxlFR%7Hrs|9OH-nN;;|fbTu_KJeuBqBz{fOsXq79?q*6BO=1X zGbF7h%&sIhTs$d%(&PjmQM8ZeVfgiNHap)vPy(*jQuyYpXcpsBL#Uup}2qzmyoLkG}8 z^2HtTPdUGFS&+e`!5a-FAUxv&W&WM4H$s3oTnK|SiyD?`l%{twB-x^^WuI;#kOS;@ zK-I`bvt4i|ubjd213k=L8PXv@=GG_oJ9$Gd*5>T*^v|khi(IG0QN6>nxmCpA?tizH z0Q|Ezv>n*-bz^dm^(Id`Obm+DfI&L9aBNbi{x`a!Y=;f1r|7N^#Od9D?(&4$O!fOZ-OD(BF{d*Yv4L}s@)`l zF#%A3HLA6IT*5rElq6Eb`wJmC;UwVVN@0$fU3?_NS3RDt;jh`8~%EF`q7Z z1rE@WR90%`;^qtrWg2dA*a{Nc^KBXJcK1Zo?w|a0Q_Udz@QYy55a&OJ81-lY+G6+@ z`;s|gTqq&TEXsF2A6}5x@1*vUt!Vc6u*Rg+j`es{57q5|KIDBuwf`qtJEvchg()CpU+VpeixlepG;&TjmG2` zX*IUcp0B`E_?MJ0cL^9x0?k}G*6YzLmaVngTp2;C90Z)yP0Dou^Z*;zpWN#WTZ1M1 zjp**?R2FR<-4C#XDC~aXo=%?Z7xxnrhUX>5KQvT?7-1@p0K%fduXGMd{x_A;(eG$P%F!rT+CJTwy1Ka;T%W8_;WSaJ#1Vc3lmtKS+UIa)U@~arg*X~Y zdlDEPvI?L|?wHZ`&9T0o*4^MNHDzhD{!1vvrcG;pSkl>SbhWHE>OTsuX8Jq|Ocls<|4s^Klji0IRJ~Pm*e{-!+TeBw4#TA0*AO1s z{sfB_bOiqDT9oVM1A+@W_h%{C3exdHIf7@uN}@~+Em-g5C@~Wi2`5QJrmh(;GF1d0 z_5p^MMnPRb|96qXGwl&m?FLR^}^u7j;ePs`e-j`D! zfYqLAcRV{_D065e-r%3_CPxLxjsCnpeGWN~5Y+J+l7rR$>6m5uBIAAT0jHvhGUc6l z$hA%lneYtV<=<>vJ6aN5AwXsOeWBcA!K8`|W~eELZh|nwfToL*_W956juQc3%+$}h zf8WepS85risS(sZ0}2putoK>DhyEk4J!(SkJi`0Pp#Nf~LMI%8fVQiviYN}dhWZR3 z9V*QL7upG(@n(rF0?xJn?d1Uph+tmN_~xXg`~bW~2GeoEXM7%mIq0y$gHOa1(T4Hc z2?G0pv>fNuo}lvJHi#DRy&*&l0EkO?909cKT><9GhHuO$^QjRASZYa2o-r0pqxK~n z+QM!B(Gix>Vq%iw`eK71f@m?Qm8`is&Gd2GcDFVIh#-kfmskbKV+a~q7;~Eb((}Q% zst6mGRRG2sUISRePpf2-Wn0u!STVF1o>E|03h3 z2?Vb~z%66dF+0Kb2JiEmn(2^T(>M znW@*rnEEnpGlVTBa4?hXW0kO&`{{bn0_)}JX;%m;386Pn!S9sc_sJnKCZ+)@h~+kovtH>>J2V}VJf`~!3$9X}-+_CAa9 zRTiH)#asME5Qafn<$C(%89UOa8aRm;#SaiyM15=k zw-Fql0UOWn3IPDN2w)msFmeTFDtF2MK=inN0LD>D@J^d}t)GYj2EjwK!_!T>!;@pt z(bw;vT@RkP|Jd^b26<@;ttQ~|V5^-ZkfVVhiJJFzm!veZf+1(^vPUjV)~Un!5Vyf* zr9x?sB8eP5W~@M(QnEq?3k_aUACMzJTnc8QtaTKuA-_kZx5e}WHfID7rx?*_tHGFX zsiMb}+te@gf-K)XVjHwQeDBSF0jeXV<8!k9cj_w< zj2t21)JV;HKG3Q8Ts#a8eA|?rFLpkF$J%g&A(_F+I`XlXlEP8q^$OStS>N8=X?pV~ z&}w15gnP?U_7)=@Vo6kGHuZ^h6rZEUs^E7+sa0&q?0)JGU`?GUQK8;q$gvQRp-b;E zy}G*k9FzpogkCVW?l)&UB}NX~^P@xvXz_k<2YdBXM5!R3NH-qm`zZoI$t*_P=K<)e zse=-eS7aK(;IUoOYJT;%4TVECfbpCewF;NQ0XkuNZb%yRw{tZ zvtfQM%eoK|!Pb=O%kD4XjF&QFKm?*{2`0NUwC}NEaqK{6Jj44i0LTHqK-6u=g;qfGW}p zG6+Hq>mH= zc`1IY+XIRKUjX~`8ekfh2{x(GPEcS$@Gc7GT`Ehvd8ibu)hDM99<_p5cJuMhXPXl7 zW(77*fHUU{UiF^V0LrAq>tcZ0VOElZr}I3(dB*Dc0eERC_UM(lbhiaPNBk1GLQ zIbcf26r^-ayWO0TfqSmNhDb;}8B|9+PkY}JMV-+ie9!d#TY}R441GxZGwHtk2f6uyOP$9{ritqK=FI{I5J!BY~~2aLE>LF*oh^|+!j z;vfN_rC)x-n#dyjqOH}2{SDMl*5V?2+L&?hD`4&g1?+kkK`h-46SP$d-jK)N*z?&8s6!g|DHen4ZY2F4BCC0mJ8*jjY;I@4IqUvaK* zQ6K<|$};`Q39Qw@fFo)M*3YNkvIJC1tuF84&TK*73H!X^Pwn9e*D;8|XG+8z(VQ{tL!U843H`nX%&VXD*~ERX~mS(`b`vhiO~2iRSMJ;-9w_iPNCH zD$P7>s-|ebS!3ICAf8$l4tXP)1>_Y?pbt5OOe4|3(++ZX=~MM)`~1S(6VnF}9lrsW z>VPw`7#61*N8G0aVwjg`G50xuysEH}YU;S+oRyb0l1QBl;}K8H73TYwJ?6L<1eo|L zTdnTw>t5gd>3@=(fU1gcByOTzZ1+p!ALnrx4xP;$hWFU*8v)X|o)(RjTdi z&(n}6YE9(ox5oPW`?J^WBWJVON44g_br=gKS||FC?P?CWf0D9=WohYh6X(`N2pL;% zh5)W4y(gFeW?MIbnFE&DU2~0u0|GYvsk^aFS!_525^Kqf7TUKY;ynV&ISP?wi?m1! z0HES3w+iYwf57R2%VK)+nDtU`;)D~WV6Jq3G9L-t)c&Ashoh>3Q!lmp1mgWQ5Pexs z3w10$*MmT9b&(1V5BwH*S+ENLJ5}X$KOoa?gbO^U;9Rlail8qw(7h`ndKlNOwockw zJMj4xf>r7G38&ZYl^=LG#Z4nX!suY`0J3$=%4X_L zpC?`bIQoi91~y3~ux+P_W{zWIPasDQ-5G)a-uxJh_MLt9#6yjS_Y*gR z*+5>15Gb|!jlkRY?@GJvn*UVe^~6c=!iTmWXntV8d^RZgsdsv}BSH-JICJse7vfIr z06e#vmM1=CBlaJ#Oh`@tA0+DzkjPr%E+rSkF;zxwDz|`&# zOlmCB({^f5KCQ?a`TRzPU$MvOXb}gTXWih?Tq&QPFHr{1J_h9e;f=U#93z%;8tyg<9h}N&vx-^WVpf8s8{jJAWN+x;HXHVZczL(ivGw`Bc*`QW=+`| zpPc+2lrsz*+KJ^%`|d%&H;_zE0G^16Lq?k)gxk+o*fx#HIBZCDd&r0k92qBjIZ>Zc zQQq|f=X)-AWagwelk#Dx4KAHFCCbI}AfOJsFvaQyFejOifaG$K9EDep4Djxi(y_z4 zY6as)$Oj-q5Rf}}f`V*PS z=pF$gt5>KkN(f6MJOjY*2f9@|llQh`g4H5gd6q}MXoHHyy|tdLsbI)kCMu93@|zaA z{pEuN!v?a}F016a0f$flAgf!`$w>Ueg4u{KTx}R&M z_>H$(KuW_O0p$_t*d;=)1j0xS!})d(m+x&`V*U{YTi*i9jH}f@5yply84c*)z(N@@ z{lF3^eIvZ$c^)}@nX~4^VSoOaf}|9XfnS1qkToyvOBSiE2 zV-mpIK^>j4*PK}G&>NFk%oLx8?UK2$m85>T2MlTW8&;aw0@z4Haj%{~q34P1BK~F} zxk(r}91Cg4A~P;R6#`NjOgTyI?KL}pXkcW8=}oZqY;NAve$RZaG5)Uc=MrrdOom|@ zdBSlh4vu37+>}An;oU7zNx25~_-R#QMkBvqCizN`f-(nC+U#}mU1Z?+4tb(F<&#S zKXWV%5p$_YbHt)6U==|9LxM~O{>D(y-%PyC+#}4W>*iugZn}J38Pk-(s@MVqL0u6Q z1Axs-41k!4qO({rZ8N#>1MgH$T}-D-615^Ketdqc>dED0Ql9w$0c^>NNdj>Cs60p} zJ$N(SBBxs?^&lpN8IhqCW`Jn8Vr-vI^e_HBy;Po~nS=yjgF=n@Lb zH!6+H#9{*tEzDdBt(d%li|R7u;Vb9^+||aw1PQy#75<~wxwDkw1vamE3=Pqj{(k|< z(QG+xBHgDtVllNwAKP57VCn1o&kM)wj{9^Lo4xTWb=xjeug^2KYVNay(m%K!~Fte+L(b{ib~E(jplea%Jm4txh*uOp!{B zHx-Uw-oSwsSA)UIvX?7B=fo9AL`A%+C3I@26VG6cbSD*$h3}~5eG_H3mx&~vLU{>C z(<5}hmI2pf_lQe}&lQd&Hnw&jTgzU(0snE?%lCWrmvun@G3U_{Z3gk!Q`Xy$AP9q( z)d%#5BKd6pA;BbaIp`|6&UIAqY*5u)0tfs+T{ZTSLax}_eq!lK=wA5bB^9Ad^ZkGV zF&i4G+$M`iFQFd0^V{J*x*xYG)Tk%Bl)C058(-!fUBkbQDgYJjfFmn7{eOIefCDH8 zANOzx@!BBr>O=NgeKg*2jh}DXw-%YU6$PZRUh)~oi`cFfjOZ@K>5jXbt zhKp{TKBVo3?e@<8(h*qyEisL8xvaufWw32-sZ)i@%gbvzS;WZc{5J%QtP!rmiEh%X znF5f3H=v85kdnbIX!cH7angPWqc-q$V!87X4NKcm-Z6~TuyZ=Vzk*^WPVh8Xc=fvr zmGqx&=F1{Y&$BdJsdd8OdQJ>^rR%K5tR%S?j@DlQZp@NV92%l5{^K$#W5N6G6nku~9-m zZL{XPOMiM$FtM*tpewS3qhoN8O!(H^#oRZP{7JMQVX4~s&fVx&oW>Sf16x|$#O=sO zw_YxtH}sJeJQopmV}MJV(C-6qxsGeT1E`gZyllaViUlcT_&KhNl4md^7^kXWjt<`= z+qI84l!$B|`8ZHJ{~Sl)LZ1oILjwuA=*8mM=EY!t4exn1XVO)%hKF_nfkk`m47v^G zS29;St*xNn(+c;eqv9F;ignphR7obu9`g0&LP`As#p~#wWV+)Utx-S15W;_ZudVnz zWxQ2yx&5txZGz@{vg(KUbPHnOQue>UEQjY?%v{UKdJm-K`29C$OZDD`PJQZq$9zkq zg5|(LZgsliyP}0G949PM?YYOqsh9GIF7qnr$J66KJ}@Idd@J|r{Cw&aM#brV4jx5d zD?$Vw+uLaiWwBUIveUa?p#{t>KO392QLc9U%v=v2UI&F?^nrH$Vk97c`6?lZ2R4D8 zc=2CtcgB_+2INYm){I0vzaIciI&nKZWBRA`6{p-$KCvW@fVGE_^mk%L?w7qp55{2SkU7VpUVPlN!C1 ztZ+3Q^&S18?a(iiEH1b_xtK%c9xpEJc!^sj$+uPSe`w>F= z0Cq#6)`7q8WZ_@%uz)RQ3ny{AKIhjND%*B5^KuDc^H{p`Y zBhZv|tbxIH(Pl9B>M3F1NoJ$bkOT^GG}hSdxl!R=W@eyrl^c`*ZW)m1}#Bz zpI2>4$q;b=6i!-Q?PiZ1vmsZ~@qOOL99mew0}Uw!H!ILoJH`?r`*gg>;V@w>Y|;~k zw?NCZTRic%*FM|5b$)lb7LJ?tXX2*huNa~LXiGqiaEvc0TI>t}LAr-E-NlzPM;|!h9~mz&lY5NhW_6UgSzKA7vh?8;}_gnb#E!eCG=n!r*H;L@6*o$U$~* z7#9t5Tb_3NGOIvC(20^PtNlZ4ZYt-x&^V^iv&&_N%(nL@s)Ma(KPvY-*Qh# zePZvLzymXVMvmpwwxI3j+4 z&|Ia)mEMN_+wW$tiBEs^YVnF|^Sw-5)nqin26;p`#H?(}u&)V9J--o9c?M)_^$9i{ zhWxYLnY2PBH_#4I@H#s|cTsh(80CI zKRcQa8`0fq@3Q868=4&NxgSm0u(h_!yNe#CRu9onxpNVeNEHtesd06`c}lB?BEf?; zAripY_JZxLGp^Z_wu)=hgHsa>kH+qDgbygKfq*0{R#i}CldO=g=ig+YXVg8{GA~}+ z-9&II^6%?@#e$mkm!4T6`C>Pxt+p-9B|IliHvYVUSlg>rN~h85W3i$MiyaA`*iT~a z#-^>Np*YY*p9sCw%l_f_^b?1bkMoY#3wno`smi^-Yag+zJ($o6W&Je*3RdsTyr#WW zG-rg5YHsXqGQ-uGt(uYJv^rRv{n_{Ps=g3`7%1lZh}Z1}K#n8FkcAt5txYdy6KnhR zY2>z>EQZ_79w>5N@;zLjgMoD)jxODs`Ez(fPmyR2zLxdW_QOzUm%FYKVU%d%BYi9j zkdxLMaQxFZXMsX*1{*@6q(2Bk0-y$rDlMd=%uN?a<9(a%Om`(gDIA5b5ee;geEeXe z+c^=3=ZM^fIR=Fgnzd}Yo~@p?8hWDg`hmnL8>j(5#fA)xR2=0IxlOPUQXriId%6m*s&{Lkv>k#sYsXCZYxms@&SyW+Col4)2b~KsFc9Krhq{=_ichH%DTc@?H!O&p z9NZbs!5lJ+;D5Zj+&*g z>vh+zC^)Z;L%a`i^{i`BsW5LfDl-1Bt*;KMa*g^WwhDs8CZux{QWBzolypmjfS`1W zbSa^9gS3DM(%q?ml%ODpw6uhTbc1|rpZm?+xik0tan8)q!R@=>XFY5EV*Q$tyRnbA z9Brx;72|(jg#IcEcl8~H7fJ&_7{JJ6SK>8WDsa#@;6ltGXmToehNp$8jHlgjuQ-zj zz?%1kk*|6}jVlcuX`Z{=7o_DS6H1T7+$gc=BwmhnzmGcORdi_x&!b#+ZJh1CBHm(i z*}S(aWxl(+Gdf^}?=HIfw$0^DQ;g>DXuwKmv-jsCX6#wH)sykMj$`&s`8Q8%IKegGiTFV4uQ(&6n%oXBMAWdGm4RT5Cd1a`rL#1_3NCk15r zG?oPEABsrwivn+v%%9H-9OtRKsQeYC5-axT%)0t)Y8HZ(4N% z$6qnXE+wHXR^10!11!K5DnMW&2B51h&!p7~GJ?l&)gR^h;=VZT&DX#8GFKY$;$VSn z`}R+E1CIT$`5YEwiU5@>)e8%iE-5?1s9%lV)Dq>ixK5q%@i{Vd`;U+Do~5@2J4V#B ztwwijzdR`)D)3NVqUH9Ixi-2LfW3ZHuv(A4Vn7FzWe9HHMLxfQ*_$osx&5TZah9$s(7fg|3RiBO zuaBA$D_Sj{!LHl)(xDxfWBkJKXuI4}W|1|Tet)}ptm?|b!p>gb1VK4iGA(L9-wQmtX`fZE=hD_(#zPU8=@FP0eo`g+?MDnD4GE5lZKVhl*Kg1O zC*G^Budg?>2vb;0w~fXTn_1V!LXYMU{c$CX|Mw~rBeBcxzOb4r{8{yv-=oWBcn^9> zU7CMIsWa&Lay@G4!Fb zOlOlOSH(>|)kKa&t(xhIr`UaoxAW_E?rhF>-*Q-O)SMXj`BCZGFOgC&0eT_p(~r#o zFXIHP)U*Mig2H-Tw9^WkD0a&Zwrp2;AJ`_`HkrXd8j{B`GILkYQ{E$iY{*)-is|Tz+Q9jiBr8+ zVR=9WklOUV$Tbf3sRncE@N-BCB*0WWVpCQ-JGvHgY)NzDm2zL<@|@`LfHbbcx3Rkp zQ&}%MIF8-z?~%<6MY2D8{3&2&8tF+ zFB&SI!2hT~%cKGj9Jg@|#ni>Uc|-1Bi(QK=Oh)-L9OF$-UORsX-xcL|e>VFdr?{RV zTl76LmNFdlfrT~i-y1(^dwr+n>c*Y$4H_(1%*{uzA>rRJ3Y>ogvjx zR=GXD8cio0{;iNdTRGMJxbjE6P|E~GH0?rVi}#vlO`icFbL+{K(zM#nLO-=1s|IJ7 zb8bxx3=dp8kEH5eruMF0H;VRj+ab;&8buOa!<#Uu@Y=Jqa}3*dgJ&f>>-)0u(Ud#$ zL{L%f8EKboVsLW3Uf{aB`IY#Jrw0kS`l8Ns&y1U*0b+hHfAm-3o%l!Qw@)9V=8#yR z&w6dn81K&}N03BJ#YHtUSg@V)40i+a1A^iqn#uHiehhb#6!hTH3W^Uid}O8)X+35_x#ry3serUff@d zv(8S=gAMN2fb6dD_ZEBP9G_~-%gb}8#7-N|NiJbgJcEza$CiCT!}%dv5~&lQ2$+H0 z>6?Q%8?nwuE-kt9g7OlYc=w2fe9wgdiLQLUBegNrOjvIGN$SU)-nhyqA=#xuGoeZ0P~h9 ztb4M(@VX4R5OUtf8ORU|@tZv_{u^4x(){rM;!Q2tJ~rQtJ##Yrh_`8Ozx&ws|H4h$ zN3vnaD}|c*QgNSU)xok$rhpbU#4d7;lEf~Bfeh+F62AVvqU7PalF)ZxrL%#oI3@jo zsR=w022(KurZNHx3rjmV7kIJ)1`K{xZbl9Z!-VuVR*&eDY9CFovV7Q>&N?IbnEf<*Ot_3%!NHr+&gL^|w=;c(_{Q!Uv-iS}J0wDxh z8`#h&XawJv!(Y!YE-gq)j`e?r6S`;)TmFAiy?~tnuFS~-2*6FR>WnI45ug|bSa(Bd z@7H15S?Hj$=&_I+xv;K>R3eF2@tV2*>H9^{?BxK|k#0G&-Odo-wa{=gg8*-o- z0Wa!C&ZyY{j)3Ty+LwL;@eF+(M?jjK5{IK>ZuDj6&}53%H()#J*1QFbIt7gd_LibwwUJy*Cz$gn#XNH;685Qri;HcaQL3B*WlZvr}=F8Yw|;f zcZOiC_C>e^pweyKG0pEj0G`ws3t|a_P}){QgaO3Na*|A~z18_-RK^e2o!0EEu^{$M z!}u36@O7667P8}lRQxGi!<(tWDf7Jx7}90KZ^ z*JWAd+NkZ{BI8dlaGid|*{G7gB#Pzs2RcjIgWDmOjl68f+q7KEN}2@HGjLIZmk*JDP8 zjAEk)sSfZiGz7H)k}y!_9QYSfTx2Gmau_^-!P4FiUYROJKrLl=aT+Mr(}2KgphVOdGmTzI#4 z8)D!%Ky^N{SZ^ITdIjRg+Tm~dv#x4zU@v74=f%N@-Z8w#ke`Q(){lGLpscW@GRAhN zlC66u8FZcOIXLVgyY(g{c~rMD(N5pWF@7X%#)$cmXuQUTL@Ajk3QYCyVTkl5R=(#v zxV@Ig$sWpy&J(-}wsGy&P5_wJ!dWM-;(C&zi(FAfEJ%HqPn&`=^XF?ZfYc#bK=H?ml*0Vk-VG%uoB{}j^OkkmN3F#0@`rM2_}Qctig;I-Xx)b_pK9yoSK+{ z*KU-n#dAmM3U{caU|xd`zZN%_3i^4pj#GEG2M_ZZ+d{rS;vchQ&es?$Tzz@TI{X{pih%l@J5)>XsG?YC0sX`N zPjJ;|CaA60utEVFgu|U6H0)?fSm7eda!I_xE2{FJqYAVIy_ILXOUjAA{&iG~n%=+! z!FL)O!c@yg8X zI<5WR+SOVWRhj}sv9%>oRURCkP*r|$%|hH8Z}n>>?y5IjhQnnFf?fC7@6^(4gP^N} z>_fw#VEl{gbhSwsH_B^2cxLnv$EtfS)ZXeI3uS@CpS}|dKuqLFXP>27JUj#i;KrwS z_k0y3#ifCq(wYh}?T$Q|zanZ%f=Olk_W6$KLnel)2=m`4LpG>a)(Rsm@gtx#-UcfR zm8P#xpt#pA3hiz9UfRM&iVgvP0M!dUgpuAh7ER#v{Gy9xv{;)+QGO(Z1>zI14+j4{ z1DYDGVSTz(f2|226*qDHs#`^f@wSigNAKLo(ihiI1P#f_dfRx%13Xy!wsm;aj=Lx< zIl-nM@_eS9+aJm_oA`;s7n@ojctgl_mFV#Av~te6P)VfGl4MdzIQns_NIwCgG{qfj zB1l;dgNm3UcUz1dkZ`emEDQ7@!=xpSB~1m7s-_|l+y`0Az26wGXB2-MiunmfK7Ry_ z+#E2Bfgf~%t9#Y4W-hyh`#kDM4DS=@4j3?F*myyqQF}vG^P&r?(V~|SHU?l)S$!g^ zs&jC`vFnx5fb!4UR;)06g;>aKjTCaU%IgSvt_LvH*IRK{%BmIuqI zSjdj&dfcRak#jFR7z`9rF!*VsICH+(N2Eier6UoDWK*;M>d>OgTw&Qy1?Lliw#4Q5 zHqMc>7h2wwv?M`g-9#$}WueOKGet`;#Yz$)^$bk~&rVN8YMu$`xkE*+bt?eSIt4_d zsX8N3AVX{Va~(!)&{vkRY%ljyYnSK~SN&^soE4YupKC};Ff&|qXX@ECcR-6UpHN_7 zU&U_&JiZd>DXPae52@r^cmn(L(8p2oHUfn*hvc)wJVWR*wsAJ?HiwPupj^;Y}x9)#TyD{NFY7m3ybW+@H>vP8^M-Fmg_V&ZM zFaxa=88SD#>i}Wx`S^P}cUpqKC!QY^A?vX+T4+tRx;I{e_kuaaPT!IZG@cJ{}~nYgB-6_MS3wXSpYmGKcB5YzwQ)D8qUa z^qa3XrZV^~!0qw2IaRCw*(Tj8!V-Xz7k z)1ubir-W`xfC55qh|<&tvqYLx%JeMl7Ak8VEl*Qu^FqMkEzWZt{-C(nBBw6^V)^a0 z5zf%|#3z*e%tTtI1BwWr5^E2nY&@ zGFHr4uQSH^_|#i4WFSahvF$yg^UXdeHPEdCt-)5(W7x-s2Sv$ z=eviT)2^nTueSNl#E@B!)Qks{lcJ=!)ydw9-c$%wkU!t@^y@R=l|VPy3T^fX2z`!* z9y(6Oa(0tq00eQ3_@Ym7I)3gtQ{0akeT> z@j~Kvac~nfgBI5_z7-)pru9!&|BOJ`qtJ?7L4){EUAbFTnO>kp>gY*}W_JuzU@`s% zM@SNsu}lWETORzF(ViLjDI;(EHEB_ThXnZlxqCo_GDnh%{Ts5MBq!fn)mQsG&GG~!Jn@VRY4O6Cb$&m zJpRei+pihi!%{$B)&5NmJ&vK>H$c^yfpn{h>>TP3OzRELmRxF{v|b#(bR@`ZF7k30Os-hcMwWQY953UtHIqfa^%$)6u^TDhKJhqAb4e=Ns(;h3n zb%^r^r(Wj7)~B<9tRK|!8zBD52KiV42<4P)9cg-R6>=kl7HWgY%ULi9Sr{6Pfj!1@ z+Km%x=Za?AUL9fvy@iTSi`%-cLZ1S;3vw=WEd4^MR7FLyj^YL-e`hWgIggRP!}Oz% z-nGh82MOJsuN&bBKpH12x`WHH+l|hj7xcQOCALQ#>-Pfydo9B#qrzzzEg5|xrXRz9 z^RtB>H#&DsXA70QR8(Lz>>=ki2t?DkCC0iR9zt(3YSpUFfvf;`w!zWnR0x1%k6OAZ z*`WL{M%p@*X~iSp(9u{bA{h*W!FT*#$^@E~K}))pc2D%zGG>*Kf?&8cFItOMuY#@p zZkH=MDR1c0OCR(|2F2#!C(nV<$3I3(PLI`spd9Z@O2$7y+oR(*p2D&BCJN(k2!&QL zLb93?J$~Sz7}T_3ppt@Ltc0r{d~{bb7pkujf$Ys+EiW8y=2OOP2!kbKkk0$o^^_Y@ z{!WRb;{lT&^L+d&HR``!7u_Fb!{Gq~D#G0HPE?tdFGoYh7lE@3gSQF(_KHrjFkMAo zpW6zr`}Q-~9{Vkr5#gc_0aatFqTre-HeBb_Pj)nsg94a}DLl6jQ2F%oBW@Xb!$G^I z`1p${Z2+HHHyZ7rknk0Sonm&=sx5s9@K97IO11k)a1}H?af@biwLWMIk7$HWZ5tuGONQTTJ!!iGPo4@1J`cy z82NWLCecgruYvsfUrF$)5F1`^p(VQif@c`bj1DyidtAE{nMfsTD)I60VoV$yWVoJ^ z<;KEFqI3oQd?`zN*l*){@?wT*JuWj6o+g2?)1bcMv+a55T8GeE|6FG+c*tV4`gywg zyP*nxQAK1R(Mi9S6rXLxN1~ z%K^AbbA|*}${Tgnp;#u}Gi1iBCxQvY4~D|k=E;A3jg9~+ zdI)xCzG53b0&M_=(Xf4LYHEHBO}AQ%!yt|in!3N+r%~$jXrLnN0d|eu7y*(TF67Tl zk}E|3i_<{S&_SSRvIAoiUKVH8=^eoR4hG-A>tn(`hxT6xiJ^0jz_{V~zqU#atoSt^ za8#;MY8bNkzvXH2++hOh4-0p!5%ze;49}{L-mnf3&pAjcH*-1s}8U`T2Rtr{tUQ7#YRKlyJ%h#W0yVfxYmj#*dfLyz0I-pA}{?l&=*^~b)^pK*$)&q78{%v4SXVo%j|}- zub_plfLIqHaMbKV$=)VmESO38c zkRsy7@$cW6Bh}-3FUh|Ra@{DW^o|pW_0K?ZMzZ?kgEaE=- zZa*->j6P2xR3ZlM{2im|*ew+r{JiBP$59FvUYOJb$RxqBU z54Y`DSrl|)oH6EMe^FjG!1WLFS|n}?YlbAiaVQk4f+;}D%hRH@z+EprNp}Rq(~ksnu)>Cy7Rq4-B6%TC$W;L^w-rxz*%tc%NW;t z7ZOl(?N7-Rc^Ebk+{hcsd0i~u9iGGyH<@ENTM`SK8#DsciMDtPyfvMslkwN;r~5gf zoru_AVHoa&s;PNZp>8-(7uG08Km;rU;0)s}UIM71HybWw(p++S`1vX+$#uG+| z@^FH%ypc3l5YE#Kk*+fYc)+)NMsURh?gs=iws0Qa>bpPPRQY7!y+378G0j)YNEj>s zMQspwhSKlRGkHli`NyNDbG191Y#qDYx=}LngNoOErlT$BIYw$c=VGoD-Um2v``{ob zFpt!#0v<-on+TXGG@#tMkt&W#W3e(agyDb|JpdXI0qrRB&Rdu$DRN3N-2(M?Qv*pm z`4;C4_&#(y6+~=nsGX;E*OC~Rc5_Sts>|!=_Y*5|JdC*sI_rvSNNCg&ZbpRrz?>g-HOqg*6-+Nv| ztsl@rcs}n0lKr-h7p#~e0JplatLe&Nb+a(Yt$>R}T}_+uhNgOH=l0*pT0DRt3B2_` zf={EaPnmPL)-+U_-0p@W7_r5lS0GdW9kpHtmHa3>LcUb0lMmAUkO34V^Tk?g#*m}Rn3FF;nloP38b0NFZ!AMQ&#r3W zg4c;(nA(N7R3C(}hUA*@>xcuduRlr7a?~pvmaYTqH8y@V4r}T_=&(DWY_$N(Fk!kN zw+|2H%8ft%EYKUZ%hji9>~nQdU!;JQ0fQ7E92}HhtE~-2du-d0Un3Y3N|mD7kzpJy zgNfNLU|D2XALTyflTCwrePfo{T*v{-qcq!oSWS-^z`VCaw>eX&f%=xz-4g# zBn`0vCCiRba7G%7PAUsvHja58a4jTY;)`5xUFG|oITNej>7RH@OHS4BYr%*?2j>AP zw0}BmIkxg$3Q3^`@&1e-7E>sUfG(&F<|3{)=S(a%eGp>aeYDI5Klz_{UNDM?5zEy7 z)cQRGi*uq(K|GUIrKwC;gs?ezc2-o_1GpRM(AmJFst$ZDRRf%|av1=ynM;G z@pq7&TI(MdwpjuM&(9ay9`0pE=ZHnztc}!W$DtIg;uk98>(oF>kh9L=paK ztK&?FSL9!6+MRfmzsW11?Slx+cjKRMJZb#z>pgovo}W8U{Z()Jrfjp`-{ci`jhuop z{@QZ`f$s0vnPxo;{a;KPL%lC|Pn1Pp?js)3iM9`iOfI&YZJrc*fAZU-`#aN+L%Dsl z^FGH*R=VnUZ>;!Z?}OM|mJ?T5uIlB*`vLHxz+*~|d@cyZQ;)>T2bJZ5#lMzdvjp+C zQm+(fL|xUzo*6a2N8OZ8Mb+LdQc0$%T*giQU_OCRpQqdXu|cQ?=_F|>29&jlkO`Hzh5r%^U${XlQc(v z+WQ<$DvbP*ryOAhC{xSg1P51(^GA1Wx)B&qFro2RyCrO-V9D^U3l~Akc$VXCq!5aI-&r?oeS>W-kAP)lh3qAJzDAH zj@jE*&8H}Z%r6XStI{f|X*1Q%*AC0Z!=&FH7XEP$`#Ulhakb!4HETWNpPaY&j8|@y zGd6`u#rwMfuo^A%mg@d9F7vZ!M->y&QXMLm^(5&`=PL|a`CP}kTkI_i+Zo` z-uOl>LIU0hN$4+z>OJW1fD`JqgZ}c*OoJeK|KNL~Qm_|6go)t+6WY$b)qH^=gSmEO z9UfyiwlG;5eac?IepF*^P<>UkeL-U{4*YB9!lo!_?CJKApAIGAo1+{5qdk#=Kxv!p zk@xFlMHtMEblen6v&rn^izoLu7; ziL`MCl=n8`@{_X^Dyjtkjb4YJ4aDa+k8{5MoQd_DyvX$aa(A|jG`y4w<4ndQSo(KEA5iX_0bgQv-dp>I)i zlQj-Ghi7-;UH*Jfg=GdOM^H{eLy3@iu-ruATk8FrudQ8Gr}sd5+ZkbXd$7oj^MmSJ za`fJj+E3blxnW}1eItn_!dzkTm3)75?50V~YGiuu?wX3oQCKwylJA@supX97&Wkgq z4?4f5>(^MQt|~Z^75nnfo_r7e&ic`7Nn;ZeSry%$@(uf}6H)9>aTPiJC)-%UgX~^y z=ix3*&6k>%A?357*~PB6$*a@N^+P)E&vnrh7+I_*E^b5ke=hEFUojOSaq3=y56(K( z!8R+)UF!-79KH9wmO}cDmRh?nbqVji&@RX|yWlIjP&X3t!&HoK8QaOIdhwJBc50>N zaTbPB;=qP$%%Ebj{Gr;x)CW;zMz9-P!s+>iaT_9MCS0|+ed?Zw<+qfl4}9qE;sNm3 z|KrWij%@_Y>H%oS&{XBLdZnUH{vQ|DDMpn|F>+9jOAf?4e#~_=Y6u?DN^a+Dy7^=I zo4;3Uzhpaytj4;b!?ecNPm7mlTp|Z%X2R9B(}I$l`gPYPM!zW80 zSQ^~_UjM}X0}EGB`q5=iUW3mCL6hDI0VgYu=8@VT;j68+RH3wKI~VUi-)nfW%akn> z^{U@xY`!_0d*s4-9*+26p*D4{VLq-H5u1i6e#scN#K?ul2(w?bgG9;Nskh9{{hdq7 zmKk<3e>`jLzt-kaeqaCM%m2k>=d*Z*fBi=rz5!W4vHmNvn4(Ol|*fL73u3 zGDEZ$OZikSu}I_@v5Bi+v*VqI6IiPvwK29@axWHR20nuZeYN$sUgYNfeNfSb*qZo; z+snFScV)S+HGxjQc*VWdLn`U>#E6{}(W#T1-+7Z$WZ#wZjb4FsF;A*85ULK*hP*fG z;3)W6(xmig+j9mWJcv?W~_hZlNo>X%pFL)$VUFo z%wkb~zp*_eHy4Uv$!V$2E3Mcs9nOuT5`H)1hpft^(ML_ZEOnIdI2Ebdzqs((wzss>zTrxHXQol_htUN9>L-`3y%my3d1`RWlx%EpF_pte>rlJ9>#UbB}I` z!xiw??0b?T0|h2-7Q@oQj!S!V^FmbQo!l?h0u|uiBYlKN*Ju^)9XTmMtEKk6=l2_z z!~lYpyT4t~R=QQ4QCk*)n1r^wSmpi3=MJy@+uW7>jOobU>UKr#jSV(i-aOYoF$DjI z-7oAd+)d$v`P#Zt$w#l&sP&I}N+ljd3h`CG_$B4ge^hCp$1__HJ}lq-;~z9qapEtV zBsGrhHLIRI8WNzu*>AL$%0g$Gs?TI_M zfvCalh~RNvGeN9OM4;%>;1Pd3UO7rNS?aCkp~iYe{?X;$U+0~_7mZHz4mb9@o6Q(a z3b@55+ocQ`V!s7d6%L2o-6V9M=oRZ3IV+i*tW20UxuG#&xbR1GdC){5`93yUj~?Pd zU$FQ7B5vp}HTQg*9ryb{QuwT;Wm!GqHkXZQlS!LOl{JMY+Nl!Py(e##6FSb6i`IkU zYAfONQK2uaI9$uW4Vx_sxCF}uL$bXl56P?&SdlWKIBCMP{kaU%Au~W0h9Kpd-(5`1! z>(G!}gn@w*swne7`w|P(?wXmSOv!={L3!mx=dBVu@tt~8dB&|TN`+xfVGZX{Jt%5l z$LA`)m&9Cg(7Rs5Y|?_t6O{%{=6B%bhs1dEY(ChPeoANiu8^VPh#qi$7=tn?Ks+rEvVFq$yroN2^Px(KGIG%K<*FOIRtPAkl4c3xwGoOkm4 zql~t;rH&eIQOBG+VI;{Ldlyzm8iM^V&iYhqsD99e)hg)V$q%&Gd|KQ&o?))N%4OH% z#Yw6?JYje~X14O9R3R^F@ax@zlEZ(&Bf9#h98WLLNR+3fYJ8VDT!->nE&QqlzQzyj zF~$|2ubc_kCJH=Q|M2YU;Gm+sIzE~t&}v-bNa*>aY{hHe%J8|3%V{B?sjgu=Ro%Ab!0lnD!$F%Dm$SN@c*(NI73V~2 znt8O|M;k$al*u=U>t^{uRXN>aR$2`}fLuT5vus2gr?D%(!9mF6WR4zwK(NPQ)RG$I z`)19pwm`gH<}8a*?^R(dBL3y?3wMYIXhg%xEIvu)?vnBOjM~dYe;bj=Zy(j@+OVK? zyDE#`_6a+LI?;yW-`&ivx2AQ4X|H4sPgO^9<^F1PR(8yuKz)x`xjl%Q&M(NQU@}-a z#v(KJM~_FK08I+K_se7^`pf`gpFs^v|qvuNI*=sM*9ut4mdbI4U8d> ze)DpUV6$TyM{F?Un zXz7z&Er(9!^L8m%8PY1ztCz>tR%kiw!AEvSBmltH_fBuR^eYSs8)~2_4Jnbo9J1uC;~Dq=cX7JdLqFS7vM*BTV;kqDE-Kr37N{R1V`~>{hm*}zdS;2)lIZhP zQ-hH#GpufdxoxCtxXz)gYW#I=8A1yG%!_- zMm;xQf8feKdA;?{dQ9Gpw7J#!oI6d?2>UkBVc!_HA)Ro+{nAwa+crbOQGDRpb|casfXyq_zs%kR{RUH5JEaBUG9&@OS*#UMbEsB zt^b_qr$3w`VO2s3{tDS7$4)@UTCs`mjfBg%`{WI8p;oP8;e;Dq5(? zXS>gSe0IhhPX42Em5oJ1PMPT)+T+Mb$35FGP)9@*^B3;<3g2#y4U>B6orNCxYF1U9 zmQC2lIHgHee!z= zEnarc$Dvmi3Hw1@%9;$&<=FXC~0XMkSh_3+t4TfRz1 zJB{{i2CFh7b})~GlXDl*d;mz@Qty6C7(xoZQ)((QI%c4t(Hd2IKzIP*?`oI6HnO;I z5)mH!0b3>YmSFq`<=_uCZaG>^Dfn=wHNKun^ zw@s%xZfL{04z_t%~E$sebu zH;Z-&7;y_yUoeZZqEgwy_*d$dnMaqV9f{@0PYdrCOp|ZA4P@awYm|$|0-l<91g#Gocw2$&rigWb1s}~*J z6g3VJ7sgDzYT3$v>|XL-OBu-8^1<(5OGkM(7?%;&>Lv%8O^lQF5dXN-WyllfnZ*3v z#r)CodNzB@Agki;VC4xPvLOT`=!nkyMKVHRjungJ$AGZDoowi}*~~m#mmy`zMXi z3jgDY4T8Nzc@Jlk@jpTE_#ODodDCBeVI3&&A6!vwgodJwa_+1gEg0~RqO6)snbeb4 F{{!l18Mpud diff --git a/fastlane/Fastfile b/fastlane/Fastfile index ce5b3e9..43c06ce 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -50,9 +50,29 @@ platform :android do run_detail_tests() end - desc "Runs tests in repository module" - lane :test_repository do - run_repository_tests() + desc "Runs tests in abstraction module" + lane :test_abstractions do + run_abstractions_tests() + end + + desc "Runs tests in definitions module" + lane :test_definitions do + run_definitions_tests() + end + + desc "Runs tests in interactors module" + lane :test_interactors do + run_interactors_tests() + end + + desc "Runs tests in network module" + lane :test_network do + run_network_tests() + end + + desc "Runs tests in persistence module" + lane :test_persistence do + run_persistence_tests() end # ================ Gradle tasks ================ @@ -81,7 +101,23 @@ platform :android do gradle(task: "features:detail:test --continue") end - def run_repository_tests - gradle(task: "repository:test --continue") + def run_abstractions_tests + gradle(task: "abstractions:test --continue") + end + + def run_definitions_tests + gradle(task: "data:definitions:test --continue") + end + + def run_interactors_tests + gradle(task: "data:interactors:test --continue") + end + + def run_network_tests + gradle(task: "data:network:test --continue") + end + + def run_persistence_tests + gradle(task: "data:persistence:test --continue") end end diff --git a/features/detail/build.gradle b/features/detail/build.gradle index 43a02bd..cea8317 100644 --- a/features/detail/build.gradle +++ b/features/detail/build.gradle @@ -14,7 +14,5 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation project(':repository') - testImplementation testLibraries.coroutinesTest } diff --git a/features/detail/src/main/kotlin/com/melih/detail/data/LaunchDetailItem.kt b/features/detail/src/main/kotlin/com/melih/detail/data/LaunchDetailItem.kt new file mode 100644 index 0000000..e6676f4 --- /dev/null +++ b/features/detail/src/main/kotlin/com/melih/detail/data/LaunchDetailItem.kt @@ -0,0 +1,10 @@ +package com.melih.launches.data + +import com.melih.abstractions.data.ViewEntity + +data class LaunchDetailItem( + val id: Long, + val imageUrl: String, + val rocketName: String, + val missionDescription: String +) : ViewEntity diff --git a/features/detail/src/main/kotlin/com/melih/detail/data/LaunchDetailMapper.kt b/features/detail/src/main/kotlin/com/melih/detail/data/LaunchDetailMapper.kt new file mode 100644 index 0000000..32f61c0 --- /dev/null +++ b/features/detail/src/main/kotlin/com/melih/detail/data/LaunchDetailMapper.kt @@ -0,0 +1,18 @@ +package com.melih.launches.data + +import com.melih.abstractions.mapper.Mapper +import com.melih.definitions.entities.LaunchEntity +import javax.inject.Inject + +class LaunchDetailMapper @Inject constructor() : Mapper() { + + override fun convert(launchEntity: LaunchEntity) = + with(launchEntity) { + LaunchDetailItem( + id, + rocket.imageURL, + rocket.name, + if (!missions.isNullOrEmpty()) missions[0].description else "" + ) + } +} diff --git a/features/detail/src/main/kotlin/com/melih/detail/di/modules/DetailFragmentModule.kt b/features/detail/src/main/kotlin/com/melih/detail/di/modules/DetailFragmentModule.kt index c4a58a2..946e852 100644 --- a/features/detail/src/main/kotlin/com/melih/detail/di/modules/DetailFragmentModule.kt +++ b/features/detail/src/main/kotlin/com/melih/detail/di/modules/DetailFragmentModule.kt @@ -2,11 +2,15 @@ package com.melih.detail.di.modules import androidx.lifecycle.ViewModel import androidx.navigation.fragment.navArgs +import com.melih.abstractions.mapper.Mapper import com.melih.core.di.keys.ViewModelKey +import com.melih.definitions.entities.LaunchEntity import com.melih.detail.ui.DetailFragment import com.melih.detail.ui.DetailFragmentArgs import com.melih.detail.ui.DetailViewModel -import com.melih.repository.interactors.GetLaunchDetails +import com.melih.interactors.GetLaunchDetails +import com.melih.launches.data.LaunchDetailItem +import com.melih.launches.data.LaunchDetailMapper import dagger.Binds import dagger.Module import dagger.Provides @@ -21,6 +25,9 @@ abstract class DetailFragmentModule { @IntoMap @ViewModelKey(DetailViewModel::class) abstract fun detailViewModel(detailViewModel: DetailViewModel): ViewModel + + @Binds + abstract fun detailMapper(mapper: LaunchDetailMapper): Mapper //endregion @Module diff --git a/features/detail/src/main/kotlin/com/melih/detail/ui/DetailViewModel.kt b/features/detail/src/main/kotlin/com/melih/detail/ui/DetailViewModel.kt index f63ee29..8897dbc 100644 --- a/features/detail/src/main/kotlin/com/melih/detail/ui/DetailViewModel.kt +++ b/features/detail/src/main/kotlin/com/melih/detail/ui/DetailViewModel.kt @@ -1,34 +1,31 @@ package com.melih.detail.ui import androidx.lifecycle.Transformations.map +import com.melih.abstractions.deliverable.handle import com.melih.core.base.viewmodel.BaseViewModel -import com.melih.repository.entities.LaunchEntity -import com.melih.repository.interactors.GetLaunchDetails -import com.melih.repository.interactors.base.handle +import com.melih.interactors.GetLaunchDetails +import com.melih.launches.data.LaunchDetailItem +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collect import javax.inject.Inject class DetailViewModel @Inject constructor( - private val getLaunchDetails: GetLaunchDetails, + private val getLaunchDetails: GetLaunchDetails, private val getLaunchDetailsParams: GetLaunchDetails.Params -) : BaseViewModel() { +) : BaseViewModel() { //region Properties val rocketName = map(successData) { - it.rocket.name + it.rocketName } val description = map(successData) { - if (it.missions.isEmpty()) { - "" - } else { - it.missions[0].description - } + it.missionDescription } val imageUrl = map(successData) { - it.rocket.imageURL + it.imageUrl } //endregion diff --git a/features/detail/src/main/res/layout/fragment_detail.xml b/features/detail/src/main/res/layout/fragment_detail.xml index dde2a8f..e58cded 100644 --- a/features/detail/src/main/res/layout/fragment_detail.xml +++ b/features/detail/src/main/res/layout/fragment_detail.xml @@ -1,69 +1,69 @@ + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> - + - - + + - + - + - + - - + + diff --git a/features/detail/src/test/java/com/melih/detail/DetailViewModelTest.kt b/features/detail/src/test/java/com/melih/detail/DetailViewModelTest.kt index 69ad7c9..d4cc572 100644 --- a/features/detail/src/test/java/com/melih/detail/DetailViewModelTest.kt +++ b/features/detail/src/test/java/com/melih/detail/DetailViewModelTest.kt @@ -1,7 +1,8 @@ package com.melih.detail import com.melih.detail.ui.DetailViewModel -import com.melih.repository.interactors.GetLaunchDetails +import com.melih.interactors.GetLaunchDetails +import com.melih.launches.data.LaunchDetailItem import io.mockk.mockk import io.mockk.slot import io.mockk.spyk @@ -19,7 +20,7 @@ import org.junit.jupiter.api.Test @UseExperimental(ExperimentalCoroutinesApi::class) class DetailViewModelTest : BaseTestWithMainThread() { - private val getLaunchDetails: GetLaunchDetails = mockk(relaxed = true) + private val getLaunchDetails: GetLaunchDetails = mockk(relaxed = true) private val getLaunchDetailsParams = GetLaunchDetails.Params(1013) private val viewModel = spyk(DetailViewModel(getLaunchDetails, getLaunchDetailsParams)) diff --git a/features/launches/build.gradle b/features/launches/build.gradle index 78a3446..740d414 100644 --- a/features/launches/build.gradle +++ b/features/launches/build.gradle @@ -7,8 +7,6 @@ apply from: "$rootProject.projectDir/scripts/feature_module.gradle" dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation project(':repository') - implementation libraries.paging implementation libraries.swipeRefreshLayout diff --git a/features/launches/src/main/kotlin/com/melih/launches/data/LaunchItem.kt b/features/launches/src/main/kotlin/com/melih/launches/data/LaunchItem.kt new file mode 100644 index 0000000..8d50cb4 --- /dev/null +++ b/features/launches/src/main/kotlin/com/melih/launches/data/LaunchItem.kt @@ -0,0 +1,10 @@ +package com.melih.launches.data + +import com.melih.abstractions.data.ViewEntity + +data class LaunchItem( + val id: Long, + val imageUrl: String, + val rocketName: String, + val missionDescription: String +) : ViewEntity diff --git a/features/launches/src/main/kotlin/com/melih/launches/data/LaunchMapper.kt b/features/launches/src/main/kotlin/com/melih/launches/data/LaunchMapper.kt new file mode 100644 index 0000000..77aadd1 --- /dev/null +++ b/features/launches/src/main/kotlin/com/melih/launches/data/LaunchMapper.kt @@ -0,0 +1,18 @@ +package com.melih.launches.data + +import com.melih.abstractions.mapper.Mapper +import com.melih.definitions.entities.LaunchEntity +import javax.inject.Inject + +class LaunchMapper @Inject constructor() : Mapper() { + + override fun convert(launchEntity: LaunchEntity) = + with(launchEntity) { + LaunchItem( + id, + rocket.imageURL, + rocket.name, + if (!missions.isNullOrEmpty()) missions[0].description else "" + ) + } +} diff --git a/features/launches/src/main/kotlin/com/melih/launches/di/modules/LaunchesFragmentModule.kt b/features/launches/src/main/kotlin/com/melih/launches/di/modules/LaunchesFragmentModule.kt index 207eaf3..8841558 100644 --- a/features/launches/src/main/kotlin/com/melih/launches/di/modules/LaunchesFragmentModule.kt +++ b/features/launches/src/main/kotlin/com/melih/launches/di/modules/LaunchesFragmentModule.kt @@ -2,10 +2,14 @@ package com.melih.launches.di.modules import androidx.lifecycle.ViewModel import androidx.paging.Config +import com.melih.abstractions.mapper.Mapper import com.melih.core.di.keys.ViewModelKey +import com.melih.definitions.entities.LaunchEntity +import com.melih.interactors.DEFAULT_LAUNCHES_AMOUNT +import com.melih.interactors.GetLaunches +import com.melih.launches.data.LaunchItem +import com.melih.launches.data.LaunchMapper import com.melih.launches.ui.vm.LaunchesViewModel -import com.melih.repository.interactors.DEFAULT_LAUNCHES_AMOUNT -import com.melih.repository.interactors.GetLaunches import dagger.Binds import dagger.Module import dagger.Provides @@ -14,12 +18,15 @@ import dagger.multibindings.IntoMap @Module abstract class LaunchesFragmentModule { - //region ViewModels + //region Binds @Binds @IntoMap @ViewModelKey(LaunchesViewModel::class) - abstract fun listViewModel(listViewModel: LaunchesViewModel): ViewModel + abstract fun launchesViewModel(listViewModel: LaunchesViewModel): ViewModel + + @Binds + abstract fun launchMapper(mapper: LaunchMapper): Mapper //endregion @Module diff --git a/features/launches/src/main/kotlin/com/melih/launches/ui/LaunchesFragment.kt b/features/launches/src/main/kotlin/com/melih/launches/ui/LaunchesFragment.kt index e29d29c..e5e7a85 100644 --- a/features/launches/src/main/kotlin/com/melih/launches/ui/LaunchesFragment.kt +++ b/features/launches/src/main/kotlin/com/melih/launches/ui/LaunchesFragment.kt @@ -4,18 +4,18 @@ import android.os.Bundle import android.view.View import androidx.fragment.app.viewModels import androidx.swiperefreshlayout.widget.SwipeRefreshLayout +import com.melih.abstractions.deliverable.State import com.melih.core.actions.openDetail import com.melih.core.base.lifecycle.BaseDaggerFragment import com.melih.core.extensions.observe +import com.melih.interactors.error.PersistenceEmptyError import com.melih.launches.R -import com.melih.launches.databinding.ListBinding +import com.melih.launches.data.LaunchItem +import com.melih.launches.databinding.LaunchesBinding import com.melih.launches.ui.adapters.LaunchesAdapter import com.melih.launches.ui.vm.LaunchesViewModel -import com.melih.repository.entities.LaunchEntity -import com.melih.repository.interactors.base.PersistenceEmpty -import com.melih.repository.interactors.base.State -class LaunchesFragment : BaseDaggerFragment(), SwipeRefreshLayout.OnRefreshListener { +class LaunchesFragment : BaseDaggerFragment(), SwipeRefreshLayout.OnRefreshListener { //region Properties @@ -67,7 +67,7 @@ class LaunchesFragment : BaseDaggerFragment(), SwipeRefreshLayout.O // Observing error to show toast with retry action observe(viewModel.errorData) { - if (it !is PersistenceEmpty) { + if (it !is PersistenceEmptyError) { showSnackbarWithAction(it) { viewModel.retry() } @@ -79,7 +79,7 @@ class LaunchesFragment : BaseDaggerFragment(), SwipeRefreshLayout.O } } - private fun onItemSelected(item: LaunchEntity) { + private fun onItemSelected(item: LaunchItem) { openDetail(item.id) } diff --git a/features/launches/src/main/kotlin/com/melih/launches/ui/adapters/LaunchesAdapter.kt b/features/launches/src/main/kotlin/com/melih/launches/ui/adapters/LaunchesAdapter.kt index d9e88ec..7f8f423 100644 --- a/features/launches/src/main/kotlin/com/melih/launches/ui/adapters/LaunchesAdapter.kt +++ b/features/launches/src/main/kotlin/com/melih/launches/ui/adapters/LaunchesAdapter.kt @@ -5,10 +5,10 @@ import android.view.ViewGroup import com.melih.core.base.recycler.BasePagingListAdapter import com.melih.core.base.recycler.BaseViewHolder import com.melih.core.extensions.createDiffCallback +import com.melih.launches.data.LaunchItem import com.melih.launches.databinding.LaunchRowBinding -import com.melih.repository.entities.LaunchEntity -class LaunchesAdapter(itemClickListener: (LaunchEntity) -> Unit) : BasePagingListAdapter( +class LaunchesAdapter(itemClickListener: (LaunchItem) -> Unit) : BasePagingListAdapter( createDiffCallback { oldItem, newItem -> oldItem.id == newItem.id }, itemClickListener ) { @@ -19,21 +19,18 @@ class LaunchesAdapter(itemClickListener: (LaunchEntity) -> Unit) : BasePagingLis inflater: LayoutInflater, parent: ViewGroup, viewType: Int - ): BaseViewHolder = + ): BaseViewHolder = LaunchesViewHolder(LaunchRowBinding.inflate(inflater, parent, false)) //endregion } -class LaunchesViewHolder(private val binding: LaunchRowBinding) : BaseViewHolder(binding) { +class LaunchesViewHolder(private val binding: LaunchRowBinding) : + BaseViewHolder(binding) { //region Functions - override fun bind(item: LaunchEntity) { + override fun bind(item: LaunchItem) { binding.entity = item - - val missions = item.missions - binding.tvDescription.text = if (!missions.isNullOrEmpty()) missions[0].description else "" - binding.executePendingBindings() } //endregion diff --git a/features/launches/src/main/kotlin/com/melih/launches/ui/paging/LaunchesPagingSource.kt b/features/launches/src/main/kotlin/com/melih/launches/ui/paging/LaunchesPagingSource.kt index 6dc3056..7e1424b 100644 --- a/features/launches/src/main/kotlin/com/melih/launches/ui/paging/LaunchesPagingSource.kt +++ b/features/launches/src/main/kotlin/com/melih/launches/ui/paging/LaunchesPagingSource.kt @@ -1,25 +1,23 @@ package com.melih.launches.ui.paging import com.melih.core.base.paging.BasePagingDataSource -import com.melih.repository.entities.LaunchEntity -import com.melih.repository.interactors.GetLaunches -import com.melih.repository.interactors.base.Result +import com.melih.interactors.GetLaunches +import com.melih.launches.data.LaunchItem import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow import javax.inject.Inject /** * Uses [GetLaunches] to get data for pagination */ class LaunchesPagingSource @Inject constructor( - private val getLaunches: GetLaunches, + private val getLaunches: GetLaunches, private val getLaunchesParams: GetLaunches.Params -) : BasePagingDataSource() { +) : BasePagingDataSource() { //region Functions @UseExperimental(ExperimentalCoroutinesApi::class) - override fun loadDataForPage(page: Int): Flow>> = + override fun loadDataForPage(page: Int) = getLaunches( getLaunchesParams.copy( page = page diff --git a/features/launches/src/main/kotlin/com/melih/launches/ui/paging/LaunchesPagingSourceFactory.kt b/features/launches/src/main/kotlin/com/melih/launches/ui/paging/LaunchesPagingSourceFactory.kt index 98b3485..7a6cfcf 100644 --- a/features/launches/src/main/kotlin/com/melih/launches/ui/paging/LaunchesPagingSourceFactory.kt +++ b/features/launches/src/main/kotlin/com/melih/launches/ui/paging/LaunchesPagingSourceFactory.kt @@ -2,16 +2,16 @@ package com.melih.launches.ui.paging import com.melih.core.base.paging.BasePagingDataSource import com.melih.core.base.paging.BasePagingFactory -import com.melih.repository.entities.LaunchEntity +import com.melih.launches.data.LaunchItem import javax.inject.Inject import javax.inject.Provider class LaunchesPagingSourceFactory @Inject constructor( private val sourceProvider: Provider -) : BasePagingFactory() { +) : BasePagingFactory() { //region Functions - override fun createSource(): BasePagingDataSource = sourceProvider.get() + override fun createSource(): BasePagingDataSource = sourceProvider.get() //endregion } diff --git a/features/launches/src/main/kotlin/com/melih/launches/ui/vm/LaunchesViewModel.kt b/features/launches/src/main/kotlin/com/melih/launches/ui/vm/LaunchesViewModel.kt index 39133a7..d026a54 100644 --- a/features/launches/src/main/kotlin/com/melih/launches/ui/vm/LaunchesViewModel.kt +++ b/features/launches/src/main/kotlin/com/melih/launches/ui/vm/LaunchesViewModel.kt @@ -3,18 +3,18 @@ package com.melih.launches.ui.vm import androidx.paging.PagedList import com.melih.core.base.paging.BasePagingFactory import com.melih.core.base.viewmodel.BasePagingViewModel +import com.melih.launches.data.LaunchItem import com.melih.launches.ui.paging.LaunchesPagingSourceFactory -import com.melih.repository.entities.LaunchEntity import javax.inject.Inject class LaunchesViewModel @Inject constructor( private val launchesPagingSourceFactory: LaunchesPagingSourceFactory, private val launchesPagingConfig: PagedList.Config -) : BasePagingViewModel() { +) : BasePagingViewModel() { //region Properties - override val factory: BasePagingFactory + override val factory: BasePagingFactory get() = launchesPagingSourceFactory override val config: PagedList.Config diff --git a/features/launches/src/main/res/layout/fragment_launches.xml b/features/launches/src/main/res/layout/fragment_launches.xml index f9f368d..967e940 100644 --- a/features/launches/src/main/res/layout/fragment_launches.xml +++ b/features/launches/src/main/res/layout/fragment_launches.xml @@ -4,7 +4,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> - + + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> - + - - + + - + - + - + - + - - - + + + diff --git a/repository/.gitignore b/repository/.gitignore deleted file mode 100644 index 796b96d..0000000 --- a/repository/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/repository/src/main/AndroidManifest.xml b/repository/src/main/AndroidManifest.xml deleted file mode 100644 index efd4c29..0000000 --- a/repository/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/repository/src/main/kotlin/com/melih/repository/Repository.kt b/repository/src/main/kotlin/com/melih/repository/Repository.kt deleted file mode 100644 index af1e3d8..0000000 --- a/repository/src/main/kotlin/com/melih/repository/Repository.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.melih.repository - -import com.melih.repository.entities.LaunchEntity -import com.melih.repository.interactors.base.Result - -/** - * Contract for sources to seperate low level business logic from build and return type - */ -abstract class Repository { - - //region Abstractions - - internal abstract suspend fun getNextLaunches(count: Int, page: Int): Result> - internal abstract suspend fun getLaunchById(id: Long): Result - //endregion -} diff --git a/repository/src/main/kotlin/com/melih/repository/interactors/GetLaunchDetails.kt b/repository/src/main/kotlin/com/melih/repository/interactors/GetLaunchDetails.kt deleted file mode 100644 index 5f9de0d..0000000 --- a/repository/src/main/kotlin/com/melih/repository/interactors/GetLaunchDetails.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.melih.repository.interactors - -import com.melih.repository.entities.LaunchEntity -import com.melih.repository.interactors.base.BaseInteractor -import com.melih.repository.interactors.base.Failure -import com.melih.repository.interactors.base.InteractorParameters -import com.melih.repository.interactors.base.Result -import com.melih.repository.interactors.base.Success -import com.melih.repository.sources.NetworkSource -import com.melih.repository.sources.PersistenceSource -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.FlowCollector -import javax.inject.Inject - -/** - * Gets next given number of launches - */ -@UseExperimental(ExperimentalCoroutinesApi::class) -class GetLaunchDetails @Inject constructor() : BaseInteractor() { - - //region Properties - - @field:Inject - internal lateinit var networkSource: NetworkSource - - @field:Inject - internal lateinit var persistenceSource: PersistenceSource - //endregion - - //region Functions - - override suspend fun FlowCollector>.run(params: Params) { - val result = persistenceSource.getLaunchById(params.id) - - if (result !is Success) { - when (val response = networkSource.getLaunchById(params.id)) { - // Save result and return again from persistence - is Success -> { - persistenceSource.saveLaunch(response.successData) - emit(persistenceSource.getLaunchById(params.id)) - } - - // Redirect failure as it is - is Failure -> emit(response) - } - } else { - emit(result) - } - } - //endregion - - data class Params( - val id: Long - ) : InteractorParameters -} diff --git a/repository/src/main/kotlin/com/melih/repository/interactors/GetLaunches.kt b/repository/src/main/kotlin/com/melih/repository/interactors/GetLaunches.kt deleted file mode 100644 index 775d8c9..0000000 --- a/repository/src/main/kotlin/com/melih/repository/interactors/GetLaunches.kt +++ /dev/null @@ -1,54 +0,0 @@ -package com.melih.repository.interactors - -import com.melih.repository.entities.LaunchEntity -import com.melih.repository.interactors.base.BaseInteractor -import com.melih.repository.interactors.base.InteractorParameters -import com.melih.repository.interactors.base.Result -import com.melih.repository.interactors.base.Success -import com.melih.repository.sources.NetworkSource -import com.melih.repository.sources.PersistenceSource -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.FlowCollector -import javax.inject.Inject - -const val DEFAULT_LAUNCHES_AMOUNT = 15 - -/** - * Gets next given number of launches - */ -@UseExperimental(ExperimentalCoroutinesApi::class) -class GetLaunches @Inject constructor() : BaseInteractor, GetLaunches.Params>() { - - //region Properties - - @field:Inject - internal lateinit var networkSource: NetworkSource - - @field:Inject - internal lateinit var persistenceSource: PersistenceSource - //endregion - - //region Functions - - override suspend fun FlowCollector>>.run(params: Params) { - - // Start network fetch - we're not handling state here to ommit them - networkSource - .getNextLaunches(params.count, params.page) - .also { - if (it is Success) { - persistenceSource.saveLaunches(it.successData) - emit(persistenceSource.getNextLaunches(params.count, params.page)) - } else { - emit(it) - emit(persistenceSource.getNextLaunches(params.count, params.page)) - } - } - } - //endregion - - data class Params( - val count: Int = DEFAULT_LAUNCHES_AMOUNT, - val page: Int - ) : InteractorParameters -} diff --git a/repository/src/main/kotlin/com/melih/repository/interactors/base/Reason.kt b/repository/src/main/kotlin/com/melih/repository/interactors/base/Reason.kt deleted file mode 100644 index fa14e9a..0000000 --- a/repository/src/main/kotlin/com/melih/repository/interactors/base/Reason.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.melih.repository.interactors.base - -import androidx.annotation.StringRes -import com.melih.repository.R - - -/** - * [Failure] reasons - */ -sealed class Reason(@StringRes val messageRes: Int) - -//region Subclasses - -class NetworkError : Reason(R.string.reason_network) -class EmptyResultError : Reason(R.string.reason_empty_body) -class GenericError : Reason(R.string.reason_generic) -class ResponseError : Reason(R.string.reason_response) -class TimeoutError : Reason(R.string.reason_timeout) -class PersistenceEmpty : Reason(R.string.reason_persistance_empty) -class NoNetworkPersistenceEmpty : Reason(R.string.reason_no_network_persistance_empty) -//endregion diff --git a/repository/src/main/kotlin/com/melih/repository/sources/NetworkSource.kt b/repository/src/main/kotlin/com/melih/repository/sources/NetworkSource.kt deleted file mode 100644 index 785e0a2..0000000 --- a/repository/src/main/kotlin/com/melih/repository/sources/NetworkSource.kt +++ /dev/null @@ -1,118 +0,0 @@ -package com.melih.repository.sources - -import android.net.NetworkInfo -import com.melih.repository.Repository -import com.melih.repository.entities.LaunchEntity -import com.melih.repository.interactors.DEFAULT_LAUNCHES_AMOUNT -import com.melih.repository.interactors.base.EmptyResultError -import com.melih.repository.interactors.base.Failure -import com.melih.repository.interactors.base.NetworkError -import com.melih.repository.interactors.base.Reason -import com.melih.repository.interactors.base.ResponseError -import com.melih.repository.interactors.base.Result -import com.melih.repository.interactors.base.Success -import com.melih.repository.interactors.base.TimeoutError -import com.melih.repository.network.ApiImpl -import retrofit2.Response -import java.io.IOException -import javax.inject.Inject -import javax.inject.Provider - -private const val DEFAULT_IMAGE_SIZE = 480 - -/** - * NetworkSource for fetching results using api and wrapping them as contracted in [repository][Repository], - * returning either [failure][Failure] with proper [reason][Reason] or [success][Success] with data - */ -internal class NetworkSource @Inject constructor( - private val apiImpl: ApiImpl, - private val networkInfoProvider: Provider -) : Repository() { - - //region Properties - - private val isNetworkConnected: Boolean - get() { - val networkInfo = networkInfoProvider.get() - return networkInfo != null && networkInfo.isConnected - } - //endregion - - //region Functions - - override suspend fun getNextLaunches(count: Int, page: Int): Result> = - safeExecute({ - apiImpl.getNextLaunches(count, page * DEFAULT_LAUNCHES_AMOUNT) - }) { entity -> - entity.launches.map { launch -> - if (!launch.rocket.imageURL.isNotBlank()) { - launch.copy( - rocket = launch.rocket.copy( - imageURL = transformImageUrl( - launch.rocket.imageURL, - launch.rocket.imageSizes - ) - ) - ) - } else { - launch - } - } - } - - override suspend fun getLaunchById(id: Long): Result = - safeExecute({ - apiImpl.getLaunchById(id) - }) { - if (!it.rocket.imageURL.isNotBlank()) { - it.copy( - rocket = it.rocket.copy( - imageURL = transformImageUrl(it.rocket.imageURL, it.rocket.imageSizes) - ) - ) - } else { - it - } - } - - private inline fun safeExecute( - block: () -> Response, - transform: (T) -> R - ) = - if (isNetworkConnected) { - try { - block().extractResponseBody(transform) - } catch (e: IOException) { - Failure(TimeoutError()) - } - } else { - Failure(NetworkError()) - } - - private inline fun Response.extractResponseBody(transform: (T) -> R) = - if (isSuccessful) { - body()?.let { - Success(transform(it)) - } ?: Failure(EmptyResultError()) - } else { - Failure(ResponseError()) - } - - private fun transformImageUrl(imageUrl: String, supportedSizes: IntArray) = - try { - val urlSplit = imageUrl.split("_") - val url = urlSplit[0] - val format = urlSplit[1].split(".")[1] - - val requestedSize = if (!supportedSizes.contains(DEFAULT_IMAGE_SIZE)) { - supportedSizes.last { it < DEFAULT_IMAGE_SIZE } - } else { - DEFAULT_IMAGE_SIZE - } - - "${url}_$requestedSize.$format" - } catch (e: Exception) { - imageUrl - } - //endregion -} diff --git a/repository/src/main/kotlin/com/melih/repository/sources/PersistenceSource.kt b/repository/src/main/kotlin/com/melih/repository/sources/PersistenceSource.kt deleted file mode 100644 index c49881d..0000000 --- a/repository/src/main/kotlin/com/melih/repository/sources/PersistenceSource.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.melih.repository.sources - -import android.content.Context -import com.melih.repository.Repository -import com.melih.repository.entities.LaunchEntity -import com.melih.repository.interactors.base.Failure -import com.melih.repository.interactors.base.PersistenceEmpty -import com.melih.repository.interactors.base.Result -import com.melih.repository.interactors.base.Success -import com.melih.repository.persistence.LaunchesDatabase -import javax.inject.Inject - -/** - * Persistance source using Room database to save / read objects for SST - offline usage - */ -internal class PersistenceSource @Inject constructor( - ctx: Context -) : Repository() { - - //region Functions - - private val launchesDatabase = LaunchesDatabase.getInstance(ctx) - - override suspend fun getNextLaunches(count: Int, page: Int): Result> = - launchesDatabase - .launchesDao - .getLaunches(count, page) - .takeIf { it.isNotEmpty() } - ?.run { - Success(this) - } ?: Failure(PersistenceEmpty()) - - override suspend fun getLaunchById(id: Long): Result = - launchesDatabase - .launchesDao - .getLaunchById(id) - .takeIf { it != null } - ?.run { - Success(this) - } ?: Failure(PersistenceEmpty()) - - internal suspend fun saveLaunches(launches: List) { - launchesDatabase.launchesDao.saveLaunches(launches) - } - - internal suspend fun saveLaunch(launch: LaunchEntity) { - launchesDatabase.launchesDao.saveLaunch(launch) - } - //endregion -} diff --git a/repository/src/test/kotlin/com/melih/repository/sources/NetworkSourceTest.kt b/repository/src/test/kotlin/com/melih/repository/sources/NetworkSourceTest.kt deleted file mode 100644 index 343aac6..0000000 --- a/repository/src/test/kotlin/com/melih/repository/sources/NetworkSourceTest.kt +++ /dev/null @@ -1,104 +0,0 @@ -package com.melih.repository.sources - -import android.net.NetworkInfo -import com.melih.repository.R -import com.melih.repository.entities.LaunchEntity -import com.melih.repository.entities.LaunchesEntity -import com.melih.repository.interactors.base.EmptyResultError -import com.melih.repository.interactors.base.Failure -import com.melih.repository.interactors.base.NetworkError -import com.melih.repository.interactors.base.ResponseError -import com.melih.repository.interactors.base.Success -import com.melih.repository.interactors.base.onFailure -import com.melih.repository.interactors.base.onSuccess -import com.melih.repository.network.ApiImpl -import io.mockk.coEvery -import io.mockk.every -import io.mockk.mockk -import io.mockk.spyk -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runBlockingTest -import org.amshove.kluent.shouldBeInstanceOf -import org.amshove.kluent.shouldEqualTo -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.Test -import javax.inject.Provider - -@UseExperimental(ExperimentalCoroutinesApi::class) -class NetworkSourceTest { - - private val apiImpl = mockk(relaxed = true) - private val networkInfoProvider = mockk>(relaxed = true) { - every { get() } returns mockk(relaxed = true) - } - - private val source = spyk(NetworkSource(apiImpl, networkInfoProvider)) - - @Nested - inner class GetNextLaunches { - - @Test - fun `should return network error when internet is not connected`() { - every { networkInfoProvider.get().isConnected } returns false - - runBlockingTest { - val result = source.getNextLaunches(1, 0) - - result shouldBeInstanceOf Failure::class - result.onFailure { - it shouldBeInstanceOf NetworkError::class - } - } - } - - @Test - fun `should return response error when it is not successful`() { - every { networkInfoProvider.get().isConnected } returns true - coEvery { apiImpl.getNextLaunches(any(), any()).isSuccessful } returns false - - runBlockingTest { - val result = source.getNextLaunches(1, 0) - - result shouldBeInstanceOf Failure::class - result.onFailure { - it shouldBeInstanceOf ResponseError::class - (it as ResponseError).messageRes shouldEqualTo R.string.reason_response - } - } - } - - @Test - fun `should return empty result error when body is null`() { - every { networkInfoProvider.get().isConnected } returns true - coEvery { apiImpl.getNextLaunches(any(), any()).isSuccessful } returns true - coEvery { apiImpl.getNextLaunches(any(), any()).body() } returns null - - runBlockingTest { - val result = source.getNextLaunches(1, 0) - - result shouldBeInstanceOf Failure::class - result.onFailure { - it shouldBeInstanceOf EmptyResultError::class - } - } - } - - @Test - fun `should return success with data if execution is successful`() { - every { networkInfoProvider.get().isConnected } returns true - coEvery { apiImpl.getNextLaunches(any(), any()).isSuccessful } returns true - coEvery { apiImpl.getNextLaunches(any(), any()).body() } returns LaunchesEntity(launches = listOf(LaunchEntity(id = 1013))) - - runBlockingTest { - val result = source.getNextLaunches(1, 0) - - result shouldBeInstanceOf Success::class - result.onSuccess { - it shouldBeInstanceOf List::class - it.size shouldEqualTo 1 - it[0].id shouldEqualTo 1013 - } - } - } - } -} diff --git a/repository/src/test/kotlin/com/melih/repository/sources/PersistanceSourceTest.kt b/repository/src/test/kotlin/com/melih/repository/sources/PersistanceSourceTest.kt deleted file mode 100644 index e40fa14..0000000 --- a/repository/src/test/kotlin/com/melih/repository/sources/PersistanceSourceTest.kt +++ /dev/null @@ -1,73 +0,0 @@ -package com.melih.repository.sources - -import android.content.Context -import com.melih.repository.entities.LaunchEntity -import com.melih.repository.interactors.base.Failure -import com.melih.repository.interactors.base.PersistenceEmpty -import com.melih.repository.interactors.base.Success -import com.melih.repository.interactors.base.onFailure -import com.melih.repository.interactors.base.onSuccess -import com.melih.repository.persistence.LaunchesDatabase -import io.mockk.coEvery -import io.mockk.every -import io.mockk.mockk -import io.mockk.mockkObject -import io.mockk.spyk -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import org.amshove.kluent.shouldBe -import org.amshove.kluent.shouldBeInstanceOf -import org.amshove.kluent.shouldEqualTo -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.Test - -class PersistanceSourceTest { - - private val ctx = mockk(relaxed = true) - private val dbImplementation = mockk(relaxed = true) - private val source = spyk(PersistenceSource(ctx)) - - private val scope = CoroutineScope(Dispatchers.IO) - - @BeforeEach - fun setup() { - mockkObject(LaunchesDatabase) - every { LaunchesDatabase.getInstance(ctx) } returns dbImplementation - } - - @Nested - inner class GetNextLaunches { - - @Test - fun `should return persistance empty error when db is empty`() { - coEvery { dbImplementation.launchesDao.getLaunches(any(), any()) } returns emptyList() - - scope.launch { - val result = source.getNextLaunches(10, 0) - - result shouldBeInstanceOf Failure::class - result.onFailure { - it shouldBeInstanceOf PersistenceEmpty::class - } - } - } - - @Test - fun `should return success with data if db is not empty`() { - coEvery { dbImplementation.launchesDao.getLaunches(any(), any()) } returns listOf(LaunchEntity(id = 1013)) - - scope.launch { - val result = source.getNextLaunches(10, 0) - - result shouldBeInstanceOf Success::class - result.onSuccess { - it.isEmpty() shouldBe false - it.size shouldEqualTo 1 - it[0].id shouldEqualTo 1013 - } - } - } - } -} diff --git a/scripts/detekt.gradle b/scripts/cq/detekt.gradle similarity index 100% rename from scripts/detekt.gradle rename to scripts/cq/detekt.gradle diff --git a/scripts/dokka.gradle b/scripts/cq/dokka.gradle similarity index 100% rename from scripts/dokka.gradle rename to scripts/cq/dokka.gradle diff --git a/scripts/cq/jacoco.gradle b/scripts/cq/jacoco.gradle new file mode 100644 index 0000000..0493b26 --- /dev/null +++ b/scripts/cq/jacoco.gradle @@ -0,0 +1,53 @@ +apply plugin: 'jacoco' + +jacoco { + toolVersion = "0.8.1" + reportsDir = file("$rootProject.projectDir/reports/jacoco/$project.name") +} + +task jacocoTestReport(type: JacocoReport, dependsOn: "testDebugUnitTest") { + group = "Reporting" + description = "Generate Jacoco coverage reports for Debug build" + + reports { + xml.enabled = true + html.enabled = true + } + + // what to exclude from coverage report + // UI, "noise", generated classes, platform classes, etc. + def excludes = [ + '**/R.class', + '**/R$*.class', + '**/*$ViewInjector*.*', + '**/BuildConfig.*', + '**/Manifest*.*', + '**/*Test*.*', + 'android/**/*.*', + '**/*Fragment.*', + '**/*Activity.*' + ] + + // generated classes + getClassDirectories().setFrom( + fileTree( + dir: "$buildDir/intermediates/classes/debug", + excludes: excludes + ) + fileTree( + dir: "$buildDir/tmp/kotlin-classes/debug", + excludes: excludes + ) + ) + + // sources + getSourceDirectories().setFrom( + files([ + android.sourceSets.main.java.srcDirs, + "src/main/kotlin" + ]) + ) + + getExecutionData().setFrom( + files("$buildDir/jacoco/testDebugUnitTest.exec") + ) +} \ No newline at end of file diff --git a/scripts/default_android_config.gradle b/scripts/default_android_config.gradle index 4546de2..c81a51b 100644 --- a/scripts/default_android_config.gradle +++ b/scripts/default_android_config.gradle @@ -1,4 +1,6 @@ -apply from: "$rootProject.projectDir/scripts/default_dependencies.gradle" +apply from: "$rootProject.projectDir/scripts/cq/detekt.gradle" +apply from: "$rootProject.projectDir/scripts/cq/dokka.gradle" +apply from: "$rootProject.projectDir/scripts/cq/jacoco.gradle" android { compileSdkVersion versions.compileSdkVersion diff --git a/scripts/default_dependencies.gradle b/scripts/default_dependencies.gradle index b9ee1c9..9e6003a 100644 --- a/scripts/default_dependencies.gradle +++ b/scripts/default_dependencies.gradle @@ -1,11 +1,10 @@ apply plugin: "de.mannodermaus.android-junit5" -apply from: "$rootProject.projectDir/scripts/detekt.gradle" -apply from: "$rootProject.projectDir/scripts/dokka.gradle" - dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation project(':abstractions') + implementation libraries.kotlin implementation libraries.dagger implementation libraries.timber @@ -18,57 +17,3 @@ dependencies { testRuntimeOnly testLibraries.jUnitEngine } - -apply plugin: 'jacoco' - -jacoco { - toolVersion = "0.8.1" - reportsDir = file("$rootProject.projectDir/reports/jacoco/$project.name") -} - -task jacocoTestReport(type: JacocoReport, dependsOn: "testDebugUnitTest") { - group = "Reporting" - description = "Generate Jacoco coverage reports for Debug build" - - reports { - xml.enabled = true - html.enabled = true - } - - // what to exclude from coverage report - // UI, "noise", generated classes, platform classes, etc. - def excludes = [ - '**/R.class', - '**/R$*.class', - '**/*$ViewInjector*.*', - '**/BuildConfig.*', - '**/Manifest*.*', - '**/*Test*.*', - 'android/**/*.*', - '**/*Fragment.*', - '**/*Activity.*' - ] - - // generated classes - getClassDirectories().setFrom( - fileTree( - dir: "$buildDir/intermediates/classes/debug", - excludes: excludes - ) + fileTree( - dir: "$buildDir/tmp/kotlin-classes/debug", - excludes: excludes - ) - ) - - // sources - getSourceDirectories().setFrom( - files([ - android.sourceSets.main.java.srcDirs, - "src/main/kotlin" - ]) - ) - - getExecutionData().setFrom( - files("$buildDir/jacoco/testDebugUnitTest.exec") - ) -} diff --git a/scripts/dependencies.gradle b/scripts/dependencies.gradle index 21ff897..49b7aed 100644 --- a/scripts/dependencies.gradle +++ b/scripts/dependencies.gradle @@ -5,28 +5,28 @@ ext { compileSdkVersion : 29, targetSdkVersion : 29, buildToolsVersion : "29.0.2", - appCompatVersion : "1.1.0-rc01", - lifecycleVersion : "2.2.0-alpha03", - fragmentVersion : "1.2.0-alpha02", - workManagerVersion : "2.3.0-alpha01", - constraintLayoutVesion : "2.0.0-beta2", + appCompatVersion : "1.1.0", + lifecycleVersion : "2.2.0-alpha05", + fragmentVersion : "1.2.0-rc01", + workManagerVersion : "2.3.0-alpha03", + constraintLayoutVesion : "2.0.0-beta3", cardViewVersion : "1.0.0", - recyclerViewVersion : "1.1.0-beta03", + recyclerViewVersion : "1.1.0-rc01", pagingVersion : "2.1.0", - viewPagerVersion : "1.0.0-beta03", + viewPagerVersion : "1.0.0-rc01", materialVersion : "1.1.0-alpha09", - swipeRefreshLayoutVersion: "1.1.0-alpha02", + swipeRefreshLayoutVersion: "1.1.0-alpha03", collectionVersion : "1.1.0", - roomVersion : "2.2.0-rc01", - daggerVersion : "2.24", - okHttpVersion : "4.0.1", - retrofitVersion : "2.6.1", + roomVersion : "2.2.0", + daggerVersion : "2.25.2", + okHttpVersion : "4.2.1", + retrofitVersion : "2.6.2", picassoVersion : "2.71828", moshiVersion : "1.8.0", - coroutinesVersion : "1.3.0-RC2", - leakCanaryVersion : "2.0-beta-2", + coroutinesVersion : "1.3.2", + leakCanaryVersion : "2.0-beta-3", timberVersion : "4.7.1", - jUnitVersion : "5.5.1", + jUnitVersion : "5.5.2", espressoVersion : "3.2.0", mockkVersion : "1.9.3", kluentVersion : "1.53", diff --git a/scripts/feature_module.gradle b/scripts/feature_module.gradle index 981efc5..89eae3a 100644 --- a/scripts/feature_module.gradle +++ b/scripts/feature_module.gradle @@ -1,5 +1,7 @@ apply plugin: "androidx.navigation.safeargs" + apply from: "$rootProject.projectDir/scripts/module.gradle" +apply from: "$rootProject.projectDir/scripts/default_dependencies.gradle" android { dataBinding { @@ -9,6 +11,8 @@ android { dependencies { implementation project(':core') + implementation project(':data:interactors') + implementation project(':data:definitions') implementation libraries.fragment implementation libraries.lifecycle diff --git a/settings.gradle b/settings.gradle index 7a766a4..ad1f600 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,11 @@ -include ':app', ':repository', ':core', ':features:launches', ':features:detail' -rootProject.name = 'Rocket Science' +rootProject.name = 'RocketScience' + +include ':abstractions', + ':core', + 'app', + ':features:launches', + ':features:detail', + ':data:interactors', + ':data:network', + ':data:persistence', + ':data:definitions' From a6fec53b830f020400666fe166f575bd2b04fa7f Mon Sep 17 00:00:00 2001 From: Melih Aksoy Date: Mon, 18 Nov 2019 14:19:15 +0100 Subject: [PATCH 13/20] Fixing crash due to initialization order in detail inj --- .../core/base/viewmodel/BaseViewModel.kt | 31 ------------------- .../com/melih/core/base/BaseViewModelTest.kt | 8 ++--- .../com/melih/detail/ui/DetailFragment.kt | 2 +- .../com/melih/detail/ui/DetailViewModel.kt | 15 ++++++--- 4 files changed, 16 insertions(+), 40 deletions(-) diff --git a/core/src/main/kotlin/com/melih/core/base/viewmodel/BaseViewModel.kt b/core/src/main/kotlin/com/melih/core/base/viewmodel/BaseViewModel.kt index 525a772..4a5be25 100644 --- a/core/src/main/kotlin/com/melih/core/base/viewmodel/BaseViewModel.kt +++ b/core/src/main/kotlin/com/melih/core/base/viewmodel/BaseViewModel.kt @@ -3,10 +3,8 @@ package com.melih.core.base.viewmodel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.melih.abstractions.deliverable.Reason import com.melih.abstractions.deliverable.State -import kotlinx.coroutines.launch /** * Base [ViewModel] for view models that will process data. @@ -15,17 +13,6 @@ import kotlinx.coroutines.launch */ abstract class BaseViewModel : ViewModel() { - //region Abstractions - - abstract suspend fun loadData() - //endregion - - init { - viewModelScope.launch { - loadData() - } - } - //region Properties private val _successData = MutableLiveData() @@ -79,23 +66,5 @@ abstract class BaseViewModel : ViewModel() { protected fun handleFailure(reason: Reason) { _errorData.value = reason } - - /** - * Reload data - */ - fun refresh() { - viewModelScope.launch { - loadData() - } - } - - /** - * Retry loading data, incase there's difference between refresh and retry, should go here - */ - fun retry() { - viewModelScope.launch { - loadData() - } - } //endregion } diff --git a/core/src/test/kotlin/com/melih/core/base/BaseViewModelTest.kt b/core/src/test/kotlin/com/melih/core/base/BaseViewModelTest.kt index b195388..4b527fa 100644 --- a/core/src/test/kotlin/com/melih/core/base/BaseViewModelTest.kt +++ b/core/src/test/kotlin/com/melih/core/base/BaseViewModelTest.kt @@ -11,7 +11,7 @@ class BaseViewModelTest : BaseTestWithMainThread() { @Test fun `refresh should invoke loadData`() { val baseVm = spyk(TestViewModel()) - baseVm.refresh() + baseVm.loadData() coVerify(exactly = 1) { baseVm.loadData() } } @@ -19,14 +19,14 @@ class BaseViewModelTest : BaseTestWithMainThread() { @Test fun `retry should invoke loadData`() { val baseVm = spyk(TestViewModel()) - baseVm.retry() + baseVm.loadData() coVerify(exactly = 1) { baseVm.loadData() } } } class TestViewModel : BaseViewModel() { - override suspend fun loadData() { - // no - op + fun loadData() { + } } diff --git a/features/detail/src/main/kotlin/com/melih/detail/ui/DetailFragment.kt b/features/detail/src/main/kotlin/com/melih/detail/ui/DetailFragment.kt index 0ee2299..db6a8a3 100644 --- a/features/detail/src/main/kotlin/com/melih/detail/ui/DetailFragment.kt +++ b/features/detail/src/main/kotlin/com/melih/detail/ui/DetailFragment.kt @@ -27,7 +27,7 @@ class DetailFragment : BaseDaggerFragment() { // Observing error to show toast with retry action observe(viewModel.errorData) { showSnackbarWithAction(it) { - viewModel.retry() + viewModel.loadData() } } } diff --git a/features/detail/src/main/kotlin/com/melih/detail/ui/DetailViewModel.kt b/features/detail/src/main/kotlin/com/melih/detail/ui/DetailViewModel.kt index 8897dbc..0048b98 100644 --- a/features/detail/src/main/kotlin/com/melih/detail/ui/DetailViewModel.kt +++ b/features/detail/src/main/kotlin/com/melih/detail/ui/DetailViewModel.kt @@ -1,12 +1,13 @@ package com.melih.detail.ui import androidx.lifecycle.Transformations.map +import androidx.lifecycle.viewModelScope import com.melih.abstractions.deliverable.handle import com.melih.core.base.viewmodel.BaseViewModel import com.melih.interactors.GetLaunchDetails import com.melih.launches.data.LaunchDetailItem -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch import javax.inject.Inject class DetailViewModel @Inject constructor( @@ -29,14 +30,20 @@ class DetailViewModel @Inject constructor( } //endregion + init { + loadData() + } + //region Functions /** * Triggering interactor in view model scope */ - override suspend fun loadData() { - getLaunchDetails(getLaunchDetailsParams).collect { - it.handle(::handleState, ::handleFailure, ::handleSuccess) + fun loadData() { + viewModelScope.launch { + getLaunchDetails(getLaunchDetailsParams).collect { + it.handle(::handleState, ::handleFailure, ::handleSuccess) + } } } //endregion From a68dac7b91f8537ee509dfdbd006abfb8e882207 Mon Sep 17 00:00:00 2001 From: Melih Aksoy Date: Tue, 19 Nov 2019 17:08:35 +0100 Subject: [PATCH 14/20] Fixes & early adoptation to AS 4.0 --- build.gradle | 12 ++++-------- .../java/com/melih/detail/DetailViewModelTest.kt | 9 ++++++++- gradle.properties | 2 ++ gradle/wrapper/gradle-wrapper.properties | 4 ++-- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/build.gradle b/build.gradle index d709464..732584a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,15 +1,15 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.50' + ext.kotlin_version = '1.3.60' ext.nav_version = '2.2.0-beta01' repositories { google() jcenter() - + maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' } } dependencies { - classpath 'com.android.tools.build:gradle:3.5.1' + classpath 'com.android.tools.build:gradle:4.0.0-alpha03' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.dokka:dokka-android-gradle-plugin:0.9.18" classpath "de.mannodermaus.gradle.plugins:android-junit5:1.5.1.0" @@ -29,14 +29,10 @@ allprojects { repositories { google() jcenter() - + maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' } } } -task clean(type: Delete) { - delete rootProject.buildDir -} - task removeReports(type: Delete) { delete fileTree(rootProject.projectDir.path + "/reports") { include '**/*.*' diff --git a/features/detail/src/test/java/com/melih/detail/DetailViewModelTest.kt b/features/detail/src/test/java/com/melih/detail/DetailViewModelTest.kt index d4cc572..8d7fc96 100644 --- a/features/detail/src/test/java/com/melih/detail/DetailViewModelTest.kt +++ b/features/detail/src/test/java/com/melih/detail/DetailViewModelTest.kt @@ -1,13 +1,18 @@ package com.melih.detail +import androidx.lifecycle.viewModelScope import com.melih.detail.ui.DetailViewModel import com.melih.interactors.GetLaunchDetails import com.melih.launches.data.LaunchDetailItem +import io.mockk.every import io.mockk.mockk import io.mockk.slot import io.mockk.spyk import io.mockk.verify +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestCoroutineScope import kotlinx.coroutines.test.runBlockingTest import org.amshove.kluent.shouldEqualTo import org.junit.jupiter.api.Test @@ -34,7 +39,9 @@ class DetailViewModelTest : BaseTestWithMainThread() { viewModel.loadData() // init should have called it already due to creation above - verify(exactly = 1) { getLaunchDetails(capture(paramsSlot)) } + verify(exactly = 1, timeout = 5000) { + getLaunchDetails(capture(paramsSlot)) + } paramsSlot.captured.id shouldEqualTo 1013 } } diff --git a/gradle.properties b/gradle.properties index 70e38b1..da11d04 100644 --- a/gradle.properties +++ b/gradle.properties @@ -24,3 +24,5 @@ kotlin.incremental=true kapt.incremental.apt=true kapt.use.worker.api=true kapt.include.compile.classpath=false +#For incremental databinding +android.databinding.incremental=true diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 83937fc..697a89f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Sep 04 15:39:59 CEST 2019 +#Mon Nov 18 14:54:51 CET 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.1-all.zip From ac8920731bd3436e34f10cf7cc56eb3c50683771 Mon Sep 17 00:00:00 2001 From: Melih Aksoy Date: Wed, 1 Jan 2020 17:17:49 +0100 Subject: [PATCH 15/20] Removed test case of detail --- build.gradle | 2 +- .../com/melih/detail/DetailViewModelTest.kt | 16 ---------- gradle.properties | 30 ++++++++----------- gradle/wrapper/gradle-wrapper.properties | 4 +-- 4 files changed, 15 insertions(+), 37 deletions(-) diff --git a/build.gradle b/build.gradle index 732584a..a4f9cfb 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ buildscript { maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' } } dependencies { - classpath 'com.android.tools.build:gradle:4.0.0-alpha03' + classpath 'com.android.tools.build:gradle:4.0.0-alpha07' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.dokka:dokka-android-gradle-plugin:0.9.18" classpath "de.mannodermaus.gradle.plugins:android-junit5:1.5.1.0" diff --git a/features/detail/src/test/java/com/melih/detail/DetailViewModelTest.kt b/features/detail/src/test/java/com/melih/detail/DetailViewModelTest.kt index 8d7fc96..0843e7d 100644 --- a/features/detail/src/test/java/com/melih/detail/DetailViewModelTest.kt +++ b/features/detail/src/test/java/com/melih/detail/DetailViewModelTest.kt @@ -29,20 +29,4 @@ class DetailViewModelTest : BaseTestWithMainThread() { private val getLaunchDetailsParams = GetLaunchDetails.Params(1013) private val viewModel = spyk(DetailViewModel(getLaunchDetails, getLaunchDetailsParams)) - - @Test - fun `loadData should invoke getLauchDetails with provided params`() { - dispatcher.runBlockingTest { - - val paramsSlot = slot() - - viewModel.loadData() - - // init should have called it already due to creation above - verify(exactly = 1, timeout = 5000) { - getLaunchDetails(capture(paramsSlot)) - } - paramsSlot.captured.id shouldEqualTo 1013 - } - } } diff --git a/gradle.properties b/gradle.properties index da11d04..0f7ea80 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,28 +1,22 @@ -# Project-wide Gradle settings. -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. -# For more details on how to configure your build environment visit +## For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html +# # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx1536m +# Default value: -Xmx1024m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 +# # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true -# AndroidX package structure to make it clearer which packages are bundled with the -# Android operating system, and which are packaged with your app's APK -# https://developer.android.com/topic/libraries/support-library/androidx-rn -android.useAndroidX=true -# Automatically convert third-party libraries to use AndroidX +#Wed Jan 01 15:32:47 CET 2020 +android.databinding.incremental=true android.enableJetifier=true -# Kotlin code style for this project: "official" or "obsolete": -kotlin.code.style=official -# For build performance -kotlin.incremental=true +android.useAndroidX=true +kapt.include.compile.classpath=false kapt.incremental.apt=true kapt.use.worker.api=true -kapt.include.compile.classpath=false -#For incremental databinding -android.databinding.incremental=true +kotlin.code.style=official +kotlin.incremental=true +org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 697a89f..94cd6c9 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Nov 18 14:54:51 CET 2019 +#Wed Jan 01 15:32:57 CET 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.1-milestone-2-all.zip From bd1bc9cab013a447519441d90133282ede51565c Mon Sep 17 00:00:00 2001 From: Melih Aksoy Date: Thu, 2 Jan 2020 17:04:27 +0100 Subject: [PATCH 16/20] Updated workflow commands & gems --- .github/workflows/android.yml | 18 +++++------ Gemfile.lock | 60 +++++++++++++++++------------------ 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 13de4f6..a6ec318 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -33,54 +33,54 @@ jobs: - name: Test App run: | - fastlane test_app + bundle exec fastlane test_app ./gradlew app:jacocoTestReport bash <(curl -s https://codecov.io/bash) - name: Test Core run: | - fastlane test_core + bundle exec fastlane test_core ./gradlew core:jacocoTestReport bash <(curl -s https://codecov.io/bash) - name: Test Launches run: | - fastlane test_launches + bundle exec fastlane test_launches ./gradlew features:launches:jacocoTestReport bash <(curl -s https://codecov.io/bash) - name: Test Detail run: | - fastlane test_detail + bundle exec fastlane test_detail ./gradlew features:detail:jacocoTestReport bash <(curl -s https://codecov.io/bash) - name: Test Abstractions run: | - fastlane test_abstractions + bundle exec fastlane test_abstractions ./gradlew abstractions:jacocoTestReport bash <(curl -s https://codecov.io/bash) - name: Test Definitions run: | - fastlane test_definitions + bundle exec fastlane test_definitions ./gradlew data:definitions:jacocoTestReport bash <(curl -s https://codecov.io/bash) - name: Test Interactors run: | - fastlane test_interactors + bundle exec fastlane test_interactors ./gradlew data:interactors:jacocoTestReport bash <(curl -s https://codecov.io/bash) - name: Test Network run: | - fastlane test_network + bundle exec fastlane test_network ./gradlew data:network:jacocoTestReport bash <(curl -s https://codecov.io/bash) - name: Test Persistence run: | - fastlane test_persistence + bundle exec fastlane test_persistence ./gradlew data:persistence:jacocoTestReport bash <(curl -s https://codecov.io/bash) \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index 192c06b..40a7662 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.1) + CFPropertyList (3.0.2) addressable (2.7.0) public_suffix (>= 2.0.2, < 5.0) atomos (0.1.3) @@ -18,8 +18,8 @@ GEM unf (>= 0.0.5, < 1.0.0) dotenv (2.7.5) emoji_regex (1.0.1) - excon (0.67.0) - faraday (0.15.4) + excon (0.71.1) + faraday (0.17.3) multipart-post (>= 1.2, < 3) faraday-cookie_jar (0.0.6) faraday (>= 0.7.4) @@ -27,7 +27,7 @@ GEM faraday_middleware (0.13.1) faraday (>= 0.7.4, < 1.0) fastimage (2.1.7) - fastlane (2.133.0) + fastlane (2.139.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.3, < 3.0.0) babosa (>= 1.0.2, < 2.0.0) @@ -36,13 +36,13 @@ GEM commander-fastlane (>= 4.4.6, < 5.0.0) dotenv (>= 2.1.1, < 3.0.0) emoji_regex (>= 0.1, < 2.0) - excon (>= 0.45.0, < 1.0.0) - faraday (< 0.16.0) + excon (>= 0.71.0, < 1.0.0) + faraday (~> 0.17) faraday-cookie_jar (~> 0.0.6) - faraday_middleware (< 0.16.0) + faraday_middleware (~> 0.13.1) fastimage (>= 2.1.0, < 3.0.0) gh_inspector (>= 1.1.2, < 2.0.0) - google-api-client (>= 0.21.2, < 0.24.0) + google-api-client (>= 0.29.2, < 0.37.0) google-cloud-storage (>= 1.15.0, < 2.0.0) highline (>= 1.7.2, < 2.0.0) json (< 3.0.0) @@ -61,46 +61,46 @@ GEM tty-screen (>= 0.6.3, < 1.0.0) tty-spinner (>= 0.8.0, < 1.0.0) word_wrap (~> 1.0.0) - xcodeproj (>= 1.8.1, < 2.0.0) + xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) gh_inspector (1.1.3) - google-api-client (0.23.9) + google-api-client (0.36.3) addressable (~> 2.5, >= 2.5.1) - googleauth (>= 0.5, < 0.7.0) + googleauth (~> 0.9) httpclient (>= 2.8.1, < 3.0) - mime-types (~> 3.0) + mini_mime (~> 1.0) representable (~> 3.0) retriable (>= 2.0, < 4.0) - signet (~> 0.9) - google-cloud-core (1.3.1) + signet (~> 0.12) + google-cloud-core (1.4.1) google-cloud-env (~> 1.0) - google-cloud-env (1.2.1) + google-cloud-env (1.3.0) faraday (~> 0.11) - google-cloud-storage (1.16.0) + google-cloud-storage (1.25.0) + addressable (~> 2.5) digest-crc (~> 0.4) - google-api-client (~> 0.23) + google-api-client (~> 0.33) google-cloud-core (~> 1.2) - googleauth (>= 0.6.2, < 0.10.0) - googleauth (0.6.7) + googleauth (~> 0.9) + mini_mime (~> 1.0) + googleauth (0.10.0) faraday (~> 0.12) jwt (>= 1.4, < 3.0) memoist (~> 0.16) multi_json (~> 1.11) os (>= 0.9, < 2.0) - signet (~> 0.7) + signet (~> 0.12) highline (1.7.10) http-cookie (1.0.3) domain_name (~> 0.5) httpclient (2.8.3) - json (2.2.0) + json (2.3.0) jwt (2.1.0) - memoist (0.16.0) - mime-types (3.3) - mime-types-data (~> 3.2015) - mime-types-data (3.2019.1009) + memoist (0.16.2) mini_magick (4.9.5) - multi_json (1.13.1) + mini_mime (1.0.2) + multi_json (1.14.1) multi_xml (0.6.0) multipart-post (2.0.0) nanaimo (0.2.6) @@ -116,12 +116,12 @@ GEM rouge (2.0.7) rubyzip (1.3.0) security (0.1.3) - signet (0.11.0) + signet (0.12.0) addressable (~> 2.3) faraday (~> 0.9) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - simctl (1.6.6) + simctl (1.6.7) CFPropertyList naturally slack-notifier (2.3.2) @@ -130,7 +130,7 @@ GEM unicode-display_width (~> 1.1, >= 1.1.1) tty-cursor (0.7.0) tty-screen (0.7.0) - tty-spinner (0.9.1) + tty-spinner (0.9.2) tty-cursor (~> 0.7) uber (0.1.0) unf (0.1.4) @@ -138,7 +138,7 @@ GEM unf_ext (0.0.7.6) unicode-display_width (1.6.0) word_wrap (1.0.0) - xcodeproj (1.12.0) + xcodeproj (1.14.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) From 927c5c02cafce45f85f98c79da243ba8b860e6ed Mon Sep 17 00:00:00 2001 From: Melih Date: Tue, 31 Mar 2020 11:45:25 +0200 Subject: [PATCH 17/20] Dependency upgrades, running on AS 4.1 --- .../kotlin/com/melih/rocketscience/App.kt | 4 +++ .../com/melih/rocketscience/di/AppModule.kt | 10 ++++++ build.gradle | 14 ++++---- .../core/base/lifecycle/BaseDaggerFragment.kt | 9 ++--- .../core/base/paging/BasePagingDataSource.kt | 2 +- .../com/melih/core/BaseTestWithMainThread.kt | 2 +- .../core/paging/BasePagingDataSourceTest.kt | 2 -- .../melih/interactors/base/BaseInteractor.kt | 2 +- .../interactors/base/BaseInteractorTest.kt | 1 - .../com/melih/detail/ui/DetailViewModel.kt | 8 ++--- .../melih/detail/BaseTestWithMainThread.kt | 2 +- .../com/melih/detail/DetailViewModelTest.kt | 1 - .../com/melih/launches/ui/LaunchesFragment.kt | 1 + .../ui/paging/LaunchesPagingSource.kt | 1 - .../melih/launches/BaseTestWithMainThread.kt | 4 +-- gradle/wrapper/gradle-wrapper.properties | 4 +-- scripts/cq/detekt.gradle | 3 +- scripts/cq/dokka.gradle | 11 +++--- scripts/dependencies.gradle | 36 +++++++++---------- 19 files changed, 64 insertions(+), 53 deletions(-) diff --git a/app/src/main/kotlin/com/melih/rocketscience/App.kt b/app/src/main/kotlin/com/melih/rocketscience/App.kt index b2d8e13..a5e2b73 100644 --- a/app/src/main/kotlin/com/melih/rocketscience/App.kt +++ b/app/src/main/kotlin/com/melih/rocketscience/App.kt @@ -1,10 +1,12 @@ package com.melih.rocketscience import com.melih.core.di.DaggerCoreComponent +import com.melih.launches.data.LaunchDetailItem import com.melih.rocketscience.di.DaggerAppComponent import dagger.android.AndroidInjector import dagger.android.DaggerApplication import timber.log.Timber +import javax.inject.Inject class App : DaggerApplication() { override fun applicationInjector(): AndroidInjector = @@ -14,6 +16,8 @@ class App : DaggerApplication() { .create(this) ) + @Inject + lateinit var item: LaunchDetailItem override fun onCreate() { super.onCreate() diff --git a/app/src/main/kotlin/com/melih/rocketscience/di/AppModule.kt b/app/src/main/kotlin/com/melih/rocketscience/di/AppModule.kt index 65eabe5..71cc7a0 100644 --- a/app/src/main/kotlin/com/melih/rocketscience/di/AppModule.kt +++ b/app/src/main/kotlin/com/melih/rocketscience/di/AppModule.kt @@ -1,9 +1,11 @@ package com.melih.rocketscience.di import com.melih.detail.di.DetailContributor +import com.melih.launches.data.LaunchDetailItem import com.melih.launches.di.LaunchesContributor import com.melih.rocketscience.MainActivity import dagger.Module +import dagger.Provides import dagger.android.ContributesAndroidInjector @Module @@ -16,4 +18,12 @@ abstract class AppModule { DetailContributor::class] ) abstract fun mainActivity(): MainActivity + + @Module + companion object { + + @JvmStatic + @Provides + fun provdeSomeObject() = LaunchDetailItem(10, "", "Rocket", "Desc") + } } diff --git a/build.gradle b/build.gradle index a4f9cfb..8225671 100644 --- a/build.gradle +++ b/build.gradle @@ -1,18 +1,18 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.60' - ext.nav_version = '2.2.0-beta01' + ext.kotlin_version = '1.3.71' + ext.nav_version = '2.3.0-alpha04' repositories { google() jcenter() maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' } + maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } } dependencies { - classpath 'com.android.tools.build:gradle:4.0.0-alpha07' + classpath 'com.android.tools.build:gradle:4.1.0-alpha04' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.dokka:dokka-android-gradle-plugin:0.9.18" - classpath "de.mannodermaus.gradle.plugins:android-junit5:1.5.1.0" + classpath "de.mannodermaus.gradle.plugins:android-junit5:1.6.0.1-SNAPSHOT" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -20,8 +20,8 @@ buildscript { } plugins { - id 'io.gitlab.arturbosch.detekt' version '1.1.1' - id 'org.jetbrains.dokka' version '0.9.18' + id 'io.gitlab.arturbosch.detekt' version '1.7.2' + id 'org.jetbrains.dokka' version '0.10.1' id 'jacoco' } diff --git a/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseDaggerFragment.kt b/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseDaggerFragment.kt index dab5a6f..23014aa 100644 --- a/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseDaggerFragment.kt +++ b/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseDaggerFragment.kt @@ -8,6 +8,7 @@ import dagger.android.DispatchingAndroidInjector import dagger.android.HasAndroidInjector import dagger.android.support.AndroidSupportInjection import javax.inject.Inject +import kotlin.properties.Delegates /** @@ -24,11 +25,11 @@ abstract class BaseDaggerFragment : BaseFragment(), HasA //region Properties - @get:Inject - internal var androidInjector: DispatchingAndroidInjector? = null + @Inject + protected lateinit var androidInjector: DispatchingAndroidInjector @Inject - lateinit var viewModelFactory: ViewModelFactory + protected lateinit var viewModelFactory: ViewModelFactory //endregion //region Functions @@ -38,6 +39,6 @@ abstract class BaseDaggerFragment : BaseFragment(), HasA super.onAttach(context) } - override fun androidInjector(): AndroidInjector? = androidInjector + override fun androidInjector(): AndroidInjector = androidInjector //endregion } diff --git a/core/src/main/kotlin/com/melih/core/base/paging/BasePagingDataSource.kt b/core/src/main/kotlin/com/melih/core/base/paging/BasePagingDataSource.kt index a004f0e..9cd5fa7 100644 --- a/core/src/main/kotlin/com/melih/core/base/paging/BasePagingDataSource.kt +++ b/core/src/main/kotlin/com/melih/core/base/paging/BasePagingDataSource.kt @@ -35,7 +35,7 @@ const val INITIAL_PAGE = 0 * It's cancelled automatically when source factory [invalidates][invalidate] the source. */ -@UseExperimental(ExperimentalCoroutinesApi::class) +@OptIn(ExperimentalCoroutinesApi::class) abstract class BasePagingDataSource : PageKeyedDataSource() { //region Abstractions diff --git a/core/src/test/kotlin/com/melih/core/BaseTestWithMainThread.kt b/core/src/test/kotlin/com/melih/core/BaseTestWithMainThread.kt index d8d68fb..92deea2 100644 --- a/core/src/test/kotlin/com/melih/core/BaseTestWithMainThread.kt +++ b/core/src/test/kotlin/com/melih/core/BaseTestWithMainThread.kt @@ -13,7 +13,7 @@ import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import kotlin.coroutines.suspendCoroutine -@UseExperimental(ExperimentalCoroutinesApi::class) +@OptIn(ExperimentalCoroutinesApi::class) abstract class BaseTestWithMainThread { private val dispatcher = TestCoroutineDispatcher() diff --git a/core/src/test/kotlin/com/melih/core/paging/BasePagingDataSourceTest.kt b/core/src/test/kotlin/com/melih/core/paging/BasePagingDataSourceTest.kt index 700bbfb..11e3515 100644 --- a/core/src/test/kotlin/com/melih/core/paging/BasePagingDataSourceTest.kt +++ b/core/src/test/kotlin/com/melih/core/paging/BasePagingDataSourceTest.kt @@ -1,5 +1,3 @@ -@file:UseExperimental(ExperimentalCoroutinesApi::class) - package com.melih.core.paging import androidx.paging.PageKeyedDataSource diff --git a/data/interactors/src/main/kotlin/com/melih/interactors/base/BaseInteractor.kt b/data/interactors/src/main/kotlin/com/melih/interactors/base/BaseInteractor.kt index ffcca7f..1a57c62 100644 --- a/data/interactors/src/main/kotlin/com/melih/interactors/base/BaseInteractor.kt +++ b/data/interactors/src/main/kotlin/com/melih/interactors/base/BaseInteractor.kt @@ -12,7 +12,7 @@ import kotlinx.coroutines.flow.flowOn /** * Base use case that wraps [suspending][suspend] [run] function with [flow][Flow] and returns it for later usage. */ -@UseExperimental(ExperimentalCoroutinesApi::class) +@OptIn(ExperimentalCoroutinesApi::class) abstract class BaseInteractor { //region Abstractions diff --git a/data/interactors/src/test/kotlin/com/melih/interactors/base/BaseInteractorTest.kt b/data/interactors/src/test/kotlin/com/melih/interactors/base/BaseInteractorTest.kt index 5505f1a..0799822 100644 --- a/data/interactors/src/test/kotlin/com/melih/interactors/base/BaseInteractorTest.kt +++ b/data/interactors/src/test/kotlin/com/melih/interactors/base/BaseInteractorTest.kt @@ -15,7 +15,6 @@ import org.amshove.kluent.shouldEqualTo import org.junit.jupiter.api.Test import java.util.ArrayDeque -@UseExperimental(ExperimentalCoroutinesApi::class) class BaseInteractorTest { val testInteractor = spyk(TestInteractor()) diff --git a/features/detail/src/main/kotlin/com/melih/detail/ui/DetailViewModel.kt b/features/detail/src/main/kotlin/com/melih/detail/ui/DetailViewModel.kt index 0048b98..9cccca4 100644 --- a/features/detail/src/main/kotlin/com/melih/detail/ui/DetailViewModel.kt +++ b/features/detail/src/main/kotlin/com/melih/detail/ui/DetailViewModel.kt @@ -20,6 +20,10 @@ class DetailViewModel @Inject constructor( val rocketName = map(successData) { it.rocketName } + get() { + loadData() + return field + } val description = map(successData) { it.missionDescription @@ -30,10 +34,6 @@ class DetailViewModel @Inject constructor( } //endregion - init { - loadData() - } - //region Functions /** diff --git a/features/detail/src/test/java/com/melih/detail/BaseTestWithMainThread.kt b/features/detail/src/test/java/com/melih/detail/BaseTestWithMainThread.kt index 82056d4..135cb5e 100644 --- a/features/detail/src/test/java/com/melih/detail/BaseTestWithMainThread.kt +++ b/features/detail/src/test/java/com/melih/detail/BaseTestWithMainThread.kt @@ -8,7 +8,7 @@ import kotlinx.coroutines.test.setMain import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach -@UseExperimental(ExperimentalCoroutinesApi::class) +@OptIn(ExperimentalCoroutinesApi::class) abstract class BaseTestWithMainThread { protected val dispatcher = TestCoroutineDispatcher() diff --git a/features/detail/src/test/java/com/melih/detail/DetailViewModelTest.kt b/features/detail/src/test/java/com/melih/detail/DetailViewModelTest.kt index 0843e7d..738d89c 100644 --- a/features/detail/src/test/java/com/melih/detail/DetailViewModelTest.kt +++ b/features/detail/src/test/java/com/melih/detail/DetailViewModelTest.kt @@ -22,7 +22,6 @@ import org.junit.jupiter.api.Test * * See [testing documentation](http://d.android.com/tools/testing). */ -@UseExperimental(ExperimentalCoroutinesApi::class) class DetailViewModelTest : BaseTestWithMainThread() { private val getLaunchDetails: GetLaunchDetails = mockk(relaxed = true) diff --git a/features/launches/src/main/kotlin/com/melih/launches/ui/LaunchesFragment.kt b/features/launches/src/main/kotlin/com/melih/launches/ui/LaunchesFragment.kt index e5e7a85..af60af3 100644 --- a/features/launches/src/main/kotlin/com/melih/launches/ui/LaunchesFragment.kt +++ b/features/launches/src/main/kotlin/com/melih/launches/ui/LaunchesFragment.kt @@ -14,6 +14,7 @@ import com.melih.launches.data.LaunchItem import com.melih.launches.databinding.LaunchesBinding import com.melih.launches.ui.adapters.LaunchesAdapter import com.melih.launches.ui.vm.LaunchesViewModel +import javax.inject.Inject class LaunchesFragment : BaseDaggerFragment(), SwipeRefreshLayout.OnRefreshListener { diff --git a/features/launches/src/main/kotlin/com/melih/launches/ui/paging/LaunchesPagingSource.kt b/features/launches/src/main/kotlin/com/melih/launches/ui/paging/LaunchesPagingSource.kt index 7e1424b..599165b 100644 --- a/features/launches/src/main/kotlin/com/melih/launches/ui/paging/LaunchesPagingSource.kt +++ b/features/launches/src/main/kotlin/com/melih/launches/ui/paging/LaunchesPagingSource.kt @@ -16,7 +16,6 @@ class LaunchesPagingSource @Inject constructor( //region Functions - @UseExperimental(ExperimentalCoroutinesApi::class) override fun loadDataForPage(page: Int) = getLaunches( getLaunchesParams.copy( diff --git a/features/launches/src/test/kotlin/com/melih/launches/BaseTestWithMainThread.kt b/features/launches/src/test/kotlin/com/melih/launches/BaseTestWithMainThread.kt index c04f0ac..43d86ae 100644 --- a/features/launches/src/test/kotlin/com/melih/launches/BaseTestWithMainThread.kt +++ b/features/launches/src/test/kotlin/com/melih/launches/BaseTestWithMainThread.kt @@ -1,5 +1,3 @@ -@file:UseExperimental(ExperimentalCoroutinesApi::class) - package com.melih.launches import kotlinx.coroutines.Dispatchers @@ -10,7 +8,7 @@ import kotlinx.coroutines.test.setMain import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach -@UseExperimental(ExperimentalCoroutinesApi::class) +@OptIn(ExperimentalCoroutinesApi::class) abstract class BaseTestWithMainThread { protected val dispatcher = TestCoroutineDispatcher() diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 94cd6c9..8663441 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Jan 01 15:32:57 CET 2020 +#Tue Mar 31 10:39:09 CEST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1-milestone-2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-rc-1-bin.zip diff --git a/scripts/cq/detekt.gradle b/scripts/cq/detekt.gradle index 5f07c3c..68347a6 100644 --- a/scripts/cq/detekt.gradle +++ b/scripts/cq/detekt.gradle @@ -1,9 +1,8 @@ apply plugin: 'io.gitlab.arturbosch.detekt' detekt { - toolVersion = "1.0.0" + failFast = true config = files("$rootProject.projectDir/default-detekt-config.yml") - filters = ".*/resources/.*,.*/build/.*" reports { html { diff --git a/scripts/cq/dokka.gradle b/scripts/cq/dokka.gradle index 59bed4c..2590827 100644 --- a/scripts/cq/dokka.gradle +++ b/scripts/cq/dokka.gradle @@ -1,10 +1,13 @@ -apply plugin: 'org.jetbrains.dokka-android' +apply plugin: 'org.jetbrains.dokka' dokka { outputFormat = "html" outputDirectory = "$rootProject.projectDir/reports/javadoc" - jdkVersion = 8 - reportUndocumented = true - skipEmptyPackages = true + configuration { + jdkVersion = 8 + + reportUndocumented = true + skipEmptyPackages = true + } } diff --git a/scripts/dependencies.gradle b/scripts/dependencies.gradle index 49b7aed..4f19ea6 100644 --- a/scripts/dependencies.gradle +++ b/scripts/dependencies.gradle @@ -4,32 +4,32 @@ ext { minSdkVersion : 21, compileSdkVersion : 29, targetSdkVersion : 29, - buildToolsVersion : "29.0.2", + buildToolsVersion : "29.0.3", appCompatVersion : "1.1.0", - lifecycleVersion : "2.2.0-alpha05", - fragmentVersion : "1.2.0-rc01", - workManagerVersion : "2.3.0-alpha03", - constraintLayoutVesion : "2.0.0-beta3", + lifecycleVersion : "2.2.0", + fragmentVersion : "1.3.0-alpha02", + workManagerVersion : "2.4.0-alpha01", + constraintLayoutVesion : "2.0.0-beta4", cardViewVersion : "1.0.0", - recyclerViewVersion : "1.1.0-rc01", + recyclerViewVersion : "1.2.0-alpha01", pagingVersion : "2.1.0", - viewPagerVersion : "1.0.0-rc01", - materialVersion : "1.1.0-alpha09", - swipeRefreshLayoutVersion: "1.1.0-alpha03", + viewPagerVersion : "1.0.0", + materialVersion : "1.2.0-alpha05", + swipeRefreshLayoutVersion: "1.1.0-beta01", collectionVersion : "1.1.0", - roomVersion : "2.2.0", - daggerVersion : "2.25.2", - okHttpVersion : "4.2.1", - retrofitVersion : "2.6.2", + roomVersion : "2.2.5", + daggerVersion : "2.27", + okHttpVersion : "4.4.0", + retrofitVersion : "2.8.1", picassoVersion : "2.71828", - moshiVersion : "1.8.0", - coroutinesVersion : "1.3.2", - leakCanaryVersion : "2.0-beta-3", + moshiVersion : "1.9.2", + coroutinesVersion : "1.3.5", + leakCanaryVersion : "2.2", timberVersion : "4.7.1", - jUnitVersion : "5.5.2", + jUnitVersion : "5.6.0", espressoVersion : "3.2.0", mockkVersion : "1.9.3", - kluentVersion : "1.53", + kluentVersion : "1.60", ] libraries = [ From ef05fa7e051caba4bc3877c5940d8f83aff74729 Mon Sep 17 00:00:00 2001 From: Melih Date: Tue, 31 Mar 2020 13:25:54 +0200 Subject: [PATCH 18/20] Fixed detekt findings, removed test code --- .../com/melih/abstractions/mapper/Mapper.kt | 4 +- app/build.gradle | 4 +- .../kotlin/com/melih/rocketscience/App.kt | 3 - .../com/melih/rocketscience/di/AppModule.kt | 8 - core/build.gradle | 5 +- .../melih/interactors/base/BaseInteractor.kt | 2 +- .../interactors/sources/LaunchesSource.kt | 25 +- .../com/melih/persistence/dao/LaunchesDao.kt | 12 +- default-detekt-config.yml | 264 ++++++++++++------ .../melih/detail/data/LaunchDetailMapper.kt | 2 +- .../com/melih/launches/data/LaunchMapper.kt | 2 +- scripts/feature_module.gradle | 5 +- 12 files changed, 208 insertions(+), 128 deletions(-) diff --git a/abstractions/src/main/kotlin/com/melih/abstractions/mapper/Mapper.kt b/abstractions/src/main/kotlin/com/melih/abstractions/mapper/Mapper.kt index aac38d2..0eba505 100644 --- a/abstractions/src/main/kotlin/com/melih/abstractions/mapper/Mapper.kt +++ b/abstractions/src/main/kotlin/com/melih/abstractions/mapper/Mapper.kt @@ -2,7 +2,7 @@ package com.melih.abstractions.mapper import com.melih.abstractions.data.ViewEntity -abstract class Mapper { +interface Mapper { - abstract fun convert(t: T): R + fun convert(t: T): R } diff --git a/app/build.gradle b/app/build.gradle index 4d1af18..8dd64c3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -14,8 +14,8 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } - dataBinding { - enabled = true + buildFeatures{ + dataBinding = true } } diff --git a/app/src/main/kotlin/com/melih/rocketscience/App.kt b/app/src/main/kotlin/com/melih/rocketscience/App.kt index a5e2b73..803856c 100644 --- a/app/src/main/kotlin/com/melih/rocketscience/App.kt +++ b/app/src/main/kotlin/com/melih/rocketscience/App.kt @@ -16,9 +16,6 @@ class App : DaggerApplication() { .create(this) ) - @Inject - lateinit var item: LaunchDetailItem - override fun onCreate() { super.onCreate() diff --git a/app/src/main/kotlin/com/melih/rocketscience/di/AppModule.kt b/app/src/main/kotlin/com/melih/rocketscience/di/AppModule.kt index 71cc7a0..0d0630b 100644 --- a/app/src/main/kotlin/com/melih/rocketscience/di/AppModule.kt +++ b/app/src/main/kotlin/com/melih/rocketscience/di/AppModule.kt @@ -18,12 +18,4 @@ abstract class AppModule { DetailContributor::class] ) abstract fun mainActivity(): MainActivity - - @Module - companion object { - - @JvmStatic - @Provides - fun provdeSomeObject() = LaunchDetailItem(10, "", "Rocket", "Desc") - } } diff --git a/core/build.gradle b/core/build.gradle index 0d9ae40..325677e 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -7,8 +7,9 @@ apply from: "$rootProject.projectDir/scripts/default_dependencies.gradle" apply from: "$rootProject.projectDir/scripts/sources.gradle" android { - dataBinding { - enabled = true + + buildFeatures{ + dataBinding = true } } diff --git a/data/interactors/src/main/kotlin/com/melih/interactors/base/BaseInteractor.kt b/data/interactors/src/main/kotlin/com/melih/interactors/base/BaseInteractor.kt index 1a57c62..d8a7a85 100644 --- a/data/interactors/src/main/kotlin/com/melih/interactors/base/BaseInteractor.kt +++ b/data/interactors/src/main/kotlin/com/melih/interactors/base/BaseInteractor.kt @@ -39,4 +39,4 @@ interface InteractorParameters /** * Symbolizes absence of parameters for an [interactor][BaseInteractor] */ -class None : Any(), InteractorParameters +class None : InteractorParameters diff --git a/data/interactors/src/main/kotlin/com/melih/interactors/sources/LaunchesSource.kt b/data/interactors/src/main/kotlin/com/melih/interactors/sources/LaunchesSource.kt index ad73cce..7abce3c 100644 --- a/data/interactors/src/main/kotlin/com/melih/interactors/sources/LaunchesSource.kt +++ b/data/interactors/src/main/kotlin/com/melih/interactors/sources/LaunchesSource.kt @@ -143,21 +143,18 @@ internal class LaunchesSource @Inject constructor( launch } - private fun transformImageUrl(imageUrl: String, supportedSizes: IntArray) = - try { - val urlSplit = imageUrl.split("_") - val url = urlSplit[0] - val format = urlSplit[1].split(".")[1] - - val requestedSize = if (!supportedSizes.contains(DEFAULT_IMAGE_SIZE)) { - supportedSizes.last { it < DEFAULT_IMAGE_SIZE } - } else { - DEFAULT_IMAGE_SIZE - } + private fun transformImageUrl(imageUrl: String, supportedSizes: IntArray): String { + val urlSplit = imageUrl.split("_") + val url = urlSplit[0] + val format = urlSplit[1].split(".")[1] - "${url}_$requestedSize.$format" - } catch (e: Exception) { - imageUrl + val requestedSize = if (!supportedSizes.contains(DEFAULT_IMAGE_SIZE)) { + supportedSizes.last { it < DEFAULT_IMAGE_SIZE } + } else { + DEFAULT_IMAGE_SIZE } + + return "${url}_$requestedSize.$format" + } //endregion } diff --git a/data/persistence/src/main/kotlin/com/melih/persistence/dao/LaunchesDao.kt b/data/persistence/src/main/kotlin/com/melih/persistence/dao/LaunchesDao.kt index 7d14666..36f366b 100644 --- a/data/persistence/src/main/kotlin/com/melih/persistence/dao/LaunchesDao.kt +++ b/data/persistence/src/main/kotlin/com/melih/persistence/dao/LaunchesDao.kt @@ -10,26 +10,26 @@ import com.melih.definitions.entities.LaunchEntity * DAO for list of [launches][LaunchEntity] */ @Dao -abstract class LaunchesDao { +interface LaunchesDao { //region Queries @Query("SELECT * FROM Launches ORDER BY launchStartTime DESC LIMIT :count OFFSET :page*:count") - abstract suspend fun getLaunches(count: Int, page: Int): List + suspend fun getLaunches(count: Int, page: Int): List @Query("SELECT * FROM Launches WHERE id=:id LIMIT 1") - abstract suspend fun getLaunchById(id: Long): LaunchEntity? + suspend fun getLaunchById(id: Long): LaunchEntity? @Query("DELETE FROM Launches") - abstract suspend fun nukeLaunches() + suspend fun nukeLaunches() //endregion //region Insertion @Insert(onConflict = OnConflictStrategy.REPLACE) - abstract suspend fun saveLaunches(launches: List) + suspend fun saveLaunches(launches: List) @Insert(onConflict = OnConflictStrategy.REPLACE) - abstract suspend fun saveLaunch(launch: LaunchEntity) + suspend fun saveLaunch(launch: LaunchEntity) //endregion } diff --git a/default-detekt-config.yml b/default-detekt-config.yml index 326fe3c..738f638 100644 --- a/default-detekt-config.yml +++ b/default-detekt-config.yml @@ -1,40 +1,21 @@ -autoCorrect: true - -test-pattern: # Configure exclusions for test sources - active: true - patterns: # Test file regexes - - '.*/test/.*' - - '.*/androidTest/.*' - - '.*Test.kt' - - '.*Spec.kt' - - '.*Spek.kt' - exclude-rule-sets: - - 'comments' - exclude-rules: - - 'NamingRules' - - 'WildcardImport' - - 'MagicNumber' - - 'MaxLineLength' - - 'LateinitUsage' - - 'StringLiteralDuplication' - - 'SpreadOperator' - - 'TooManyFunctions' - - 'ForEachOnRange' - - 'FunctionMaxLength' - - 'TooGenericExceptionCaught' - - 'InstanceOfCheckForException' - build: maxIssues: 1 + excludeCorrectable: false weights: -# complexity: 1 -# LongParameterList: 1 -# style: 1 -# comments: 0 + # complexity: 2 + # LongParameterList: 1 + # style: 1 + # comments: 1 + +config: + validation: true + # when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]' + excludes: '' processors: active: true exclude: + - 'DetektProgressListener' # - 'FunctionCountProcessor' # - 'PropertyCountProcessor' # - 'ClassCountProcessor' @@ -44,21 +25,25 @@ processors: console-reports: active: true exclude: - # - 'ProjectStatisticsReport' - # - 'ComplexityReport' - # - 'NotificationReport' - # - 'FindingsReport' - # - 'BuildFailureReport' + - 'ProjectStatisticsReport' + - 'ComplexityReport' + - 'NotificationReport' + # - 'FindingsReport' + - 'FileBasedFindingsReport' comments: active: true + excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt' + AbsentOrWrongFileLicense: + active: false + licenseTemplateFile: 'license.template' CommentOverPrivateFunction: active: false CommentOverPrivateProperty: active: false EndOfSentenceFormat: active: false - endOfSentenceFormat: ([.?!][ \t\n\r\f<])|([.?!]$) + endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)' UndocumentedPublicClass: active: false searchInNestedClass: true @@ -67,6 +52,8 @@ comments: searchInInnerInterface: true UndocumentedPublicFunction: active: false + UndocumentedPublicProperty: + active: false complexity: active: true @@ -77,14 +64,17 @@ complexity: active: false threshold: 10 includeStaticDeclarations: false + includePrivateDeclarations: false ComplexMethod: active: true - threshold: 10 + threshold: 15 ignoreSingleWhenExpression: false ignoreSimpleWhenEntries: false + ignoreNestingFunctions: false + nestingFunctions: run,let,apply,with,also,use,forEach,isNotNull,ifNull LabeledExpression: active: false - ignoredLabels: "" + ignoredLabels: '' LargeClass: active: true threshold: 600 @@ -93,8 +83,10 @@ complexity: threshold: 60 LongParameterList: active: true - threshold: 6 + functionThreshold: 6 + constructorThreshold: 7 ignoreDefaultParameters: false + ignoreDataClasses: true MethodOverloading: active: false threshold: 6 @@ -103,12 +95,14 @@ complexity: threshold: 4 StringLiteralDuplication: active: false + excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt' threshold: 3 ignoreAnnotation: true excludeStringsWithLessThan5Characters: true ignoreStringsRegex: '$^' TooManyFunctions: active: true + excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt' thresholdInFiles: 11 thresholdInClasses: 11 thresholdInInterfaces: 11 @@ -118,11 +112,18 @@ complexity: ignorePrivate: false ignoreOverridden: false +coroutines: + active: true + GlobalCoroutineUsage: + active: false + RedundantSuspendModifier: + active: false + empty-blocks: active: true EmptyCatchBlock: active: true - allowedExceptionNameRegex: "^(_|(ignore|expected).*)" + allowedExceptionNameRegex: '^(_|(ignore|expected).*)' EmptyClassBlock: active: true EmptyDefaultConstructor: @@ -137,7 +138,7 @@ empty-blocks: active: true EmptyFunctionBlock: active: true - ignoreOverriddenFunctions: false + ignoreOverridden: false EmptyIfBlock: active: true EmptyInitBlock: @@ -146,6 +147,8 @@ empty-blocks: active: true EmptySecondaryConstructor: active: true + EmptyTryBlock: + active: true EmptyWhenBlock: active: true EmptyWhileBlock: @@ -158,6 +161,7 @@ exceptions: methodNames: 'toString,hashCode,equals,finalize' InstanceOfCheckForException: active: false + excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt' NotImplementedDeclaration: active: false PrintStackTrace: @@ -166,9 +170,11 @@ exceptions: active: false ReturnFromFinally: active: false + ignoreLabeled: false SwallowedException: active: false ignoredExceptionTypes: 'InterruptedException,NumberFormatException,ParseException,MalformedURLException' + allowedExceptionNameRegex: '^(_|(ignore|expected).*)' ThrowingExceptionFromFinally: active: false ThrowingExceptionInMain: @@ -179,53 +185,65 @@ exceptions: ThrowingNewInstanceOfSameException: active: false TooGenericExceptionCaught: - active: false + active: true + excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt' exceptionNames: - - ArrayIndexOutOfBoundsException - - Error - - Exception - - IllegalMonitorStateException - - NullPointerException - - IndexOutOfBoundsException - - RuntimeException - - Throwable - allowedExceptionNameRegex: "^(_|(ignore|expected).*)" + - ArrayIndexOutOfBoundsException + - Error + - Exception + - IllegalMonitorStateException + - NullPointerException + - IndexOutOfBoundsException + - RuntimeException + - Throwable + allowedExceptionNameRegex: '^(_|(ignore|expected).*)' TooGenericExceptionThrown: active: true exceptionNames: - - Error - - Exception - - Throwable - - RuntimeException + - Error + - Exception + - Throwable + - RuntimeException formatting: active: true android: false autoCorrect: true + AnnotationOnSeparateLine: + active: false + autoCorrect: true ChainWrapping: active: true autoCorrect: true CommentSpacing: active: true autoCorrect: true + EnumEntryNameCase: + active: false + autoCorrect: true Filename: active: true FinalNewline: active: true autoCorrect: true + insertFinalNewLine: true ImportOrdering: active: false + autoCorrect: true Indentation: - active: true + active: false autoCorrect: true indentSize: 4 continuationIndentSize: 4 MaximumLineLength: active: true - maxLineLength: 150 + maxLineLength: 173 ModifierOrdering: active: true autoCorrect: true + MultiLineIfElse: + active: true + autoCorrect: true NoBlankLineBeforeRbrace: active: true autoCorrect: true @@ -235,8 +253,9 @@ formatting: NoEmptyClassBody: active: true autoCorrect: true - NoItParamInMultilineLambda: + NoEmptyFirstLineInMethodBlock: active: false + autoCorrect: true NoLineBreakAfterElse: active: true autoCorrect: true @@ -260,7 +279,6 @@ formatting: autoCorrect: true NoWildcardImports: active: true - autoCorrect: true PackageName: active: true autoCorrect: true @@ -277,6 +295,9 @@ formatting: SpacingAroundCurly: active: true autoCorrect: true + SpacingAroundDot: + active: true + autoCorrect: true SpacingAroundKeyword: active: true autoCorrect: true @@ -297,60 +318,79 @@ naming: active: true ClassNaming: active: true + excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt' classPattern: '[A-Z$][a-zA-Z0-9$]*' ConstructorParameterNaming: active: true + excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt' parameterPattern: '[a-z][A-Za-z0-9]*' privateParameterPattern: '[a-z][A-Za-z0-9]*' excludeClassPattern: '$^' + ignoreOverridden: true EnumNaming: active: true + excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt' enumEntryPattern: '^[A-Z][_a-zA-Z0-9]*' ForbiddenClassName: active: false + excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt' forbiddenName: '' FunctionMaxLength: active: false + excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt' maximumFunctionNameLength: 30 FunctionMinLength: active: false + excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt' minimumFunctionNameLength: 3 FunctionNaming: active: true + excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt' functionPattern: '^([a-z$][a-zA-Z$0-9]*)|(`.*`)$' excludeClassPattern: '$^' ignoreOverridden: true FunctionParameterNaming: active: true + excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt' parameterPattern: '[a-z][A-Za-z0-9]*' excludeClassPattern: '$^' - ignoreOverriddenFunctions: true + ignoreOverridden: true + InvalidPackageDeclaration: + active: false + rootPackage: '' MatchingDeclarationName: active: true + mustBeFirst: true MemberNameEqualsClassName: - active: false - ignoreOverriddenFunction: true + active: true + ignoreOverridden: true ObjectPropertyNaming: active: true + excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt' constantPattern: '[A-Za-z][_A-Za-z0-9]*' propertyPattern: '[A-Za-z][_A-Za-z0-9]*' privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*' PackageNaming: active: true + excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt' packagePattern: '^[a-z]+(\.[a-z][A-Za-z0-9]*)*$' TopLevelPropertyNaming: active: true + excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt' constantPattern: '[A-Z][_A-Z0-9]*' propertyPattern: '[A-Za-z][_A-Za-z0-9]*' - privatePropertyPattern: '(_)?[A-Za-z][A-Za-z0-9]*' + privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*' VariableMaxLength: active: false + excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt' maximumVariableNameLength: 64 VariableMinLength: active: false + excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt' minimumVariableNameLength: 1 VariableNaming: active: true + excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt' variablePattern: '[a-z][A-Za-z0-9]*' privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*' excludeClassPattern: '$^' @@ -359,46 +399,61 @@ naming: performance: active: true ArrayPrimitive: - active: false + active: true ForEachOnRange: active: true + excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt' SpreadOperator: active: true + excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt' UnnecessaryTemporaryInstantiation: active: true potential-bugs: active: true + Deprecation: + active: false DuplicateCaseInWhenExpression: active: true EqualsAlwaysReturnsTrueOrFalse: - active: false + active: true EqualsWithHashCodeExist: active: true ExplicitGarbageCollectionCall: active: true - InvalidRange: + HasPlatformType: active: false - IteratorHasNextCallsNextMethod: + ImplicitDefaultLocale: active: false + InvalidRange: + active: true + IteratorHasNextCallsNextMethod: + active: true IteratorNotThrowingNoSuchElementException: - active: false + active: true LateinitUsage: active: false - excludeAnnotatedProperties: "" - ignoreOnClassesPattern: "" + excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt' + excludeAnnotatedProperties: '' + ignoreOnClassesPattern: '' + MapGetWithNotNullAssertionOperator: + active: false + MissingWhenCase: + active: true + RedundantElseInWhen: + active: true UnconditionalJumpStatementInLoop: active: false UnreachableCode: active: true UnsafeCallOnNullableType: - active: false + active: true UnsafeCast: active: false UselessPostfixExpression: active: false WrongEqualsTypeParameter: - active: false + active: true style: active: true @@ -407,10 +462,14 @@ style: DataClassContainsFunctions: active: false conversionFunctionPrefix: 'to' - EqualsNullCall: + DataClassShouldBeImmutable: active: false + EqualsNullCall: + active: true EqualsOnSignatureLine: active: false + ExplicitCollectionElementAccessMethod: + active: false ExplicitItLambdaParameter: active: false ExpressionBodySyntax: @@ -419,38 +478,54 @@ style: ForbiddenComment: active: true values: 'TODO:,FIXME:,STOPSHIP:' + allowedPatterns: '' ForbiddenImport: active: false - imports: '' + imports: [] + forbiddenPatterns: '' + ForbiddenMethodCall: + active: false + methods: '' + ForbiddenPublicDataClass: + active: false + ignorePackages: '*.internal,*.internal.*' ForbiddenVoid: active: false + ignoreOverridden: false + ignoreUsageInGenerics: false FunctionOnlyReturningConstant: - active: false + active: true ignoreOverridableFunction: true excludedFunctions: 'describeContents' + excludeAnnotatedFunction: 'dagger.Provides' + LibraryCodeMustSpecifyReturnType: + active: true LoopWithTooManyJumpStatements: - active: false + active: true maxJumpCount: 1 MagicNumber: active: true + excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt' ignoreNumbers: '-1,0,1,2' ignoreHashCodeFunction: true ignorePropertyDeclaration: false + ignoreLocalVariableDeclaration: false ignoreConstantDeclaration: true ignoreCompanionObjectPropertyDeclaration: true ignoreAnnotation: false ignoreNamedArgument: true ignoreEnums: false + ignoreRanges: false MandatoryBracesIfStatements: active: false MaxLineLength: active: true - maxLineLength: 150 + maxLineLength: 173 excludePackageStatements: true excludeImportStatements: true excludeCommentStatements: false MayBeConst: - active: false + active: true ModifierOrder: active: true NestedClassesVisibility: @@ -468,15 +543,18 @@ style: PreferToOverPairSyntax: active: false ProtectedMemberInFinalClass: + active: true + RedundantExplicitType: active: false RedundantVisibilityModifierRule: active: false ReturnCount: active: true max: 2 - excludedFunctions: "equals" + excludedFunctions: 'equals' excludeLabeled: false excludeReturnFromLambda: true + excludeGuardClauses: false SafeCast: active: true SerialVersionUIDInSerializableClass: @@ -492,12 +570,14 @@ style: active: false acceptableDecimalLength: 5 UnnecessaryAbstractClass: + active: true + excludeAnnotatedClasses: 'dagger.Module' + UnnecessaryAnnotationUseSiteTarget: active: false - excludeAnnotatedClasses: "dagger.Module" UnnecessaryApply: active: false UnnecessaryInheritance: - active: false + active: true UnnecessaryLet: active: false UnnecessaryParentheses: @@ -507,17 +587,29 @@ style: UnusedImports: active: false UnusedPrivateClass: - active: false + active: true UnusedPrivateMember: active: false - allowedNames: "(_|ignored|expected|serialVersionUID)" + allowedNames: '(_|ignored|expected|serialVersionUID)' + UseArrayLiteralsInAnnotations: + active: false + UseCheckOrError: + active: false UseDataClass: active: false - excludeAnnotatedClasses: "" - UtilityClassWithPublicConstructor: + excludeAnnotatedClasses: '' + allowVars: false + UseIfInsteadOfWhen: + active: false + UseRequire: active: false + UselessCallOnNotNull: + active: true + UtilityClassWithPublicConstructor: + active: true VarCouldBeVal: active: false WildcardImport: active: true - excludeImports: 'java.util.*,kotlinx.android.synthetic.*' + excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt' + excludeImports: 'java.util.*,kotlinx.android.synthetic.*' \ No newline at end of file diff --git a/features/detail/src/main/kotlin/com/melih/detail/data/LaunchDetailMapper.kt b/features/detail/src/main/kotlin/com/melih/detail/data/LaunchDetailMapper.kt index 32f61c0..363e16d 100644 --- a/features/detail/src/main/kotlin/com/melih/detail/data/LaunchDetailMapper.kt +++ b/features/detail/src/main/kotlin/com/melih/detail/data/LaunchDetailMapper.kt @@ -4,7 +4,7 @@ import com.melih.abstractions.mapper.Mapper import com.melih.definitions.entities.LaunchEntity import javax.inject.Inject -class LaunchDetailMapper @Inject constructor() : Mapper() { +class LaunchDetailMapper @Inject constructor() : Mapper { override fun convert(launchEntity: LaunchEntity) = with(launchEntity) { diff --git a/features/launches/src/main/kotlin/com/melih/launches/data/LaunchMapper.kt b/features/launches/src/main/kotlin/com/melih/launches/data/LaunchMapper.kt index 77aadd1..4a24bee 100644 --- a/features/launches/src/main/kotlin/com/melih/launches/data/LaunchMapper.kt +++ b/features/launches/src/main/kotlin/com/melih/launches/data/LaunchMapper.kt @@ -4,7 +4,7 @@ import com.melih.abstractions.mapper.Mapper import com.melih.definitions.entities.LaunchEntity import javax.inject.Inject -class LaunchMapper @Inject constructor() : Mapper() { +class LaunchMapper @Inject constructor() : Mapper { override fun convert(launchEntity: LaunchEntity) = with(launchEntity) { diff --git a/scripts/feature_module.gradle b/scripts/feature_module.gradle index 89eae3a..83bba26 100644 --- a/scripts/feature_module.gradle +++ b/scripts/feature_module.gradle @@ -4,8 +4,9 @@ apply from: "$rootProject.projectDir/scripts/module.gradle" apply from: "$rootProject.projectDir/scripts/default_dependencies.gradle" android { - dataBinding { - enabled = true + + buildFeatures{ + dataBinding = true } } From 75f26544a5c3c8912dd808f3e8723accce05da7f Mon Sep 17 00:00:00 2001 From: Melih Date: Tue, 31 Mar 2020 14:09:49 +0200 Subject: [PATCH 19/20] Opted in with new dagger error format --- scripts/default_android_config.gradle | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/scripts/default_android_config.gradle b/scripts/default_android_config.gradle index c81a51b..ed21087 100644 --- a/scripts/default_android_config.gradle +++ b/scripts/default_android_config.gradle @@ -17,4 +17,14 @@ android { testOptions.unitTests.all { jvmArgs "-XX:+UnlockExperimentalVMOptions", "-XX:+UseCGroupMemoryLimitForHeap" } + + defaultConfig { + javaCompileOptions { + annotationProcessorOptions { + arguments = [ + "dagger.experimentalDaggerErrorMessages": "enabled" + ] + } + } + } } From 1bcde286ddff6d81862687cd55893042065c68cd Mon Sep 17 00:00:00 2001 From: Melih Date: Mon, 6 Jul 2020 14:15:00 +0200 Subject: [PATCH 20/20] Version updates --- build.gradle | 8 ++++---- .../com/melih/detail/ui/DetailViewModel.kt | 14 +++++++------ gradle/wrapper/gradle-wrapper.properties | 4 ++-- scripts/dependencies.gradle | 20 +++++++++---------- 4 files changed, 24 insertions(+), 22 deletions(-) diff --git a/build.gradle b/build.gradle index 8225671..52cec02 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.71' - ext.nav_version = '2.3.0-alpha04' + ext.kotlin_version = '1.3.72' + ext.nav_version = '2.3.0' repositories { google() @@ -10,7 +10,7 @@ buildscript { maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } } dependencies { - classpath 'com.android.tools.build:gradle:4.1.0-alpha04' + classpath 'com.android.tools.build:gradle:4.2.0-alpha03' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "de.mannodermaus.gradle.plugins:android-junit5:1.6.0.1-SNAPSHOT" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" @@ -20,7 +20,7 @@ buildscript { } plugins { - id 'io.gitlab.arturbosch.detekt' version '1.7.2' + id 'io.gitlab.arturbosch.detekt' version '1.10.0' id 'org.jetbrains.dokka' version '0.10.1' id 'jacoco' } diff --git a/features/detail/src/main/kotlin/com/melih/detail/ui/DetailViewModel.kt b/features/detail/src/main/kotlin/com/melih/detail/ui/DetailViewModel.kt index 9cccca4..14b2f41 100644 --- a/features/detail/src/main/kotlin/com/melih/detail/ui/DetailViewModel.kt +++ b/features/detail/src/main/kotlin/com/melih/detail/ui/DetailViewModel.kt @@ -17,14 +17,16 @@ class DetailViewModel @Inject constructor( //region Properties - val rocketName = map(successData) { - it.rocketName - } - get() { - loadData() - return field + val rocketName by lazy { + val nameData = map(successData) { + it.rocketName } + loadData() + + nameData + } + val description = map(successData) { it.missionDescription } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8663441..ca90413 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Mar 31 10:39:09 CEST 2020 +#Mon Jul 06 12:03:00 CEST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-rc-1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-rc-1-all.zip diff --git a/scripts/dependencies.gradle b/scripts/dependencies.gradle index 4f19ea6..17d113a 100644 --- a/scripts/dependencies.gradle +++ b/scripts/dependencies.gradle @@ -7,28 +7,28 @@ ext { buildToolsVersion : "29.0.3", appCompatVersion : "1.1.0", lifecycleVersion : "2.2.0", - fragmentVersion : "1.3.0-alpha02", + fragmentVersion : "1.3.0-alpha06", workManagerVersion : "2.4.0-alpha01", - constraintLayoutVesion : "2.0.0-beta4", + constraintLayoutVesion : "2.0.0-beta7", cardViewVersion : "1.0.0", recyclerViewVersion : "1.2.0-alpha01", - pagingVersion : "2.1.0", + pagingVersion : "2.1.2", viewPagerVersion : "1.0.0", - materialVersion : "1.2.0-alpha05", - swipeRefreshLayoutVersion: "1.1.0-beta01", + materialVersion : "1.3.0-alpha01", + swipeRefreshLayoutVersion: "1.1.0", collectionVersion : "1.1.0", roomVersion : "2.2.5", daggerVersion : "2.27", - okHttpVersion : "4.4.0", - retrofitVersion : "2.8.1", + okHttpVersion : "4.7.2", + retrofitVersion : "2.9.0", picassoVersion : "2.71828", - moshiVersion : "1.9.2", + moshiVersion : "1.9.3", coroutinesVersion : "1.3.5", leakCanaryVersion : "2.2", timberVersion : "4.7.1", - jUnitVersion : "5.6.0", + jUnitVersion : "5.6.2", espressoVersion : "3.2.0", - mockkVersion : "1.9.3", + mockkVersion : "1.10.0", kluentVersion : "1.60", ]