From 515fc1acd4058ebbc9b3edef40991b1a1b509663 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petrus=20Nguy=E1=BB=85n=20Th=C3=A1i=20H=E1=BB=8Dc?= Date: Sun, 9 Oct 2022 20:56:30 +0700 Subject: [PATCH] =?UTF-8?q?style:=20config=20spotless,=20ktlint=20and=20fo?= =?UTF-8?q?rmat=20all=20=E2=9D=A4=EF=B8=8F=20(#57)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * style: config spotless, ktlint and format all ❤️ * ci: update * ci: update * ci: update * ci: paths-ignore: [ '**.md' ] --- .editorconfig | 11 + .github/workflows/build.yml | 5 +- .github/workflows/remove-old-artifacts.yml | 19 + .github/workflows/reviewdog-suggester.yml | 57 +++ .../hoc/weatherapp/ExampleInstrumentedTest.kt | 12 +- .../weatherapp/CancelNotificationReceiver.kt | 30 +- .../com/hoc/weatherapp/data/CityRepository.kt | 3 +- .../hoc/weatherapp/data/CityRepositoryImpl.kt | 88 ++--- .../data/CurrentWeatherRepository.kt | 3 +- .../data/CurrentWeatherRepositoryImpl.kt | 118 +++--- .../data/FiveDayForecastRepository.kt | 2 +- .../data/FiveDayForecastRepositoryImpl.kt | 66 ++-- .../weatherapp/data/LocalDataSourceUtil.kt | 44 +-- .../java/com/hoc/weatherapp/data/Mapper.kt | 100 ++--- .../hoc/weatherapp/data/local/AppDatabase.kt | 41 ++- .../com/hoc/weatherapp/data/local/CityDao.kt | 18 +- .../data/local/CityLocalDataSource.kt | 2 +- .../data/local/CurrentWeatherDao.kt | 23 +- .../local/CurrentWeatherLocalDataSource.kt | 10 +- .../data/local/FiveDayForecastDao.kt | 8 +- .../local/FiveDayForecastLocalDataSource.kt | 6 +- .../data/local/SelectedCityPreference.kt | 34 +- .../data/local/SettingPreferences.kt | 92 ++--- .../com/hoc/weatherapp/data/models/Units.kt | 28 +- .../weatherapp/data/models/WindDirection.kt | 2 +- .../data/models/apiresponse/Clouds.kt | 12 +- .../data/models/apiresponse/Coord.kt | 22 +- .../data/models/apiresponse/Rain.kt | 12 +- .../data/models/apiresponse/Snow.kt | 12 +- .../data/models/apiresponse/WeatherModel.kt | 42 +-- .../data/models/apiresponse/Wind.kt | 20 +- .../CurrentWeatherResponse.kt | 65 ++-- .../currentweatherapiresponse/Main.kt | 22 +- .../currentweatherapiresponse/Sys.kt | 26 +- .../forecastweatherapiresponse/City.kt | 42 +-- .../DailyWeatherModel.kt | 109 +++--- .../FiveDayForecastResponse.kt | 51 ++- .../forecastweatherapiresponse/Main.kt | 106 +++--- .../forecastweatherapiresponse/Sys.kt | 6 +- .../timezonedb/TimezoneDbResponse.kt | 26 +- .../hoc/weatherapp/data/models/entity/City.kt | 74 ++-- .../models/entity/CityAndCurrentWeather.kt | 2 +- .../data/models/entity/CurrentWeather.kt | 258 ++++++------- .../data/models/entity/DailyWeather.kt | 242 ++++++------ .../data/remote/OpenWeatherMapApiService.kt | 10 +- .../data/remote/TimezoneDbApiService.kt | 28 +- .../initializer/AndroidThreeTenInitializer.kt | 4 +- .../weatherapp/initializer/KoinInitializer.kt | 2 +- .../initializer/NotificationInitializer.kt | 8 +- .../initializer/PlacesApiInitializer.kt | 2 +- .../initializer/RxJavaPluginsInitializer.kt | 2 +- .../initializer/WorkManagerInitializer.kt | 38 +- .../hoc/weatherapp/koin/DataSourceModule.kt | 17 +- .../hoc/weatherapp/koin/PresenterModule.kt | 5 +- .../com/hoc/weatherapp/koin/RetrofitModule.kt | 108 +++--- .../weatherapp/koin/SharePrefUtilModule.kt | 2 +- .../com/hoc/weatherapp/ui/BaseMviActivity.kt | 2 +- .../com/hoc/weatherapp/ui/BaseMviFragment.kt | 5 +- .../hoc/weatherapp/ui/LiveWeatherActivity.kt | 2 +- .../com/hoc/weatherapp/ui/SplashActivity.kt | 10 +- .../weatherapp/ui/addcity/AddCityActivity.kt | 2 +- .../weatherapp/ui/addcity/AddCityContract.kt | 2 +- .../weatherapp/ui/addcity/AddCityPresenter.kt | 152 ++++---- .../weatherapp/ui/cities/CitiesActivity.kt | 37 +- .../weatherapp/ui/cities/CitiesContract.kt | 28 +- .../weatherapp/ui/cities/CitiesPresenter.kt | 347 +++++++++--------- .../hoc/weatherapp/ui/cities/CityAdapter.kt | 77 ++-- .../hoc/weatherapp/ui/cities/CityListItem.kt | 18 +- .../weatherapp/ui/main/ColorHolderSource.kt | 6 +- .../hoc/weatherapp/ui/main/MainActivity.kt | 11 +- .../hoc/weatherapp/ui/main/MainContract.kt | 8 +- .../hoc/weatherapp/ui/main/MainPresenter.kt | 42 +-- .../weatherapp/ui/main/chart/ChartContract.kt | 10 +- .../weatherapp/ui/main/chart/ChartFragment.kt | 118 +++--- .../ui/main/chart/ChartPresenter.kt | 61 +-- .../ui/main/currentweather/CurrentWeather.kt | 34 +- .../currentweather/CurrentWeatherContract.kt | 15 +- .../currentweather/CurrentWeatherFragment.kt | 61 ++- .../currentweather/CurrentWeatherPresenter.kt | 255 +++++++------ .../fivedayforecast/DailyDetailActivity.kt | 2 +- .../fivedayforecast/DailyWeatherAdapter.kt | 41 ++- .../fivedayforecast/DailyWeatherContract.kt | 14 +- .../fivedayforecast/DailyWeatherListItem.kt | 34 +- .../fivedayforecast/DailyWeatherPresenter.kt | 254 +++++++------ .../com/hoc/weatherapp/ui/map/MapActivity.kt | 26 +- .../weatherapp/ui/setting/SettingsActivity.kt | 108 +++--- .../java/com/hoc/weatherapp/utils/DateUtil.kt | 4 +- .../hoc/weatherapp/utils/ExtensionFuncs.kt | 231 ++++++------ .../hoc/weatherapp/utils/MyUnsafeLazyImpl.kt | 4 +- .../hoc/weatherapp/utils/NotificationUtil.kt | 139 ++++--- .../weatherapp/utils/SharedPrefExtension.kt | 91 ++--- .../com/hoc/weatherapp/utils/UnitConverter.kt | 10 +- .../weatherapp/utils/blur/BlurImageUtil.kt | 16 +- .../utils/blur/GlideBlurTransformation.kt | 12 +- .../utils/ui/BackgroundAndSoundUtil.kt | 26 +- .../weatherapp/utils/ui/CustomViewPager.kt | 6 +- .../utils/ui/HeaderItemDecoration.kt | 7 +- .../weatherapp/utils/ui/SwipeController.kt | 170 ++++----- .../hoc/weatherapp/utils/ui/WindmillView.kt | 2 +- .../utils/ui/ZoomOutPageTransformer.kt | 4 +- .../com/hoc/weatherapp/utils/ui/rxbinding.kt | 16 +- .../worker/UpdateCurrentWeatherWorker.kt | 37 +- .../worker/UpdateDailyWeatherWorker.kt | 31 +- .../com/hoc/weatherapp/worker/WorkerUtil.kt | 40 +- app/src/main/res/anim/windmill.xml | 2 +- .../res/drawable/daily_weather_divider.xml | 2 +- app/src/main/res/drawable/default_bg.xml | 2 +- app/src/main/res/drawable/gradient_shape.xml | 2 +- app/src/main/res/drawable/ic_detail_hum.xml | 2 +- app/src/main/res/drawable/ic_detail_pcpn.xml | 2 +- .../main/res/drawable/ic_detail_pressure.xml | 2 +- .../res/drawable/ic_detail_visibility.xml | 2 +- .../res/drawable/ic_humidity_black_24dp.xml | 2 +- .../res/drawable/ic_pressure_black_24dp.xml | 2 +- .../main/res/drawable/ic_sync_black_24dp.xml | 2 +- .../drawable/ic_thermometer_black_24dp.xml | 2 +- .../drawable/ic_thermometer_white_24dp.xml | 2 +- .../main/res/drawable/ic_water_black_24dp.xml | 2 +- .../main/res/drawable/ic_water_white_24dp.xml | 2 +- .../main/res/drawable/ic_windmill_blade.xml | 2 +- .../main/res/drawable/ic_windmill_column.xml | 2 +- .../main/res/drawable/ic_windy_black_24dp.xml | 2 +- .../main/res/drawable/ic_windy_white_24dp.xml | 2 +- .../main/res/drawable/splash_background.xml | 2 +- .../res/drawable/vertial_scrollbar_thumb.xml | 2 +- .../res/drawable/white_background_ripple.xml | 2 +- app/src/main/res/layout/activity_add_city.xml | 2 +- app/src/main/res/layout/activity_cities.xml | 2 +- .../layout/activity_detail_daily_weather.xml | 2 +- .../main/res/layout/activity_live_weather.xml | 2 +- app/src/main/res/layout/activity_main.xml | 2 +- app/src/main/res/layout/activity_map.xml | 2 +- app/src/main/res/layout/city_item_layout.xml | 2 +- .../layout/daily_weather_header_layout.xml | 2 +- .../res/layout/daily_weather_item_layout.xml | 2 +- .../main/res/layout/detail_item_layout.xml | 2 +- app/src/main/res/layout/fragment_chart.xml | 2 +- .../res/layout/fragment_current_weather.xml | 2 - .../res/layout/fragment_daily_weather.xml | 2 +- app/src/main/res/layout/some_city_layout.xml | 2 +- app/src/main/res/layout/splash_demo.xml | 4 +- app/src/main/res/layout/windmill_layout.xml | 2 +- app/src/main/res/menu/menu_bottom_nav_map.xml | 2 +- app/src/main/res/menu/menu_location.xml | 2 +- .../res/mipmap-anydpi-v26/ic_launcher.xml | 2 +- .../mipmap-anydpi-v26/ic_launcher_round.xml | 2 +- app/src/main/res/xml/preferences.xml | 2 +- .../com/hoc/weatherapp/ExampleUnitTest.kt | 8 +- build.gradle | 5 +- spotless.gradle | 83 +++++ 150 files changed, 2649 insertions(+), 2360 deletions(-) create mode 100644 .editorconfig create mode 100644 .github/workflows/remove-old-artifacts.yml create mode 100644 .github/workflows/reviewdog-suggester.yml create mode 100644 spotless.gradle diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..34095d6 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +root=true +[*] +indent_size=2 +end_of_line=lf +charset=utf-8 +trim_trailing_whitespace=true +insert_final_newline=true +[*.{kt,kts}] +ij_kotlin_imports_layout=* +[*.xml] +indent_size=4 \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 798d4d1..a091bb1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,8 +3,10 @@ name: Build CI on: push: branches: [ try_mvi ] + paths-ignore: [ '**.md' ] pull_request: branches: [ try_mvi ] + paths-ignore: [ '**.md' ] jobs: build: @@ -25,9 +27,6 @@ jobs: env: BASEURL: ${{ secrets.PLACE_API_KEY }} run: echo PLACE_API_KEY="$PLACE_API_KEY" > ./local.properties - - # - name: Spotless check - # run: ./gradlew spotlessCheck - name: Build debug APK uses: gradle/gradle-build-action@v2 diff --git a/.github/workflows/remove-old-artifacts.yml b/.github/workflows/remove-old-artifacts.yml new file mode 100644 index 0000000..d8c854b --- /dev/null +++ b/.github/workflows/remove-old-artifacts.yml @@ -0,0 +1,19 @@ +name: Remove old artifacts + +on: + schedule: + # Runs at 01:00 UTC on the 1, 8, 15, 22 and 29th of every month. + - cron: '0 1 */7 * *' + +jobs: + remove-old-artifacts: + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Remove old artifacts + uses: c-hive/gha-remove-artifacts@v1 + with: + age: '7 days' + skip-tags: true + skip-recent: 5 diff --git a/.github/workflows/reviewdog-suggester.yml b/.github/workflows/reviewdog-suggester.yml new file mode 100644 index 0000000..be69aca --- /dev/null +++ b/.github/workflows/reviewdog-suggester.yml @@ -0,0 +1,57 @@ +name: reviewdog-suggester +on: + pull_request: + types: [ opened, synchronize, reopened ] + paths-ignore: [ '**.md' ] + +jobs: + kotlin: + name: runner / suggester / spotless + runs-on: ubuntu-latest + steps: + - name: Check try_mvi branch + uses: actions/checkout@v3 + with: + ref: try_mvi + + - name: Checkout current branch + uses: actions/checkout@v3 + + - name: Setup Java + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '11' + + - name: Cache gradle, wrapper and buildSrc + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-${{ github.job }}-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}-${{ hashFiles('**/buildSrc/**/*.kt') }} + restore-keys: | + ${{ runner.os }}-${{ github.job }}- + + - name: Make gradlew executable + run: chmod +x ./gradlew + + - name: Decode PLACE_API_KEY + env: + BASEURL: ${{ secrets.PLACE_API_KEY }} + run: echo PLACE_API_KEY="$PLACE_API_KEY" > ./local.properties + + - name: Spotless apply + run: ./gradlew spotlessKotlinApply + + - name: Reviewdog + uses: reviewdog/action-suggester@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + tool_name: spotless + + - name: Make gradlew executable + run: chmod +x ./gradlew + + - name: Spotless check + run: ./gradlew spotlessKotlinCheck diff --git a/app/src/androidTest/java/com/hoc/weatherapp/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/hoc/weatherapp/ExampleInstrumentedTest.kt index 5e98d34..1cd1dd0 100644 --- a/app/src/androidTest/java/com/hoc/weatherapp/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/com/hoc/weatherapp/ExampleInstrumentedTest.kt @@ -13,10 +13,10 @@ import org.junit.runner.RunWith */ @RunWith(AndroidJUnit4::class) class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getTargetContext() - assertEquals("com.hoc.weatherapp", appContext.packageName) - } + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getTargetContext() + assertEquals("com.hoc.weatherapp", appContext.packageName) + } } diff --git a/app/src/main/java/com/hoc/weatherapp/CancelNotificationReceiver.kt b/app/src/main/java/com/hoc/weatherapp/CancelNotificationReceiver.kt index 1e04867..3cd70b0 100644 --- a/app/src/main/java/com/hoc/weatherapp/CancelNotificationReceiver.kt +++ b/app/src/main/java/com/hoc/weatherapp/CancelNotificationReceiver.kt @@ -26,22 +26,22 @@ class CancelNotificationReceiver : BroadcastReceiver(), KoinComponent { context.cancelNotificationById(WEATHER_NOTIFICATION_ID) Completable - .fromCallable { settingPreferences.showNotificationPreference.saveActual(false) } - .subscribeOn(Schedulers.single()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnComplete { settingPreferences.showNotificationPreference.save(false) } - .doOnTerminate { pendingResult.finish() } - .subscribe(object : CompletableObserver { - override fun onComplete() { - LocalBroadcastManager - .getInstance(context) - .sendBroadcast(Intent(ACTION_CANCEL_NOTIFICATION)) - debug("[SUCCESS] showNotificationPreference", TAG) - } + .fromCallable { settingPreferences.showNotificationPreference.saveActual(false) } + .subscribeOn(Schedulers.single()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnComplete { settingPreferences.showNotificationPreference.save(false) } + .doOnTerminate { pendingResult.finish() } + .subscribe(object : CompletableObserver { + override fun onComplete() { + LocalBroadcastManager + .getInstance(context) + .sendBroadcast(Intent(ACTION_CANCEL_NOTIFICATION)) + debug("[SUCCESS] showNotificationPreference", TAG) + } - override fun onSubscribe(d: Disposable) = Unit - override fun onError(e: Throwable) = Unit - }) + override fun onSubscribe(d: Disposable) = Unit + override fun onError(e: Throwable) = Unit + }) } } diff --git a/app/src/main/java/com/hoc/weatherapp/data/CityRepository.kt b/app/src/main/java/com/hoc/weatherapp/data/CityRepository.kt index 4cc6b92..6cea3a6 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/CityRepository.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/CityRepository.kt @@ -6,7 +6,6 @@ import io.reactivex.Completable import io.reactivex.Observable import io.reactivex.Single - interface CityRepository { /** * Change selected city to [city] @@ -40,4 +39,4 @@ interface CityRepository { * Synchronous access selected city */ val selectedCity: City? -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/data/CityRepositoryImpl.kt b/app/src/main/java/com/hoc/weatherapp/data/CityRepositoryImpl.kt index 875313f..e75e86b 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/CityRepositoryImpl.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/CityRepositoryImpl.kt @@ -10,19 +10,23 @@ import com.hoc.weatherapp.data.models.entity.City import com.hoc.weatherapp.data.remote.OpenWeatherMapApiService import com.hoc.weatherapp.data.remote.TimezoneDbApiService import com.hoc.weatherapp.data.remote.getZoneId -import com.hoc.weatherapp.utils.* +import com.hoc.weatherapp.utils.None +import com.hoc.weatherapp.utils.Optional +import com.hoc.weatherapp.utils.Some +import com.hoc.weatherapp.utils.debug +import com.hoc.weatherapp.utils.getOrNull import io.reactivex.Completable import io.reactivex.Single import io.reactivex.rxkotlin.Singles import io.reactivex.schedulers.Schedulers class CityRepositoryImpl( - private val openWeatherMapApiService: OpenWeatherMapApiService, - private val timezoneDbApiService: TimezoneDbApiService, - private val cityLocalDataSource: CityLocalDataSource, - private val fiveDayForecastLocalDataSource: FiveDayForecastLocalDataSource, - private val currentWeatherLocalDataSource: CurrentWeatherLocalDataSource, - private val selectedCityPreference: SelectedCityPreference + private val openWeatherMapApiService: OpenWeatherMapApiService, + private val timezoneDbApiService: TimezoneDbApiService, + private val cityLocalDataSource: CityLocalDataSource, + private val fiveDayForecastLocalDataSource: FiveDayForecastLocalDataSource, + private val currentWeatherLocalDataSource: CurrentWeatherLocalDataSource, + private val selectedCityPreference: SelectedCityPreference ) : CityRepository { override val selectedCity get() = selectedCityPreference.value.getOrNull() @@ -31,51 +35,51 @@ class CityRepositoryImpl( override fun deleteCity(city: City): Single { return Completable.mergeArray( - cityLocalDataSource - .deleteCity(city) - .subscribeOn(Schedulers.io()), - /** - * If [city] is current selected city, then [changeSelectedCity] to null - */ - Single - .fromCallable { selectedCityPreference.value } - .filter { it.getOrNull() == city } - .flatMapCompletable { changeSelectedCity(None) } + cityLocalDataSource + .deleteCity(city) + .subscribeOn(Schedulers.io()), + /** + * If [city] is current selected city, then [changeSelectedCity] to null + */ + Single + .fromCallable { selectedCityPreference.value } + .filter { it.getOrNull() == city } + .flatMapCompletable { changeSelectedCity(None) } ).toSingleDefault(city) } override fun addCityByLatLng(latitude: Double, longitude: Double): Single { return Singles.zip( - openWeatherMapApiService - .getCurrentWeatherByLatLng(latitude, longitude) - .subscribeOn(Schedulers.io()), - getZoneId(timezoneDbApiService, latitude, longitude) + openWeatherMapApiService + .getCurrentWeatherByLatLng(latitude, longitude) + .subscribeOn(Schedulers.io()), + getZoneId(timezoneDbApiService, latitude, longitude) + ) + .flatMap { + debug("@@@@@@@$it", "@@@@") + saveCityAndCurrentWeather( + cityLocalDataSource, + currentWeatherLocalDataSource, + it.first, + it.second ) - .flatMap { - debug("@@@@@@@$it", "@@@@") - saveCityAndCurrentWeather( - cityLocalDataSource, - currentWeatherLocalDataSource, - it.first, - it.second - ) - } - .map { it.city } - .flatMap { city -> - openWeatherMapApiService - .get5DayEvery3HourForecastByCityId(city.id) - .subscribeOn(Schedulers.io()) - .flatMap { saveFiveDayForecastWeather(fiveDayForecastLocalDataSource, it) } - .map { city } - } + } + .map { it.city } + .flatMap { city -> + openWeatherMapApiService + .get5DayEvery3HourForecastByCityId(city.id) + .subscribeOn(Schedulers.io()) + .flatMap { saveFiveDayForecastWeather(fiveDayForecastLocalDataSource, it) } + .map { city } + } } override fun changeSelectedCity(city: City) = changeSelectedCity(Some(city)) private fun changeSelectedCity(optionalCity: Optional): Completable { return Completable - .fromCallable { selectedCityPreference.save(optionalCity) } - .subscribeOn(Schedulers.single()) - .onErrorResumeNext { Completable.error(SaveSelectedCityError(it)) } + .fromCallable { selectedCityPreference.save(optionalCity) } + .subscribeOn(Schedulers.single()) + .onErrorResumeNext { Completable.error(SaveSelectedCityError(it)) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/data/CurrentWeatherRepository.kt b/app/src/main/java/com/hoc/weatherapp/data/CurrentWeatherRepository.kt index f39af8f..5a4adfa 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/CurrentWeatherRepository.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/CurrentWeatherRepository.kt @@ -20,7 +20,6 @@ interface CurrentWeatherRepository { */ fun refreshCurrentWeatherOfSelectedCity(): Single - /** * Get pair of selected city and current weather, get from local database * @return [Observable] that emits [Optional]s of [CityAndCurrentWeather], None when having no selected city @@ -32,4 +31,4 @@ interface CurrentWeatherRepository { * @return [Observable] that emits [List]s of [CityAndCurrentWeather] */ fun getAllCityAndCurrentWeathers(querySearch: String): Observable> -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/data/CurrentWeatherRepositoryImpl.kt b/app/src/main/java/com/hoc/weatherapp/data/CurrentWeatherRepositoryImpl.kt index 713698d..5d14371 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/CurrentWeatherRepositoryImpl.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/CurrentWeatherRepositoryImpl.kt @@ -20,79 +20,81 @@ import io.reactivex.rxkotlin.zipWith import io.reactivex.schedulers.Schedulers class CurrentWeatherRepositoryImpl( - private val openWeatherMapApiService: OpenWeatherMapApiService, - private val timezoneDbApiService: TimezoneDbApiService, - private val currentWeatherLocalDataSource: CurrentWeatherLocalDataSource, - private val fiveDayForecastLocalDataSource: FiveDayForecastLocalDataSource, - private val cityLocalDataSource: CityLocalDataSource, - private val selectedCityPreference: SelectedCityPreference + private val openWeatherMapApiService: OpenWeatherMapApiService, + private val timezoneDbApiService: TimezoneDbApiService, + private val currentWeatherLocalDataSource: CurrentWeatherLocalDataSource, + private val fiveDayForecastLocalDataSource: FiveDayForecastLocalDataSource, + private val cityLocalDataSource: CityLocalDataSource, + private val selectedCityPreference: SelectedCityPreference ) : CurrentWeatherRepository { private val selectedCityAndCurrentWeatherObservable = - selectedCityPreference - .observable - .distinctUntilChanged() - .switchMap { optionalCity -> - when (optionalCity) { - is Some -> currentWeatherLocalDataSource - .getCityAndCurrentWeatherByCityId(optionalCity.value.id) - .subscribeOn(Schedulers.io()) - .map(::Some) - is None -> Observable.just(None) - } - } - .replay(1) - .autoConnect(0) + selectedCityPreference + .observable + .distinctUntilChanged() + .switchMap { optionalCity -> + when (optionalCity) { + is Some -> + currentWeatherLocalDataSource + .getCityAndCurrentWeatherByCityId(optionalCity.value.id) + .subscribeOn(Schedulers.io()) + .map(::Some) + is None -> Observable.just(None) + } + } + .replay(1) + .autoConnect(0) private val refreshCurrentWeatherOfSelectedCitySingle = - Single.fromCallable { selectedCityPreference.value } - .flatMap { cityOptional -> - when (cityOptional) { - is None -> Single.error(NoSelectedCityException) - is Some -> openWeatherMapApiService - .getCurrentWeatherByCityId(cityOptional.value.id) - .subscribeOn(Schedulers.io()) - .zipWith(getZoneIdIfNeeded(cityOptional.value)) - .flatMap { - saveCityAndCurrentWeather( - cityLocalDataSource, - currentWeatherLocalDataSource, - it.first, - it.second - ) - } - } - } + Single.fromCallable { selectedCityPreference.value } + .flatMap { cityOptional -> + when (cityOptional) { + is None -> Single.error(NoSelectedCityException) + is Some -> + openWeatherMapApiService + .getCurrentWeatherByCityId(cityOptional.value.id) + .subscribeOn(Schedulers.io()) + .zipWith(getZoneIdIfNeeded(cityOptional.value)) + .flatMap { + saveCityAndCurrentWeather( + cityLocalDataSource, + currentWeatherLocalDataSource, + it.first, + it.second + ) + } + } + } override fun getAllCityAndCurrentWeathers(querySearch: String): Observable> { return currentWeatherLocalDataSource - .getAllCityAndCurrentWeathers(querySearch) - .subscribeOn(Schedulers.io()) + .getAllCityAndCurrentWeathers(querySearch) + .subscribeOn(Schedulers.io()) } override fun getSelectedCityAndCurrentWeatherOfSelectedCity() = - selectedCityAndCurrentWeatherObservable + selectedCityAndCurrentWeatherObservable override fun refreshCurrentWeatherOfSelectedCity() = refreshCurrentWeatherOfSelectedCitySingle override fun refreshWeatherOf(city: City): Single>> { return openWeatherMapApiService - .getCurrentWeatherByCityId(city.id) - .subscribeOn(Schedulers.io()) - .zipWith(getZoneIdIfNeeded(city)) - .flatMap { - saveCityAndCurrentWeather( - cityLocalDataSource, - currentWeatherLocalDataSource, - it.first, - it.second - ) - } - .zipWith( - openWeatherMapApiService - .get5DayEvery3HourForecastByCityId(city.id) - .subscribeOn(Schedulers.io()) - .flatMap { saveFiveDayForecastWeather(fiveDayForecastLocalDataSource, it) } + .getCurrentWeatherByCityId(city.id) + .subscribeOn(Schedulers.io()) + .zipWith(getZoneIdIfNeeded(city)) + .flatMap { + saveCityAndCurrentWeather( + cityLocalDataSource, + currentWeatherLocalDataSource, + it.first, + it.second ) + } + .zipWith( + openWeatherMapApiService + .get5DayEvery3HourForecastByCityId(city.id) + .subscribeOn(Schedulers.io()) + .flatMap { saveFiveDayForecastWeather(fiveDayForecastLocalDataSource, it) } + ) } private fun getZoneIdIfNeeded(city: City): Single { @@ -102,4 +104,4 @@ class CurrentWeatherRepositoryImpl( getZoneId(timezoneDbApiService, city.lat, city.lng) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/data/FiveDayForecastRepository.kt b/app/src/main/java/com/hoc/weatherapp/data/FiveDayForecastRepository.kt index 314327f..8ec6658 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/FiveDayForecastRepository.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/FiveDayForecastRepository.kt @@ -18,4 +18,4 @@ interface FiveDayForecastRepository { * @return [Single] emit result or error, emit [NoSelectedCityException] when having no selected city */ fun refreshFiveDayForecastOfSelectedCity(): Single>> -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/data/FiveDayForecastRepositoryImpl.kt b/app/src/main/java/com/hoc/weatherapp/data/FiveDayForecastRepositoryImpl.kt index 36b53af..2cf156c 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/FiveDayForecastRepositoryImpl.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/FiveDayForecastRepositoryImpl.kt @@ -10,44 +10,46 @@ import io.reactivex.Single import io.reactivex.schedulers.Schedulers class FiveDayForecastRepositoryImpl( - private val openWeatherMapApiService: OpenWeatherMapApiService, - private val fiveDayForecastLocalDataSource: FiveDayForecastLocalDataSource, - private val selectedCityPreference: SelectedCityPreference + private val openWeatherMapApiService: OpenWeatherMapApiService, + private val fiveDayForecastLocalDataSource: FiveDayForecastLocalDataSource, + private val selectedCityPreference: SelectedCityPreference ) : FiveDayForecastRepository { private val fiveDayForecastObservable = selectedCityPreference - .observable - .distinctUntilChanged() - .switchMap { optional -> - when (optional) { - is Some -> fiveDayForecastLocalDataSource - .getAllDailyWeathersByCityId(optional.value.id) - .subscribeOn(Schedulers.io()) - .map { optional.value to it } - .map(::Some) - is None -> Observable.just(None) - } + .observable + .distinctUntilChanged() + .switchMap { optional -> + when (optional) { + is Some -> + fiveDayForecastLocalDataSource + .getAllDailyWeathersByCityId(optional.value.id) + .subscribeOn(Schedulers.io()) + .map { optional.value to it } + .map(::Some) + is None -> Observable.just(None) } - .replay(1) - .autoConnect(0) + } + .replay(1) + .autoConnect(0) private val refreshFiveDayForecast = Single - .fromCallable { selectedCityPreference.value } - .flatMap { cityOptional -> - when (cityOptional) { - is None -> Single.error(NoSelectedCityException) - is Some -> openWeatherMapApiService - .get5DayEvery3HourForecastByCityId(cityOptional.value.id) - .subscribeOn(Schedulers.io()) - .flatMap { - LocalDataSourceUtil.saveFiveDayForecastWeather( - fiveDayForecastLocalDataSource, - it - ) - } - .map { cityOptional.value to it } - } + .fromCallable { selectedCityPreference.value } + .flatMap { cityOptional -> + when (cityOptional) { + is None -> Single.error(NoSelectedCityException) + is Some -> + openWeatherMapApiService + .get5DayEvery3HourForecastByCityId(cityOptional.value.id) + .subscribeOn(Schedulers.io()) + .flatMap { + LocalDataSourceUtil.saveFiveDayForecastWeather( + fiveDayForecastLocalDataSource, + it + ) + } + .map { cityOptional.value to it } } + } override fun getFiveDayForecastOfSelectedCity() = fiveDayForecastObservable override fun refreshFiveDayForecastOfSelectedCity() = refreshFiveDayForecast -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/data/LocalDataSourceUtil.kt b/app/src/main/java/com/hoc/weatherapp/data/LocalDataSourceUtil.kt index 3b73888..d23b621 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/LocalDataSourceUtil.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/LocalDataSourceUtil.kt @@ -20,26 +20,26 @@ object LocalDataSourceUtil { */ @JvmStatic fun saveCityAndCurrentWeather( - cityLocalDataSource: CityLocalDataSource, - currentWeatherLocalDataSource: CurrentWeatherLocalDataSource, - response: CurrentWeatherResponse, - zoneId: String + cityLocalDataSource: CityLocalDataSource, + currentWeatherLocalDataSource: CurrentWeatherLocalDataSource, + response: CurrentWeatherResponse, + zoneId: String ): Single { val city = Mapper.responseToCity(response, zoneId) val weather = Mapper.responseToCurrentWeatherEntity(response) return cityLocalDataSource - .insertOrUpdateCity(city) - .andThen( - currentWeatherLocalDataSource - .insertOrUpdateCurrentWeather(weather) - ) - .toSingleDefault( - CityAndCurrentWeather().apply { - this.city = city - this.currentWeather = weather - } - ) - .subscribeOn(Schedulers.io()) + .insertOrUpdateCity(city) + .andThen( + currentWeatherLocalDataSource + .insertOrUpdateCurrentWeather(weather) + ) + .toSingleDefault( + CityAndCurrentWeather().apply { + this.city = city + this.currentWeather = weather + } + ) + .subscribeOn(Schedulers.io()) } /** @@ -50,14 +50,14 @@ object LocalDataSourceUtil { */ @JvmStatic fun saveFiveDayForecastWeather( - fiveDayForecastLocalDataSource: FiveDayForecastLocalDataSource, - response: FiveDayForecastResponse + fiveDayForecastLocalDataSource: FiveDayForecastLocalDataSource, + response: FiveDayForecastResponse ): Single> { val city = Mapper.responseToCity(response) val weathers = Mapper.responseToListDailyWeatherEntity(response) return fiveDayForecastLocalDataSource - .deleteDailyWeathersByCityIdAndInsert(weathers = weathers, cityId = city.id) - .toSingleDefault(weathers) - .subscribeOn(Schedulers.io()) + .deleteDailyWeathersByCityIdAndInsert(weathers = weathers, cityId = city.id) + .toSingleDefault(weathers) + .subscribeOn(Schedulers.io()) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/data/Mapper.kt b/app/src/main/java/com/hoc/weatherapp/data/Mapper.kt index 99e426e..af37de4 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/Mapper.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/Mapper.kt @@ -19,23 +19,23 @@ object Mapper { val firstWeather = weather?.first() DailyWeather( - timeOfDataForecasted = Date((dt ?: 0) * 1_000), - cloudiness = clouds?.all ?: 0, - description = firstWeather?.description ?: "No description", - main = firstWeather?.main ?: "No main weather", - groundLevel = main?.grndLevel ?: 0.0, - humidity = main?.humidity ?: 0, - icon = firstWeather?.icon ?: "", - pressure = main?.pressure ?: 0.0, - rainVolumeForTheLast3Hours = rain?._3h ?: 0.0, - snowVolumeForTheLast3Hours = snow?._3h ?: 0.0, - seaLevel = main?.seaLevel ?: 0.0, - temperature = main?.temp ?: 0.0, - temperatureMax = main?.tempMax ?: 0.0, - temperatureMin = main?.tempMin ?: 0.0, - winDegrees = wind?.deg ?: 0.0, - windSpeed = wind?.speed ?: 0.0, - cityId = response.city?.id ?: -1 + timeOfDataForecasted = Date((dt ?: 0) * 1_000), + cloudiness = clouds?.all ?: 0, + description = firstWeather?.description ?: "No description", + main = firstWeather?.main ?: "No main weather", + groundLevel = main?.grndLevel ?: 0.0, + humidity = main?.humidity ?: 0, + icon = firstWeather?.icon ?: "", + pressure = main?.pressure ?: 0.0, + rainVolumeForTheLast3Hours = rain?._3h ?: 0.0, + snowVolumeForTheLast3Hours = snow?._3h ?: 0.0, + seaLevel = main?.seaLevel ?: 0.0, + temperature = main?.temp ?: 0.0, + temperatureMax = main?.tempMax ?: 0.0, + temperatureMin = main?.tempMin ?: 0.0, + winDegrees = wind?.deg ?: 0.0, + windSpeed = wind?.speed ?: 0.0, + cityId = response.city?.id ?: -1 ) } }.orEmpty().onEach { debug(it.cityId, "@#@#") } @@ -47,26 +47,26 @@ object Mapper { val firstWeather = weather?.first() CurrentWeather( - cityId = id ?: Long.MIN_VALUE, - cloudiness = clouds?.all ?: 0, - main = firstWeather?.main ?: "No main weather", - description = firstWeather?.description - ?: "No description", - icon = firstWeather?.icon ?: "", - temperature = main?.temp ?: 0.0, - pressure = main?.pressure ?: 0.0, - humidity = main?.humidity ?: 0, - temperatureMin = main?.tempMin ?: 0.0, - temperatureMax = main?.tempMax ?: 0.0, - winSpeed = wind?.speed ?: 0.0, - winDegrees = wind?.deg ?: 0.0, - dataTime = Date((dt ?: 0) * 1_000), - snowVolumeForThe3Hours = snow?._3h ?: 0.0, - rainVolumeForThe3Hours = rain?._3h ?: 0.0, - visibility = visibility ?: 0.0, - sunrise = Date((sys?.sunrise ?: 0) * 1_000), - sunset = Date((sys?.sunset ?: 0) * 1_000), - weatherConditionId = firstWeather?.id ?: -1 + cityId = id ?: Long.MIN_VALUE, + cloudiness = clouds?.all ?: 0, + main = firstWeather?.main ?: "No main weather", + description = firstWeather?.description + ?: "No description", + icon = firstWeather?.icon ?: "", + temperature = main?.temp ?: 0.0, + pressure = main?.pressure ?: 0.0, + humidity = main?.humidity ?: 0, + temperatureMin = main?.tempMin ?: 0.0, + temperatureMax = main?.tempMax ?: 0.0, + winSpeed = wind?.speed ?: 0.0, + winDegrees = wind?.deg ?: 0.0, + dataTime = Date((dt ?: 0) * 1_000), + snowVolumeForThe3Hours = snow?._3h ?: 0.0, + rainVolumeForThe3Hours = rain?._3h ?: 0.0, + visibility = visibility ?: 0.0, + sunrise = Date((sys?.sunrise ?: 0) * 1_000), + sunset = Date((sys?.sunset ?: 0) * 1_000), + weatherConditionId = firstWeather?.id ?: -1 ) } } @@ -74,12 +74,12 @@ object Mapper { @JvmStatic fun responseToCity(response: CurrentWeatherResponse, zoneId: String): City { return City( - id = response.id ?: Long.MIN_VALUE, - name = response.name ?: "", - country = response.sys?.country ?: "", - lng = response.coord?.lon ?: Double.NEGATIVE_INFINITY, - lat = response.coord?.lat ?: Double.NEGATIVE_INFINITY, - zoneId = zoneId + id = response.id ?: Long.MIN_VALUE, + name = response.name ?: "", + country = response.sys?.country ?: "", + lng = response.coord?.lon ?: Double.NEGATIVE_INFINITY, + lat = response.coord?.lat ?: Double.NEGATIVE_INFINITY, + zoneId = zoneId ) } @@ -87,12 +87,12 @@ object Mapper { fun responseToCity(response: FiveDayForecastResponse): City { val city = response.city return City( - id = city?.id ?: Long.MIN_VALUE, - name = city?.name ?: "", - country = city?.country ?: "", - lng = city?.coord?.lon ?: Double.NEGATIVE_INFINITY, - lat = city?.coord?.lat ?: Double.NEGATIVE_INFINITY, - zoneId = "" // not need + id = city?.id ?: Long.MIN_VALUE, + name = city?.name ?: "", + country = city?.country ?: "", + lng = city?.coord?.lon ?: Double.NEGATIVE_INFINITY, + lat = city?.coord?.lat ?: Double.NEGATIVE_INFINITY, + zoneId = "" // not need ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/data/local/AppDatabase.kt b/app/src/main/java/com/hoc/weatherapp/data/local/AppDatabase.kt index 2f301ac..496bfe4 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/local/AppDatabase.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/local/AppDatabase.kt @@ -1,13 +1,17 @@ package com.hoc.weatherapp.data.local import android.content.Context -import androidx.room.* +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.room.TypeConverter +import androidx.room.TypeConverters import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import com.hoc.weatherapp.data.models.entity.City import com.hoc.weatherapp.data.models.entity.CurrentWeather import com.hoc.weatherapp.data.models.entity.DailyWeather -import java.util.* +import java.util.Date object Converters { @JvmStatic @@ -20,13 +24,13 @@ object Converters { } @Database( - entities = [ - CurrentWeather::class, - City::class, - DailyWeather::class - ], - version = 2, - exportSchema = false + entities = [ + CurrentWeather::class, + City::class, + DailyWeather::class + ], + version = 2, + exportSchema = false ) @TypeConverters(value = [Converters::class]) abstract class AppDatabase : RoomDatabase() { @@ -43,19 +47,20 @@ abstract class AppDatabase : RoomDatabase() { } } - @Volatile private var instance: AppDatabase? = null + @Volatile + private var instance: AppDatabase? = null fun getInstance(context: Context): AppDatabase { return instance ?: synchronized(this::class.java) { instance ?: Room.databaseBuilder( - context, - AppDatabase::class.java, - DATABASE_NAME - ) - .addMigrations(MIGRATION_1_2) - .build() - .also { instance = it } + context, + AppDatabase::class.java, + DATABASE_NAME + ) + .addMigrations(MIGRATION_1_2) + .build() + .also { instance = it } } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/data/local/CityDao.kt b/app/src/main/java/com/hoc/weatherapp/data/local/CityDao.kt index 8e9263b..dc6ed3c 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/local/CityDao.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/local/CityDao.kt @@ -1,7 +1,11 @@ package com.hoc.weatherapp.data.local -import androidx.room.* +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert import androidx.room.OnConflictStrategy.IGNORE +import androidx.room.Transaction +import androidx.room.Update import com.hoc.weatherapp.data.models.entity.City import com.hoc.weatherapp.utils.debug @@ -19,10 +23,10 @@ abstract class CityDao { @Transaction open fun upsert(city: City) { insertCity(city) - .takeIf { - debug("insertCity => $it", "__DAO__") - it == -1L - } - ?.let { updateCity(city) } + .takeIf { + debug("insertCity => $it", "__DAO__") + it == -1L + } + ?.let { updateCity(city) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/data/local/CityLocalDataSource.kt b/app/src/main/java/com/hoc/weatherapp/data/local/CityLocalDataSource.kt index 4a3a887..d630aae 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/local/CityLocalDataSource.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/local/CityLocalDataSource.kt @@ -19,4 +19,4 @@ class CityLocalDataSource(private val cityDao: CityDao) { cityDao.upsert(city) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/data/local/CurrentWeatherDao.kt b/app/src/main/java/com/hoc/weatherapp/data/local/CurrentWeatherDao.kt index a5a4670..1cfce49 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/local/CurrentWeatherDao.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/local/CurrentWeatherDao.kt @@ -1,6 +1,11 @@ package com.hoc.weatherapp.data.local -import androidx.room.* +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Transaction +import androidx.room.Update import com.hoc.weatherapp.data.models.entity.CityAndCurrentWeather import com.hoc.weatherapp.data.models.entity.CurrentWeather import com.hoc.weatherapp.utils.debug @@ -9,13 +14,13 @@ import io.reactivex.Observable @Dao abstract class CurrentWeatherDao { @Query( - """SELECT * FROM current_weathers INNER JOIN cities ON current_weathers.city_id = cities.id + """SELECT * FROM current_weathers INNER JOIN cities ON current_weathers.city_id = cities.id WHERE city_id = :cityId LIMIT 1""" ) abstract fun getCityAndCurrentWeatherByCityId(cityId: Long): Observable @Query( - """SELECT * FROM current_weathers INNER JOIN cities ON current_weathers.city_id = cities.id + """SELECT * FROM current_weathers INNER JOIN cities ON current_weathers.city_id = cities.id WHERE cities.name LIKE '%' || :querySearch || '%' OR cities.country LIKE '%' || :querySearch || '%' OR current_weathers.description LIKE '%' || :querySearch || '%' @@ -33,10 +38,10 @@ abstract class CurrentWeatherDao { @Transaction open fun upsert(weather: CurrentWeather) { insertCurrentWeather(weather) - .takeIf { - debug("insertCurrentWeather => $it", "__DAO__") - it == -1L - } - ?.let { updateCurrentWeather(weather) } + .takeIf { + debug("insertCurrentWeather => $it", "__DAO__") + it == -1L + } + ?.let { updateCurrentWeather(weather) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/data/local/CurrentWeatherLocalDataSource.kt b/app/src/main/java/com/hoc/weatherapp/data/local/CurrentWeatherLocalDataSource.kt index f90f784..b89b4f5 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/local/CurrentWeatherLocalDataSource.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/local/CurrentWeatherLocalDataSource.kt @@ -12,14 +12,14 @@ import io.reactivex.Observable class CurrentWeatherLocalDataSource(private val currentWeatherDao: CurrentWeatherDao) { fun getCityAndCurrentWeatherByCityId(cityId: Long): Observable { return currentWeatherDao - .getCityAndCurrentWeatherByCityId(cityId) - .distinctUntilChanged() + .getCityAndCurrentWeatherByCityId(cityId) + .distinctUntilChanged() } fun getAllCityAndCurrentWeathers(querySearch: String): Observable> { return currentWeatherDao - .getAllCityAndCurrentWeathers(querySearch) - .distinctUntilChanged() + .getAllCityAndCurrentWeathers(querySearch) + .distinctUntilChanged() } fun insertOrUpdateCurrentWeather(weather: CurrentWeather): Completable { @@ -27,4 +27,4 @@ class CurrentWeatherLocalDataSource(private val currentWeatherDao: CurrentWeathe currentWeatherDao.upsert(weather) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/data/local/FiveDayForecastDao.kt b/app/src/main/java/com/hoc/weatherapp/data/local/FiveDayForecastDao.kt index f1e0d56..4e931f0 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/local/FiveDayForecastDao.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/local/FiveDayForecastDao.kt @@ -1,6 +1,10 @@ package com.hoc.weatherapp.data.local -import androidx.room.* +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Transaction import com.hoc.weatherapp.data.models.entity.DailyWeather import io.reactivex.Observable @@ -20,4 +24,4 @@ abstract class FiveDayForecastDao { deleteAllDailyWeathersByCityId(cityId) insertDailyWeathers(weathers) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/data/local/FiveDayForecastLocalDataSource.kt b/app/src/main/java/com/hoc/weatherapp/data/local/FiveDayForecastLocalDataSource.kt index e30f746..6fa656d 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/local/FiveDayForecastLocalDataSource.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/local/FiveDayForecastLocalDataSource.kt @@ -10,11 +10,11 @@ class FiveDayForecastLocalDataSource(private val fiveDayForecastDao: FiveDayFore } fun deleteDailyWeathersByCityIdAndInsert( - cityId: Long, - weathers: List + cityId: Long, + weathers: List ): Completable { return Completable.fromAction { fiveDayForecastDao.deleteDailyWeathersByCityIdAndInsert(cityId, weathers) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/data/local/SelectedCityPreference.kt b/app/src/main/java/com/hoc/weatherapp/data/local/SelectedCityPreference.kt index 107f839..e9b7f0f 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/local/SelectedCityPreference.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/local/SelectedCityPreference.kt @@ -3,7 +3,12 @@ package com.hoc.weatherapp.data.local import android.content.SharedPreferences import androidx.annotation.WorkerThread import com.hoc.weatherapp.data.models.entity.City -import com.hoc.weatherapp.utils.* +import com.hoc.weatherapp.utils.None +import com.hoc.weatherapp.utils.Optional +import com.hoc.weatherapp.utils.asObservable +import com.hoc.weatherapp.utils.delegate +import com.hoc.weatherapp.utils.getOrNull +import com.hoc.weatherapp.utils.toOptional import com.squareup.moshi.Moshi import io.reactivex.Single import io.reactivex.SingleObserver @@ -12,28 +17,28 @@ import io.reactivex.schedulers.Schedulers import io.reactivex.subjects.BehaviorSubject class SelectedCityPreference(sharedPreferences: SharedPreferences, private val moshi: Moshi) : - PreferenceInterface> { + PreferenceInterface> { private var selectedCityJsonString by sharedPreferences.delegate() private val citySubject = BehaviorSubject.createDefault>(None) init { Single - .fromCallable(::getSelectedCityFromSharedPref) - .subscribeOn(Schedulers.single()) - .onErrorReturnItem(None) - .subscribe(object : SingleObserver> { - override fun onSuccess(t: Optional) = citySubject.onNext(t) - override fun onSubscribe(d: Disposable) = Unit - override fun onError(e: Throwable) = Unit - }) + .fromCallable(::getSelectedCityFromSharedPref) + .subscribeOn(Schedulers.single()) + .onErrorReturnItem(None) + .subscribe(object : SingleObserver> { + override fun onSuccess(t: Optional) = citySubject.onNext(t) + override fun onSubscribe(d: Disposable) = Unit + override fun onError(e: Throwable) = Unit + }) } @WorkerThread private fun getSelectedCityFromSharedPref(): Optional { return runCatching { moshi - .adapter(City::class.java) - .fromJson(selectedCityJsonString) + .adapter(City::class.java) + .fromJson(selectedCityJsonString) }.getOrNull().toOptional() } @@ -44,8 +49,8 @@ class SelectedCityPreference(sharedPreferences: SharedPreferences, private val m @WorkerThread override fun save(value: Optional) { selectedCityJsonString = moshi - .adapter(City::class.java) - .toJson(value.getOrNull()) + .adapter(City::class.java) + .toJson(value.getOrNull()) citySubject.onNext(value) } @@ -53,4 +58,3 @@ class SelectedCityPreference(sharedPreferences: SharedPreferences, private val m override val value @WorkerThread get() = getSelectedCityFromSharedPref() } - diff --git a/app/src/main/java/com/hoc/weatherapp/data/local/SettingPreferences.kt b/app/src/main/java/com/hoc/weatherapp/data/local/SettingPreferences.kt index 32b3083..ff65f76 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/local/SettingPreferences.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/local/SettingPreferences.kt @@ -31,7 +31,7 @@ interface PreferenceInterface { * Base class implement [PreferenceInterface], used to get and save value setting */ class BaseSettingPreference(delegateProperty: ReadWriteProperty) : - PreferenceInterface { + PreferenceInterface { private var _value by delegateProperty private val subject = BehaviorSubject.createDefault(_value) @@ -59,69 +59,69 @@ class BaseSettingPreference(delegateProperty: ReadWriteProperty class SettingPreferences(sharedPreferences: SharedPreferences, androidApplication: Application) { val temperatureUnitPreference = BaseSettingPreference( - delegateProperty = sharedPreferences.delegateVar( - getter = { key, defaultValue -> - getString(key, defaultValue.toString())!!.let { TemperatureUnit.fromString(it) } - }, - setter = { key, value -> putString(key, value.toString()) }, - defaultValue = TemperatureUnit.KELVIN, - key = androidApplication.getString(R.string.key_temperature_unit) - ) + delegateProperty = sharedPreferences.delegateVar( + getter = { key, defaultValue -> + getString(key, defaultValue.toString())!!.let { TemperatureUnit.fromString(it) } + }, + setter = { key, value -> putString(key, value.toString()) }, + defaultValue = TemperatureUnit.KELVIN, + key = androidApplication.getString(R.string.key_temperature_unit) + ) ) val speedUnitPreference = BaseSettingPreference( - delegateProperty = sharedPreferences.delegateVar( - getter = { key, defaultValue -> - getString(key, defaultValue.toString())!!.let { SpeedUnit.valueOf(it) } - }, - setter = { key, value -> putString(key, value.toString()) }, - defaultValue = SpeedUnit.METERS_PER_SECOND, - key = androidApplication.getString(R.string.key_speed_unit) - ) + delegateProperty = sharedPreferences.delegateVar( + getter = { key, defaultValue -> + getString(key, defaultValue.toString())!!.let { SpeedUnit.valueOf(it) } + }, + setter = { key, value -> putString(key, value.toString()) }, + defaultValue = SpeedUnit.METERS_PER_SECOND, + key = androidApplication.getString(R.string.key_speed_unit) + ) ) val pressureUnitPreference = BaseSettingPreference( - delegateProperty = sharedPreferences.delegateVar( - getter = { key, defaultValue -> - getString(key, defaultValue.toString())!!.let { PressureUnit.valueOf(it) } - }, - setter = { key, value -> putString(key, value.toString()) }, - defaultValue = PressureUnit.HPA, - key = androidApplication.getString(R.string.key_pressure_unit) - ) + delegateProperty = sharedPreferences.delegateVar( + getter = { key, defaultValue -> + getString(key, defaultValue.toString())!!.let { PressureUnit.valueOf(it) } + }, + setter = { key, value -> putString(key, value.toString()) }, + defaultValue = PressureUnit.HPA, + key = androidApplication.getString(R.string.key_pressure_unit) + ) ) val showNotificationPreference = BaseSettingPreference( - delegateProperty = sharedPreferences.delegate( - default = true, - key = androidApplication.getString(R.string.key_show_notification) - ) + delegateProperty = sharedPreferences.delegate( + default = true, + key = androidApplication.getString(R.string.key_show_notification) + ) ) val autoUpdatePreference = BaseSettingPreference( - delegateProperty = sharedPreferences.delegate( - default = true, - key = androidApplication.getString(R.string.key_auto_update) - ) + delegateProperty = sharedPreferences.delegate( + default = true, + key = androidApplication.getString(R.string.key_auto_update) + ) ) val soundNotificationPreference = BaseSettingPreference( - delegateProperty = sharedPreferences.delegate( - default = false, - key = androidApplication.getString(R.string.key_sound_notification) - ) + delegateProperty = sharedPreferences.delegate( + default = false, + key = androidApplication.getString(R.string.key_sound_notification) + ) ) val darkThemePreference = BaseSettingPreference( - delegateProperty = sharedPreferences.delegate( - default = false, - key = androidApplication.getString(R.string.key_dark_theme) - ) + delegateProperty = sharedPreferences.delegate( + default = false, + key = androidApplication.getString(R.string.key_dark_theme) + ) ) override fun toString() = - "SettingPreferences($temperatureUnitPreference," + - " $speedUnitPreference, $pressureUnitPreference," + - " $showNotificationPreference, $autoUpdatePreference," + - " $soundNotificationPreference, $darkThemePreference)" -} \ No newline at end of file + "SettingPreferences($temperatureUnitPreference," + + " $speedUnitPreference, $pressureUnitPreference," + + " $showNotificationPreference, $autoUpdatePreference," + + " $soundNotificationPreference, $darkThemePreference)" +} diff --git a/app/src/main/java/com/hoc/weatherapp/data/models/Units.kt b/app/src/main/java/com/hoc/weatherapp/data/models/Units.kt index 61e827f..fb48e88 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/models/Units.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/models/Units.kt @@ -2,7 +2,6 @@ package com.hoc.weatherapp.data.models import com.hoc.weatherapp.utils.UnitConverter import java.text.DecimalFormat -import java.util.Locale private val NUMBER_FORMAT = DecimalFormat("#.#") @@ -19,10 +18,10 @@ enum class TemperatureUnit { fun format(temperatureInKelvin: Double): String { return NUMBER_FORMAT.format( - UnitConverter.convertTemperature( - temperatureInKelvin, - this - ) + UnitConverter.convertTemperature( + temperatureInKelvin, + this + ) ) + symbol() } @@ -56,10 +55,10 @@ enum class SpeedUnit { fun format(speedInMetersPerSecond: Double): String { return NUMBER_FORMAT.format( - UnitConverter.convertSpeed( - speedInMetersPerSecond, - this - ) + UnitConverter.convertSpeed( + speedInMetersPerSecond, + this + ) ) + symbol() } @@ -72,17 +71,16 @@ enum class SpeedUnit { } } - enum class PressureUnit { HPA, MM_HG; fun format(pressureIn_hPa: Double): String { return NUMBER_FORMAT.format( - UnitConverter.convertPressure( - pressureIn_hPa, - this - ) + UnitConverter.convertPressure( + pressureIn_hPa, + this + ) ) + symbol() } @@ -92,4 +90,4 @@ enum class PressureUnit { MM_HG -> "mmHg" } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/data/models/WindDirection.kt b/app/src/main/java/com/hoc/weatherapp/data/models/WindDirection.kt index db9d9a8..e80de8c 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/models/WindDirection.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/models/WindDirection.kt @@ -9,4 +9,4 @@ enum class WindDirection { return WindDirection.values()[d] } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/Clouds.kt b/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/Clouds.kt index a530d74..5d3bcea 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/Clouds.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/Clouds.kt @@ -3,9 +3,9 @@ package com.hoc.weatherapp.data.models.apiresponse import com.squareup.moshi.Json class Clouds( - /** - * Cloudiness, % - */ - @Json(name = "all") - val all: Long? = null -) \ No newline at end of file + /** + * Cloudiness, % + */ + @Json(name = "all") + val all: Long? = null +) diff --git a/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/Coord.kt b/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/Coord.kt index 776f42a..1392455 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/Coord.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/Coord.kt @@ -3,14 +3,14 @@ package com.hoc.weatherapp.data.models.apiresponse import com.squareup.moshi.Json class Coord( - /** - * City geo location, latitude - */ - @Json(name = "lon") - val lon: Double? = null, - /** - * City geo location, longitude - */ - @Json(name = "lat") - val lat: Double? = null -) \ No newline at end of file + /** + * City geo location, latitude + */ + @Json(name = "lon") + val lon: Double? = null, + /** + * City geo location, longitude + */ + @Json(name = "lat") + val lat: Double? = null +) diff --git a/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/Rain.kt b/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/Rain.kt index b184c4c..d33f5e4 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/Rain.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/Rain.kt @@ -3,9 +3,9 @@ package com.hoc.weatherapp.data.models.apiresponse import com.squareup.moshi.Json class Rain( - /** - * Rain volume for last 3 hours, mm - */ - @Json(name = "3h") - val _3h: Double? = null -) \ No newline at end of file + /** + * Rain volume for last 3 hours, mm + */ + @Json(name = "3h") + val _3h: Double? = null +) diff --git a/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/Snow.kt b/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/Snow.kt index 1df3fb5..70a3c57 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/Snow.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/Snow.kt @@ -3,9 +3,9 @@ package com.hoc.weatherapp.data.models.apiresponse import com.squareup.moshi.Json class Snow( - /** - * Snow volume for last 3 hours - */ - @Json(name = "3h") - val _3h: Double? = null -) \ No newline at end of file + /** + * Snow volume for last 3 hours + */ + @Json(name = "3h") + val _3h: Double? = null +) diff --git a/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/WeatherModel.kt b/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/WeatherModel.kt index a276d7f..5a8448f 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/WeatherModel.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/WeatherModel.kt @@ -3,27 +3,27 @@ package com.hoc.weatherapp.data.models.apiresponse import com.squareup.moshi.Json class WeatherModel( - /** - * Weather condition id - */ - @Json(name = "id") - val id: Long? = null, + /** + * Weather condition id + */ + @Json(name = "id") + val id: Long? = null, - /** - * Group of weather parameters (Rain, Snow, Extreme etc.) - */ - @Json(name = "main") - val main: String? = null, + /** + * Group of weather parameters (Rain, Snow, Extreme etc.) + */ + @Json(name = "main") + val main: String? = null, - /** - * Weather condition within the group - */ - @Json(name = "description") - val description: String? = null, + /** + * Weather condition within the group + */ + @Json(name = "description") + val description: String? = null, - /** - * Weather icon id - */ - @Json(name = "icon") - val icon: String? = null -) \ No newline at end of file + /** + * Weather icon id + */ + @Json(name = "icon") + val icon: String? = null +) diff --git a/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/Wind.kt b/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/Wind.kt index 6186bc0..74ae49c 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/Wind.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/Wind.kt @@ -3,15 +3,15 @@ package com.hoc.weatherapp.data.models.apiresponse import com.squareup.moshi.Json class Wind( - /** - * Wind speed. Unit Default: meter/sec, Metric: meter/sec, Imperial: miles/hour. - */ - @Json(name = "speed") - val speed: Double? = null, + /** + * Wind speed. Unit Default: meter/sec, Metric: meter/sec, Imperial: miles/hour. + */ + @Json(name = "speed") + val speed: Double? = null, - /** - * Wind direction, degrees (meteorological) - */ - @Json(name = "deg") - val deg: Double? = null + /** + * Wind direction, degrees (meteorological) + */ + @Json(name = "deg") + val deg: Double? = null ) diff --git a/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/currentweatherapiresponse/CurrentWeatherResponse.kt b/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/currentweatherapiresponse/CurrentWeatherResponse.kt index 206c38e..0ec6ad0 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/currentweatherapiresponse/CurrentWeatherResponse.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/currentweatherapiresponse/CurrentWeatherResponse.kt @@ -1,37 +1,40 @@ package com.hoc.weatherapp.data.models.apiresponse.currentweatherapiresponse -import com.hoc.weatherapp.data.models.apiresponse.* +import com.hoc.weatherapp.data.models.apiresponse.Clouds +import com.hoc.weatherapp.data.models.apiresponse.Coord +import com.hoc.weatherapp.data.models.apiresponse.Rain +import com.hoc.weatherapp.data.models.apiresponse.Snow +import com.hoc.weatherapp.data.models.apiresponse.WeatherModel +import com.hoc.weatherapp.data.models.apiresponse.Wind import com.squareup.moshi.Json class CurrentWeatherResponse( - @Json(name = "coord") - val coord: Coord? = null, - @Json(name = "weather") - val weather: List? = null, - @Json(name = "base") - val base: String? = null, - @Json(name = "main") - val main: Main? = null, - @Json(name = "visibilityKm") - val visibility: Double? = null, - @Json(name = "wind") - val wind: Wind? = null, - @Json(name = "clouds") - val clouds: Clouds? = null, - @Json(name = "dt") - val dt: Long? = null, - @Json(name = "sys") - val sys: Sys? = null, - @Json(name = "id") - val id: Long? = null, - @Json(name = "name") - val name: String? = null, - @Json(name = "cod") - val cod: Long? = null, - @Json(name = "rain") - val rain: Rain? = null, - @Json(name = "snow") - val snow: Snow? = null + @Json(name = "coord") + val coord: Coord? = null, + @Json(name = "weather") + val weather: List? = null, + @Json(name = "base") + val base: String? = null, + @Json(name = "main") + val main: Main? = null, + @Json(name = "visibilityKm") + val visibility: Double? = null, + @Json(name = "wind") + val wind: Wind? = null, + @Json(name = "clouds") + val clouds: Clouds? = null, + @Json(name = "dt") + val dt: Long? = null, + @Json(name = "sys") + val sys: Sys? = null, + @Json(name = "id") + val id: Long? = null, + @Json(name = "name") + val name: String? = null, + @Json(name = "cod") + val cod: Long? = null, + @Json(name = "rain") + val rain: Rain? = null, + @Json(name = "snow") + val snow: Snow? = null ) - - diff --git a/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/currentweatherapiresponse/Main.kt b/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/currentweatherapiresponse/Main.kt index 0f68d12..5ca8a7f 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/currentweatherapiresponse/Main.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/currentweatherapiresponse/Main.kt @@ -3,14 +3,14 @@ package com.hoc.weatherapp.data.models.apiresponse.currentweatherapiresponse import com.squareup.moshi.Json class Main( - @Json(name = "temp") - val temp: Double? = null, - @Json(name = "pressure") - val pressure: Double? = null, - @Json(name = "humidity") - val humidity: Long? = null, - @Json(name = "temp_min") - val tempMin: Double? = null, - @Json(name = "temp_max") - val tempMax: Double? = null -) \ No newline at end of file + @Json(name = "temp") + val temp: Double? = null, + @Json(name = "pressure") + val pressure: Double? = null, + @Json(name = "humidity") + val humidity: Long? = null, + @Json(name = "temp_min") + val tempMin: Double? = null, + @Json(name = "temp_max") + val tempMax: Double? = null +) diff --git a/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/currentweatherapiresponse/Sys.kt b/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/currentweatherapiresponse/Sys.kt index 27331b8..4fff63a 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/currentweatherapiresponse/Sys.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/currentweatherapiresponse/Sys.kt @@ -3,16 +3,16 @@ package com.hoc.weatherapp.data.models.apiresponse.currentweatherapiresponse import com.squareup.moshi.Json class Sys( - @Json(name = "type") - val type: Long? = null, - @Json(name = "weatherId") - val id: Long? = null, - @Json(name = "message") - val message: Double? = null, - @Json(name = "country") - val country: String? = null, - @Json(name = "sunrise") - val sunrise: Long? = null, - @Json(name = "sunset") - val sunset: Long? = null -) \ No newline at end of file + @Json(name = "type") + val type: Long? = null, + @Json(name = "weatherId") + val id: Long? = null, + @Json(name = "message") + val message: Double? = null, + @Json(name = "country") + val country: String? = null, + @Json(name = "sunrise") + val sunrise: Long? = null, + @Json(name = "sunset") + val sunset: Long? = null +) diff --git a/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/forecastweatherapiresponse/City.kt b/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/forecastweatherapiresponse/City.kt index 42b170b..877daec 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/forecastweatherapiresponse/City.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/forecastweatherapiresponse/City.kt @@ -4,24 +4,24 @@ import com.hoc.weatherapp.data.models.apiresponse.Coord import com.squareup.moshi.Json class City( - /** - * City ID - */ - @Json(name = "id") - val id: Long? = null, - /** - * City name - */ - @Json(name = "name") - val name: String? = null, - /** - * Location - */ - @Json(name = "coord") - val coord: Coord? = null, - /** - * Country code (GB, JP etc.) - */ - @Json(name = "country") - val country: String? = null -) \ No newline at end of file + /** + * City ID + */ + @Json(name = "id") + val id: Long? = null, + /** + * City name + */ + @Json(name = "name") + val name: String? = null, + /** + * Location + */ + @Json(name = "coord") + val coord: Coord? = null, + /** + * Country code (GB, JP etc.) + */ + @Json(name = "country") + val country: String? = null +) diff --git a/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/forecastweatherapiresponse/DailyWeatherModel.kt b/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/forecastweatherapiresponse/DailyWeatherModel.kt index 85a086a..7641b5d 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/forecastweatherapiresponse/DailyWeatherModel.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/forecastweatherapiresponse/DailyWeatherModel.kt @@ -1,58 +1,61 @@ package com.hoc.weatherapp.data.models.apiresponse.forecastweatherapiresponse -import com.hoc.weatherapp.data.models.apiresponse.* +import com.hoc.weatherapp.data.models.apiresponse.Clouds +import com.hoc.weatherapp.data.models.apiresponse.Rain +import com.hoc.weatherapp.data.models.apiresponse.Snow +import com.hoc.weatherapp.data.models.apiresponse.WeatherModel +import com.hoc.weatherapp.data.models.apiresponse.Wind import com.squareup.moshi.Json class DailyWeatherModel( - /** - * Time of data forecasted, unix, UTC - */ - @Json(name = "dt") - val dt: Long? = null, - - /** - * Main information - */ - @Json(name = "main") - val main: Main? = null, - - /** - * More info Weather condition codes - */ - @Json(name = "weather") - val weather: List? = null, - - /** - * Cloud - */ - @Json(name = "clouds") - val clouds: Clouds? = null, - - /** - * Wind - */ - @Json(name = "wind") - val wind: Wind? = null, - - - /** - * Rain - */ - @Json(name = "rain") - val rain: Rain? = null, - - /** - * Snow - */ - @Json(name = "snow") - val snow: Snow? = null, - - @Json(name = "sys") - val sys: Sys? = null, - - /** - * Data/time of calculation, UTC - */ - @Json(name = "dt_txt") - val dtTxt: String? = null -) \ No newline at end of file + /** + * Time of data forecasted, unix, UTC + */ + @Json(name = "dt") + val dt: Long? = null, + + /** + * Main information + */ + @Json(name = "main") + val main: Main? = null, + + /** + * More info Weather condition codes + */ + @Json(name = "weather") + val weather: List? = null, + + /** + * Cloud + */ + @Json(name = "clouds") + val clouds: Clouds? = null, + + /** + * Wind + */ + @Json(name = "wind") + val wind: Wind? = null, + + /** + * Rain + */ + @Json(name = "rain") + val rain: Rain? = null, + + /** + * Snow + */ + @Json(name = "snow") + val snow: Snow? = null, + + @Json(name = "sys") + val sys: Sys? = null, + + /** + * Data/time of calculation, UTC + */ + @Json(name = "dt_txt") + val dtTxt: String? = null +) diff --git a/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/forecastweatherapiresponse/FiveDayForecastResponse.kt b/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/forecastweatherapiresponse/FiveDayForecastResponse.kt index 773b20a..3df2e2b 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/forecastweatherapiresponse/FiveDayForecastResponse.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/forecastweatherapiresponse/FiveDayForecastResponse.kt @@ -3,34 +3,33 @@ package com.hoc.weatherapp.data.models.apiresponse.forecastweatherapiresponse import com.squareup.moshi.Json class FiveDayForecastResponse( - /** - * Internal parameter - */ - @Json(name = "cod") - val cod: String? = null, + /** + * Internal parameter + */ + @Json(name = "cod") + val cod: String? = null, - /** - * Internal parameter - */ - @Json(name = "message") - val message: Double? = null, + /** + * Internal parameter + */ + @Json(name = "message") + val message: Double? = null, - /** - * Number of lines returned by this API call - */ - @Json(name = "cnt") - val cnt: Long? = null, + /** + * Number of lines returned by this API call + */ + @Json(name = "cnt") + val cnt: Long? = null, - /** - * List of daily weathers - */ - @Json(name = "list") - val list: List? = null, + /** + * List of daily weathers + */ + @Json(name = "list") + val list: List? = null, - /** - * City information - */ - @Json(name = "city") - val city: City? = null + /** + * City information + */ + @Json(name = "city") + val city: City? = null ) - diff --git a/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/forecastweatherapiresponse/Main.kt b/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/forecastweatherapiresponse/Main.kt index 847bbab..2e6e782 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/forecastweatherapiresponse/Main.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/forecastweatherapiresponse/Main.kt @@ -3,56 +3,56 @@ package com.hoc.weatherapp.data.models.apiresponse.forecastweatherapiresponse import com.squareup.moshi.Json class Main( - /** - * Temperature. Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit. - */ - @Json(name = "temp") - val temp: Double? = null, - - /** - * Minimum temperatureString at the moment of calculation. - * This is deviation from 'temp' that is possible for large cities and megalopolises geographically expanded (use these parameter optionally). - * Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit. - */ - @Json(name = "temp_min") - val tempMin: Double? = null, - - /** - * Maximum temperatureString at the moment of calculation. - * This is deviation from 'temp' that is possible for large cities and megalopolises geographically expanded (use these parameter optionally). - * Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit. - */ - @Json(name = "temp_max") - val - tempMax: Double? = null, - - /** - * Atmospheric pressure on the sea level by default, hPa - */ - @Json(name = "pressure") - val pressure: Double? = null, - - /** - * Atmospheric pressure on the sea level, hPa - */ - @Json(name = "sea_level") - val seaLevel: Double? = null, - - /** - * Atmospheric pressure on the ground level, hPa - */ - @Json(name = "grnd_level") - val grndLevel: Double? = null, - - /** - * Humidity, % - */ - @Json(name = "humidity") - val humidity: Long? = null, - - /** - * Internal parameter - */ - @Json(name = "temp_kf") - val tempKf: Double? = null -) \ No newline at end of file + /** + * Temperature. Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit. + */ + @Json(name = "temp") + val temp: Double? = null, + + /** + * Minimum temperatureString at the moment of calculation. + * This is deviation from 'temp' that is possible for large cities and megalopolises geographically expanded (use these parameter optionally). + * Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit. + */ + @Json(name = "temp_min") + val tempMin: Double? = null, + + /** + * Maximum temperatureString at the moment of calculation. + * This is deviation from 'temp' that is possible for large cities and megalopolises geographically expanded (use these parameter optionally). + * Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit. + */ + @Json(name = "temp_max") + val + tempMax: Double? = null, + + /** + * Atmospheric pressure on the sea level by default, hPa + */ + @Json(name = "pressure") + val pressure: Double? = null, + + /** + * Atmospheric pressure on the sea level, hPa + */ + @Json(name = "sea_level") + val seaLevel: Double? = null, + + /** + * Atmospheric pressure on the ground level, hPa + */ + @Json(name = "grnd_level") + val grndLevel: Double? = null, + + /** + * Humidity, % + */ + @Json(name = "humidity") + val humidity: Long? = null, + + /** + * Internal parameter + */ + @Json(name = "temp_kf") + val tempKf: Double? = null +) diff --git a/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/forecastweatherapiresponse/Sys.kt b/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/forecastweatherapiresponse/Sys.kt index 460f571..9a64fc5 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/forecastweatherapiresponse/Sys.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/forecastweatherapiresponse/Sys.kt @@ -3,6 +3,6 @@ package com.hoc.weatherapp.data.models.apiresponse.forecastweatherapiresponse import com.squareup.moshi.Json class Sys( - @Json(name = "pod") - val pod: String? = null -) \ No newline at end of file + @Json(name = "pod") + val pod: String? = null +) diff --git a/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/timezonedb/TimezoneDbResponse.kt b/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/timezonedb/TimezoneDbResponse.kt index d9a8429..044ed31 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/timezonedb/TimezoneDbResponse.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/models/apiresponse/timezonedb/TimezoneDbResponse.kt @@ -3,16 +3,16 @@ package com.hoc.weatherapp.data.models.apiresponse.timezonedb import com.squareup.moshi.Json data class TimezoneDbResponse( - /** - * Status of the API query. Either OK or FAILED. - */ - @Json(name = "status") val status: String, - /** - * Error message. Empty if no error. - */ - @Json(name = "message") val message: String, - /** - * The time zone name. - */ - @Json(name = "zoneName") val zoneName: String -) \ No newline at end of file + /** + * Status of the API query. Either OK or FAILED. + */ + @Json(name = "status") val status: String, + /** + * Error message. Empty if no error. + */ + @Json(name = "message") val message: String, + /** + * The time zone name. + */ + @Json(name = "zoneName") val zoneName: String +) diff --git a/app/src/main/java/com/hoc/weatherapp/data/models/entity/City.kt b/app/src/main/java/com/hoc/weatherapp/data/models/entity/City.kt index 68b368d..674ae59 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/models/entity/City.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/models/entity/City.kt @@ -14,40 +14,40 @@ import kotlinx.parcelize.Parcelize @Entity(tableName = "cities") @Parcelize data class City( - /** - * Id of city - */ - @PrimaryKey(autoGenerate = false) - @ColumnInfo(name = "id") - val id: Long, - - /** - * Name of city - */ - @ColumnInfo(name = "name") - val name: String = "", - - /** - * Country of city - */ - @ColumnInfo(name = "country") - val country: String = "", - - /** - * Latitude of city - */ - @ColumnInfo(name = "lat") - val lat: Double = Double.NEGATIVE_INFINITY, - - /** - * Longitude of city - */ - @ColumnInfo(name = "lng") - val lng: Double = Double.NEGATIVE_INFINITY, - - /** - * The time zone name. - */ - @ColumnInfo(name = "zone_id") - val zoneId: String -) : Parcelable \ No newline at end of file + /** + * Id of city + */ + @PrimaryKey(autoGenerate = false) + @ColumnInfo(name = "id") + val id: Long, + + /** + * Name of city + */ + @ColumnInfo(name = "name") + val name: String = "", + + /** + * Country of city + */ + @ColumnInfo(name = "country") + val country: String = "", + + /** + * Latitude of city + */ + @ColumnInfo(name = "lat") + val lat: Double = Double.NEGATIVE_INFINITY, + + /** + * Longitude of city + */ + @ColumnInfo(name = "lng") + val lng: Double = Double.NEGATIVE_INFINITY, + + /** + * The time zone name. + */ + @ColumnInfo(name = "zone_id") + val zoneId: String +) : Parcelable diff --git a/app/src/main/java/com/hoc/weatherapp/data/models/entity/CityAndCurrentWeather.kt b/app/src/main/java/com/hoc/weatherapp/data/models/entity/CityAndCurrentWeather.kt index e807a84..eb4856c 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/models/entity/CityAndCurrentWeather.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/models/entity/CityAndCurrentWeather.kt @@ -22,4 +22,4 @@ class CityAndCurrentWeather { override fun hashCode() = 31 * city.hashCode() + currentWeather.hashCode() override fun toString() = "CityAndCurrentWeather(city=$city, currentWeather=$currentWeather)" -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/data/models/entity/CurrentWeather.kt b/app/src/main/java/com/hoc/weatherapp/data/models/entity/CurrentWeather.kt index ada404b..aae82e3 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/models/entity/CurrentWeather.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/models/entity/CurrentWeather.kt @@ -6,8 +6,8 @@ import androidx.room.Entity import androidx.room.ForeignKey import androidx.room.ForeignKey.CASCADE import androidx.room.PrimaryKey -import kotlinx.parcelize.Parcelize import java.util.* +import kotlinx.parcelize.Parcelize /** * Declaring the column info allows for the renaming of variables without implementing a @@ -16,133 +16,133 @@ import java.util.* @Parcelize @Entity( - tableName = "current_weathers", - foreignKeys = [ - ForeignKey( - entity = City::class, - onDelete = CASCADE, - parentColumns = ["id"], - childColumns = ["city_id"] - ) - ] + tableName = "current_weathers", + foreignKeys = [ + ForeignKey( + entity = City::class, + onDelete = CASCADE, + parentColumns = ["id"], + childColumns = ["city_id"] + ) + ] ) data class CurrentWeather( - /** - * Id of city - */ - @PrimaryKey - @ColumnInfo(name = "city_id") - val cityId: Long, - - /** - * Cloudiness, % - */ - @ColumnInfo(name = "cloudiness") - val cloudiness: Long, - - /** - * Group of weather parameters (Rain, Snow, Extreme etc.) - */ - @ColumnInfo(name = "main") - val main: String, - - /** - * Weather condition within the group - */ - @ColumnInfo(name = "description") - val description: String, - - /** - * Weather icon weatherId - */ - @ColumnInfo(name = "icon") - val icon: String, - - /** - * Temperature. Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit. - */ - @ColumnInfo(name = "temperature") - val temperature: Double, - - /** - * Atmospheric pressure on the sea level by default, hPa - */ - @ColumnInfo(name = "pressure") - val pressure: Double, - - /** - * Humidity, % - */ - @ColumnInfo(name = "humidity") - val humidity: Long, - - /** - * Minimum temperature at the moment of calculation. - * This is deviation from 'temp' that is possible for large cities and megalopolises geographically expanded (use these parameter optionally). - * Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit. - */ - @ColumnInfo(name = "temperature_min") - val temperatureMin: Double, - - /** - * Maximum temperature at the moment of calculation. - * This is deviation from 'temp' that is possible for large cities and megalopolises geographically expanded (use these parameter optionally). - * Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit. - */ - @ColumnInfo(name = "temperature_max") - val temperatureMax: Double, - - /** - * Wind speed. Unit Default: meter/sec, Metric: meter/sec, Imperial: miles/hour. - */ - @ColumnInfo(name = "win_speed") - val winSpeed: Double, - - /** - * Wind direction, degrees (meteorological) - */ - @ColumnInfo(name = "win_degrees") - val winDegrees: Double, - - /** - * Time of data forecasted, unix, UTC - */ - @ColumnInfo(name = "data_time") - val dataTime: Date, - - /** - * Rain volume for last 3 hours, mm - */ - @ColumnInfo(name = "rain_volume_for_last_3_hours") - val rainVolumeForThe3Hours: Double, - - /** - * Snow volume for last 3 hours - */ - @ColumnInfo(name = "snow_volume_for_last_3_hours") - val snowVolumeForThe3Hours: Double, - - /** - * Visibility, meter - */ - @ColumnInfo(name = "visibilityKm") - val visibility: Double, - - /** - * Sunrise time, unix, UTC - */ - @ColumnInfo(name = "sunrise") - val sunrise: Date, - - /** - * Sunset time, unix, UTC - */ - @ColumnInfo(name = "sunset") - val sunset: Date, - - /** - * Weather condition weatherId - */ - @ColumnInfo(name = "weather_condition_id") - val weatherConditionId: Long -) : Parcelable \ No newline at end of file + /** + * Id of city + */ + @PrimaryKey + @ColumnInfo(name = "city_id") + val cityId: Long, + + /** + * Cloudiness, % + */ + @ColumnInfo(name = "cloudiness") + val cloudiness: Long, + + /** + * Group of weather parameters (Rain, Snow, Extreme etc.) + */ + @ColumnInfo(name = "main") + val main: String, + + /** + * Weather condition within the group + */ + @ColumnInfo(name = "description") + val description: String, + + /** + * Weather icon weatherId + */ + @ColumnInfo(name = "icon") + val icon: String, + + /** + * Temperature. Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit. + */ + @ColumnInfo(name = "temperature") + val temperature: Double, + + /** + * Atmospheric pressure on the sea level by default, hPa + */ + @ColumnInfo(name = "pressure") + val pressure: Double, + + /** + * Humidity, % + */ + @ColumnInfo(name = "humidity") + val humidity: Long, + + /** + * Minimum temperature at the moment of calculation. + * This is deviation from 'temp' that is possible for large cities and megalopolises geographically expanded (use these parameter optionally). + * Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit. + */ + @ColumnInfo(name = "temperature_min") + val temperatureMin: Double, + + /** + * Maximum temperature at the moment of calculation. + * This is deviation from 'temp' that is possible for large cities and megalopolises geographically expanded (use these parameter optionally). + * Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit. + */ + @ColumnInfo(name = "temperature_max") + val temperatureMax: Double, + + /** + * Wind speed. Unit Default: meter/sec, Metric: meter/sec, Imperial: miles/hour. + */ + @ColumnInfo(name = "win_speed") + val winSpeed: Double, + + /** + * Wind direction, degrees (meteorological) + */ + @ColumnInfo(name = "win_degrees") + val winDegrees: Double, + + /** + * Time of data forecasted, unix, UTC + */ + @ColumnInfo(name = "data_time") + val dataTime: Date, + + /** + * Rain volume for last 3 hours, mm + */ + @ColumnInfo(name = "rain_volume_for_last_3_hours") + val rainVolumeForThe3Hours: Double, + + /** + * Snow volume for last 3 hours + */ + @ColumnInfo(name = "snow_volume_for_last_3_hours") + val snowVolumeForThe3Hours: Double, + + /** + * Visibility, meter + */ + @ColumnInfo(name = "visibilityKm") + val visibility: Double, + + /** + * Sunrise time, unix, UTC + */ + @ColumnInfo(name = "sunrise") + val sunrise: Date, + + /** + * Sunset time, unix, UTC + */ + @ColumnInfo(name = "sunset") + val sunset: Date, + + /** + * Weather condition weatherId + */ + @ColumnInfo(name = "weather_condition_id") + val weatherConditionId: Long +) : Parcelable diff --git a/app/src/main/java/com/hoc/weatherapp/data/models/entity/DailyWeather.kt b/app/src/main/java/com/hoc/weatherapp/data/models/entity/DailyWeather.kt index 41b6011..128362f 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/models/entity/DailyWeather.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/models/entity/DailyWeather.kt @@ -1,132 +1,136 @@ package com.hoc.weatherapp.data.models.entity import android.os.Parcelable -import androidx.room.* +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.ForeignKey import androidx.room.ForeignKey.CASCADE -import kotlinx.parcelize.Parcelize +import androidx.room.Index +import androidx.room.PrimaryKey +import java.util.Date import kotlinx.parcelize.IgnoredOnParcel -import java.util.* +import kotlinx.parcelize.Parcelize @Parcelize @Entity( - tableName = "five_day_forecast", - foreignKeys = [ - ForeignKey( - entity = City::class, - onDelete = CASCADE, - onUpdate = CASCADE, - parentColumns = ["id"], - childColumns = ["city_id"] - ) - ], - indices = [Index(value = ["city_id"])] + tableName = "five_day_forecast", + foreignKeys = [ + ForeignKey( + entity = City::class, + onDelete = CASCADE, + onUpdate = CASCADE, + parentColumns = ["id"], + childColumns = ["city_id"] + ) + ], + indices = [Index(value = ["city_id"])] ) data class DailyWeather( - /** - * Time of data forecasted, unix, UTC - */ - @ColumnInfo(name = "data_time") - val timeOfDataForecasted: Date, - - /** - * Temperature. Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit. - */ - @ColumnInfo(name = "temperature") - val temperature: Double, - - /** - * Minimum temperature at the moment of calculation. - * This is deviation from 'temp' that is possible for large cities and megalopolises geographically expanded (use these parameter optionally). - * Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit. - */ - @ColumnInfo(name = "temperature_min") - val temperatureMin: Double, - - /** - * Maximum temperature at the moment of calculation. - * This is deviation from 'temp' that is possible for large cities and megalopolises geographically expanded (use these parameter optionally). - * Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit. - */ - @ColumnInfo(name = "temperature_max") - val temperatureMax: Double, - - /** - * Atmospheric pressure on the sea level by default, hPa - */ - @ColumnInfo(name = "pressure") - val pressure: Double, - - /** - * Atmospheric pressure on the sea level, hPa - */ - @ColumnInfo(name = "sea_level") - val seaLevel: Double, - - /** - * Atmospheric pressure on the ground level, hPa - */ - @ColumnInfo(name = "ground_level") - val groundLevel: Double, - - /** - * Humidity, % - */ - @ColumnInfo(name = "humidity") - val humidity: Long, - - /** - * Group of weather parameters (Rain, Snow, Extreme etc.) - */ - @ColumnInfo(name = "main") - val main: String, - - /** - * Weather condition within the group - */ - @ColumnInfo(name = "description") - val description: String, - - /** - * Weather icon weatherId - */ - @ColumnInfo(name = "icon") - val icon: String, - - /** - * Cloudiness, % - */ - @ColumnInfo(name = "cloudiness") - val cloudiness: Long, - - /** - * Wind speed. Unit Default: meter/sec, Metric: meter/sec, Imperial: miles/hour. - */ - @ColumnInfo(name = "win_speed") - val windSpeed: Double, - - /** - * Wind direction, degrees (meteorological) - */ - @ColumnInfo(name = "win_degrees") - val winDegrees: Double, - - /** - * Rain volume for last 3 hours, mm - */ - @ColumnInfo(name = "rain_volume_for_last_3_hours") - val rainVolumeForTheLast3Hours: Double, - - /** - * Snow volume for last 3 hours - */ - @ColumnInfo(name = "snow_volume_for_last_3_hours") - val snowVolumeForTheLast3Hours: Double, - - /** - * Id of city - */ - @ColumnInfo(name = "city_id") - val cityId: Long + /** + * Time of data forecasted, unix, UTC + */ + @ColumnInfo(name = "data_time") + val timeOfDataForecasted: Date, + + /** + * Temperature. Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit. + */ + @ColumnInfo(name = "temperature") + val temperature: Double, + + /** + * Minimum temperature at the moment of calculation. + * This is deviation from 'temp' that is possible for large cities and megalopolises geographically expanded (use these parameter optionally). + * Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit. + */ + @ColumnInfo(name = "temperature_min") + val temperatureMin: Double, + + /** + * Maximum temperature at the moment of calculation. + * This is deviation from 'temp' that is possible for large cities and megalopolises geographically expanded (use these parameter optionally). + * Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit. + */ + @ColumnInfo(name = "temperature_max") + val temperatureMax: Double, + + /** + * Atmospheric pressure on the sea level by default, hPa + */ + @ColumnInfo(name = "pressure") + val pressure: Double, + + /** + * Atmospheric pressure on the sea level, hPa + */ + @ColumnInfo(name = "sea_level") + val seaLevel: Double, + + /** + * Atmospheric pressure on the ground level, hPa + */ + @ColumnInfo(name = "ground_level") + val groundLevel: Double, + + /** + * Humidity, % + */ + @ColumnInfo(name = "humidity") + val humidity: Long, + + /** + * Group of weather parameters (Rain, Snow, Extreme etc.) + */ + @ColumnInfo(name = "main") + val main: String, + + /** + * Weather condition within the group + */ + @ColumnInfo(name = "description") + val description: String, + + /** + * Weather icon weatherId + */ + @ColumnInfo(name = "icon") + val icon: String, + + /** + * Cloudiness, % + */ + @ColumnInfo(name = "cloudiness") + val cloudiness: Long, + + /** + * Wind speed. Unit Default: meter/sec, Metric: meter/sec, Imperial: miles/hour. + */ + @ColumnInfo(name = "win_speed") + val windSpeed: Double, + + /** + * Wind direction, degrees (meteorological) + */ + @ColumnInfo(name = "win_degrees") + val winDegrees: Double, + + /** + * Rain volume for last 3 hours, mm + */ + @ColumnInfo(name = "rain_volume_for_last_3_hours") + val rainVolumeForTheLast3Hours: Double, + + /** + * Snow volume for last 3 hours + */ + @ColumnInfo(name = "snow_volume_for_last_3_hours") + val snowVolumeForTheLast3Hours: Double, + + /** + * Id of city + */ + @ColumnInfo(name = "city_id") + val cityId: Long ) : Parcelable { @IgnoredOnParcel @ColumnInfo(name = "id") diff --git a/app/src/main/java/com/hoc/weatherapp/data/remote/OpenWeatherMapApiService.kt b/app/src/main/java/com/hoc/weatherapp/data/remote/OpenWeatherMapApiService.kt index e555ea4..efd311b 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/remote/OpenWeatherMapApiService.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/remote/OpenWeatherMapApiService.kt @@ -12,17 +12,17 @@ const val OPEN_WEATHER_MAP_APP_ID = "6592d24a33ae13c2ac1401db99732c61" interface OpenWeatherMapApiService { @GET("weather") fun getCurrentWeatherByLatLng( - @Query("lat") lat: Double, - @Query("lon") lon: Double + @Query("lat") lat: Double, + @Query("lon") lon: Double ): Single @GET("weather") fun getCurrentWeatherByCityId( - @Query("id") id: Long + @Query("id") id: Long ): Single @GET("forecast") fun get5DayEvery3HourForecastByCityId( - @Query("id") id: Long + @Query("id") id: Long ): Single -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/data/remote/TimezoneDbApiService.kt b/app/src/main/java/com/hoc/weatherapp/data/remote/TimezoneDbApiService.kt index ef8549f..59944bd 100644 --- a/app/src/main/java/com/hoc/weatherapp/data/remote/TimezoneDbApiService.kt +++ b/app/src/main/java/com/hoc/weatherapp/data/remote/TimezoneDbApiService.kt @@ -12,25 +12,25 @@ const val TIMEZONE_DB_API_KEY = "AAHLCYFT7MLW" interface TimezoneDbApiService { @GET("get-time-zone") fun getTimezoneByLatLng( - @Query("lat") lat: Double, - @Query("lng") lng: Double + @Query("lat") lat: Double, + @Query("lng") lng: Double ): Single } fun getZoneId( - timezoneDbApiService: TimezoneDbApiService, - latitude: Double, - longitude: Double + timezoneDbApiService: TimezoneDbApiService, + latitude: Double, + longitude: Double ): Single { return timezoneDbApiService - .getTimezoneByLatLng(latitude, longitude) - .subscribeOn(Schedulers.io()) - .map { - if (it.status != "OK") { - "" - } else { - it.zoneName - } + .getTimezoneByLatLng(latitude, longitude) + .subscribeOn(Schedulers.io()) + .map { + if (it.status != "OK") { + "" + } else { + it.zoneName } - .onErrorReturnItem("") + } + .onErrorReturnItem("") } diff --git a/app/src/main/java/com/hoc/weatherapp/initializer/AndroidThreeTenInitializer.kt b/app/src/main/java/com/hoc/weatherapp/initializer/AndroidThreeTenInitializer.kt index 703f7fd..929c7fb 100644 --- a/app/src/main/java/com/hoc/weatherapp/initializer/AndroidThreeTenInitializer.kt +++ b/app/src/main/java/com/hoc/weatherapp/initializer/AndroidThreeTenInitializer.kt @@ -8,7 +8,7 @@ import com.jakewharton.threetenabp.AndroidThreeTen @Suppress("unused") class AndroidThreeTenInitializer : Initializer { override fun create(context: Context) = AndroidThreeTen.init(context) - .also { debug("AndroidThreeTenInitializer", "Initializer") } + .also { debug("AndroidThreeTenInitializer", "Initializer") } override fun dependencies(): List>> = emptyList() -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/initializer/KoinInitializer.kt b/app/src/main/java/com/hoc/weatherapp/initializer/KoinInitializer.kt index 2cc8bc8..3a885cb 100644 --- a/app/src/main/java/com/hoc/weatherapp/initializer/KoinInitializer.kt +++ b/app/src/main/java/com/hoc/weatherapp/initializer/KoinInitializer.kt @@ -47,4 +47,4 @@ fun Context.startKoinIfNeeded(): Koin { ) ) }.koin -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/initializer/NotificationInitializer.kt b/app/src/main/java/com/hoc/weatherapp/initializer/NotificationInitializer.kt index bf56dbd..16d7f0b 100644 --- a/app/src/main/java/com/hoc/weatherapp/initializer/NotificationInitializer.kt +++ b/app/src/main/java/com/hoc/weatherapp/initializer/NotificationInitializer.kt @@ -16,9 +16,9 @@ class NotificationInitializer : Initializer { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel( - context.getString(R.string.notification_channel_id), - context.getString(R.string.notification_channel_name), - NotificationManager.IMPORTANCE_DEFAULT + context.getString(R.string.notification_channel_id), + context.getString(R.string.notification_channel_name), + NotificationManager.IMPORTANCE_DEFAULT ).apply { description = "Notification channel of weather app" } getSystemService(context, NotificationManager::class.java)!!.createNotificationChannel(channel) @@ -26,4 +26,4 @@ class NotificationInitializer : Initializer { } override fun dependencies(): List>> = emptyList() -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/initializer/PlacesApiInitializer.kt b/app/src/main/java/com/hoc/weatherapp/initializer/PlacesApiInitializer.kt index ead5b8f..79cf580 100644 --- a/app/src/main/java/com/hoc/weatherapp/initializer/PlacesApiInitializer.kt +++ b/app/src/main/java/com/hoc/weatherapp/initializer/PlacesApiInitializer.kt @@ -15,4 +15,4 @@ class PlacesApiInitializer : Initializer { } override fun dependencies(): List>> = emptyList() -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/initializer/RxJavaPluginsInitializer.kt b/app/src/main/java/com/hoc/weatherapp/initializer/RxJavaPluginsInitializer.kt index d585543..b514b24 100644 --- a/app/src/main/java/com/hoc/weatherapp/initializer/RxJavaPluginsInitializer.kt +++ b/app/src/main/java/com/hoc/weatherapp/initializer/RxJavaPluginsInitializer.kt @@ -17,4 +17,4 @@ class RxJavaPluginsInitializer : Initializer { } override fun dependencies(): List>> = emptyList() -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/initializer/WorkManagerInitializer.kt b/app/src/main/java/com/hoc/weatherapp/initializer/WorkManagerInitializer.kt index 11b2aa2..a2311d7 100644 --- a/app/src/main/java/com/hoc/weatherapp/initializer/WorkManagerInitializer.kt +++ b/app/src/main/java/com/hoc/weatherapp/initializer/WorkManagerInitializer.kt @@ -15,34 +15,34 @@ class WorkManagerInitializer : Initializer { debug("WorkManagerInitializer", "Initializer") WorkManager.initialize( - context, - Configuration.Builder() - .setMinimumLoggingLevel(Log.INFO) - .build() + context, + Configuration.Builder() + .setMinimumLoggingLevel(Log.INFO) + .build() ) WorkManager.getInstance(context).run { getWorkInfosForUniqueWorkLiveData(UpdateDailyWeatherWorker.UNIQUE_WORK_NAME) - .observeForever { - it.forEach { workInfo -> - debug( - "data=${workInfo.outputData.keyValueMap}, info=$workInfo", - UpdateDailyWeatherWorker.TAG - ) - } + .observeForever { + it.forEach { workInfo -> + debug( + "data=${workInfo.outputData.keyValueMap}, info=$workInfo", + UpdateDailyWeatherWorker.TAG + ) } + } getWorkInfosForUniqueWorkLiveData(UpdateCurrentWeatherWorker.UNIQUE_WORK_NAME) - .observeForever { - it.forEach { workInfo -> - debug( - "data=${workInfo.outputData.keyValueMap}, info=$workInfo", - UpdateCurrentWeatherWorker.TAG - ) - } + .observeForever { + it.forEach { workInfo -> + debug( + "data=${workInfo.outputData.keyValueMap}, info=$workInfo", + UpdateCurrentWeatherWorker.TAG + ) } + } } } override fun dependencies(): List>> = emptyList() -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/koin/DataSourceModule.kt b/app/src/main/java/com/hoc/weatherapp/koin/DataSourceModule.kt index 6a09889..e1d50c8 100644 --- a/app/src/main/java/com/hoc/weatherapp/koin/DataSourceModule.kt +++ b/app/src/main/java/com/hoc/weatherapp/koin/DataSourceModule.kt @@ -1,7 +1,18 @@ package com.hoc.weatherapp.koin -import com.hoc.weatherapp.data.* -import com.hoc.weatherapp.data.local.* +import com.hoc.weatherapp.data.CityRepository +import com.hoc.weatherapp.data.CityRepositoryImpl +import com.hoc.weatherapp.data.CurrentWeatherRepository +import com.hoc.weatherapp.data.CurrentWeatherRepositoryImpl +import com.hoc.weatherapp.data.FiveDayForecastRepository +import com.hoc.weatherapp.data.FiveDayForecastRepositoryImpl +import com.hoc.weatherapp.data.local.AppDatabase +import com.hoc.weatherapp.data.local.CityDao +import com.hoc.weatherapp.data.local.CityLocalDataSource +import com.hoc.weatherapp.data.local.CurrentWeatherDao +import com.hoc.weatherapp.data.local.CurrentWeatherLocalDataSource +import com.hoc.weatherapp.data.local.FiveDayForecastDao +import com.hoc.weatherapp.data.local.FiveDayForecastLocalDataSource import org.koin.android.ext.koin.androidContext import org.koin.core.scope.Scope import org.koin.dsl.bind @@ -67,4 +78,4 @@ private fun Scope.getCityRepositoryImpl(): CityRepositoryImpl { private fun Scope.getFiveDayForecastRepositoryImpl(): FiveDayForecastRepositoryImpl { return FiveDayForecastRepositoryImpl(get(), get(), get()) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/koin/PresenterModule.kt b/app/src/main/java/com/hoc/weatherapp/koin/PresenterModule.kt index 518b374..31d0ddd 100644 --- a/app/src/main/java/com/hoc/weatherapp/koin/PresenterModule.kt +++ b/app/src/main/java/com/hoc/weatherapp/koin/PresenterModule.kt @@ -13,7 +13,8 @@ import org.koin.android.ext.koin.androidApplication import org.koin.core.scope.Scope import org.koin.dsl.module -@ExperimentalStdlibApi val presenterModule = module { +@ExperimentalStdlibApi +val presenterModule = module { factory { getCitiesPresenter() } factory { getCurrentWeatherPresenter() } @@ -62,4 +63,4 @@ private fun Scope.getCurrentWeatherPresenter(): CurrentWeatherPresenter { @ExperimentalStdlibApi private fun Scope.getCitiesPresenter(): CitiesPresenter { return CitiesPresenter(get(), get(), get(), get(), androidApplication()) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/koin/RetrofitModule.kt b/app/src/main/java/com/hoc/weatherapp/koin/RetrofitModule.kt index 939b4be..12605b4 100644 --- a/app/src/main/java/com/hoc/weatherapp/koin/RetrofitModule.kt +++ b/app/src/main/java/com/hoc/weatherapp/koin/RetrofitModule.kt @@ -2,7 +2,12 @@ package com.hoc.weatherapp.koin import com.hoc.weatherapp.BuildConfig import com.hoc.weatherapp.data.models.TemperatureUnit -import com.hoc.weatherapp.data.remote.* +import com.hoc.weatherapp.data.remote.BASE_URL_TIMEZONE_DB +import com.hoc.weatherapp.data.remote.OPEN_WEATHER_MAP_APP_ID +import com.hoc.weatherapp.data.remote.OPEN_WEATHER_MAP_BASE_URL +import com.hoc.weatherapp.data.remote.OpenWeatherMapApiService +import com.hoc.weatherapp.data.remote.TIMEZONE_DB_API_KEY +import com.hoc.weatherapp.data.remote.TimezoneDbApiService import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import okhttp3.OkHttpClient @@ -33,79 +38,80 @@ val retrofitModule = module { private fun Scope.getTimezoneDbApiService(): TimezoneDbApiService { return get(TIMEZONE_DB_RETROFIT) - .create(TimezoneDbApiService::class.java) + .create(TimezoneDbApiService::class.java) } private fun Scope.getTimezoneDbRetrofit(): Retrofit { return Retrofit.Builder() - .baseUrl(BASE_URL_TIMEZONE_DB) - .client(get()) - .addConverterFactory(MoshiConverterFactory.create(get())) - .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) - .build() + .baseUrl(BASE_URL_TIMEZONE_DB) + .client(get()) + .addConverterFactory(MoshiConverterFactory.create(get())) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .build() } private fun Scope.getWeatherApiService(): OpenWeatherMapApiService { return get(OPEN_WEATHER_MAP_RETROFIT) - .create(OpenWeatherMapApiService::class.java) + .create(OpenWeatherMapApiService::class.java) } private fun Scope.getOpenWeatherMapRetrofit(): Retrofit { return Retrofit.Builder() - .baseUrl(OPEN_WEATHER_MAP_BASE_URL) - .client(get()) - .addConverterFactory(MoshiConverterFactory.create(get())) - .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) - .build() + .baseUrl(OPEN_WEATHER_MAP_BASE_URL) + .client(get()) + .addConverterFactory(MoshiConverterFactory.create(get())) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .build() } private fun getMoshi(): Moshi { return Moshi.Builder() - .add(KotlinJsonAdapterFactory()) - .build() + .add(KotlinJsonAdapterFactory()) + .build() } private fun getOkHttpClient(): OkHttpClient { return OkHttpClient.Builder() - .apply { - if (BuildConfig.DEBUG) { - HttpLoggingInterceptor() - .setLevel(HttpLoggingInterceptor.Level.BODY) - .let(::addInterceptor) - } + .apply { + if (BuildConfig.DEBUG) { + HttpLoggingInterceptor() + .setLevel(HttpLoggingInterceptor.Level.BODY) + .let(::addInterceptor) } - .addInterceptor { chain -> - val originalRequest = chain.request() - val host = originalRequest.url.host + } + .addInterceptor { chain -> + val originalRequest = chain.request() + val host = originalRequest.url.host - when { - "openweathermap" in host -> originalRequest + when { + "openweathermap" in host -> + originalRequest + .newBuilder() + .url( + originalRequest.url + .newBuilder() + .addQueryParameter("units", TemperatureUnit.KELVIN.toString()) + .addQueryParameter("appid", OPEN_WEATHER_MAP_APP_ID) + .build() + ) + "timezonedb" in host -> { + if ("get-time-zone" in originalRequest.url.encodedPath) { + originalRequest .newBuilder() .url( - originalRequest.url - .newBuilder() - .addQueryParameter("units", TemperatureUnit.KELVIN.toString()) - .addQueryParameter("appid", OPEN_WEATHER_MAP_APP_ID) - .build() - ) - "timezonedb" in host -> { - if ("get-time-zone" in originalRequest.url.encodedPath) { - originalRequest + originalRequest.url .newBuilder() - .url( - originalRequest.url - .newBuilder() - .addQueryParameter("format", "json") - .addQueryParameter("key", TIMEZONE_DB_API_KEY) - .addQueryParameter("by", "position") - .build() - ) - } else { - return@addInterceptor chain.proceed(originalRequest) - } + .addQueryParameter("format", "json") + .addQueryParameter("key", TIMEZONE_DB_API_KEY) + .addQueryParameter("by", "position") + .build() + ) + } else { + return@addInterceptor chain.proceed(originalRequest) } - else -> return@addInterceptor chain.proceed(originalRequest) - }.build().let(chain::proceed) - } - .build() -} \ No newline at end of file + } + else -> return@addInterceptor chain.proceed(originalRequest) + }.build().let(chain::proceed) + } + .build() +} diff --git a/app/src/main/java/com/hoc/weatherapp/koin/SharePrefUtilModule.kt b/app/src/main/java/com/hoc/weatherapp/koin/SharePrefUtilModule.kt index cd4b385..81eafae 100644 --- a/app/src/main/java/com/hoc/weatherapp/koin/SharePrefUtilModule.kt +++ b/app/src/main/java/com/hoc/weatherapp/koin/SharePrefUtilModule.kt @@ -26,4 +26,4 @@ private fun Scope.getSettingPreference(): SettingPreferences { private fun Scope.getSharedPreferences(): SharedPreferences { return PreferenceManager.getDefaultSharedPreferences(androidApplication()) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/ui/BaseMviActivity.kt b/app/src/main/java/com/hoc/weatherapp/ui/BaseMviActivity.kt index 452870b..2b2410a 100644 --- a/app/src/main/java/com/hoc/weatherapp/ui/BaseMviActivity.kt +++ b/app/src/main/java/com/hoc/weatherapp/ui/BaseMviActivity.kt @@ -97,4 +97,4 @@ private fun AppCompatActivity.setTheme(isDarkTheme: Boolean, noActionBar: Boolea } } ) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/ui/BaseMviFragment.kt b/app/src/main/java/com/hoc/weatherapp/ui/BaseMviFragment.kt index 0049c93..c3d141f 100644 --- a/app/src/main/java/com/hoc/weatherapp/ui/BaseMviFragment.kt +++ b/app/src/main/java/com/hoc/weatherapp/ui/BaseMviFragment.kt @@ -8,13 +8,12 @@ import com.hannesdorfmann.mosby3.mvi.MviFragment import com.hannesdorfmann.mosby3.mvi.MviPresenter import com.hannesdorfmann.mosby3.mvp.MvpView -abstract class BaseMviFragment>( +abstract class BaseMviFragment>( @LayoutRes private val contentLayoutId: Int -): MviFragment() { +) : MviFragment() { final override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ) = inflater.inflate(contentLayoutId, container, false)!! } - diff --git a/app/src/main/java/com/hoc/weatherapp/ui/LiveWeatherActivity.kt b/app/src/main/java/com/hoc/weatherapp/ui/LiveWeatherActivity.kt index e13d160..6f5b9a5 100644 --- a/app/src/main/java/com/hoc/weatherapp/ui/LiveWeatherActivity.kt +++ b/app/src/main/java/com/hoc/weatherapp/ui/LiveWeatherActivity.kt @@ -29,4 +29,4 @@ class LiveWeatherActivity : BaseAppCompatActivity(R.layout.activity_live_weather else -> super.onOptionsItemSelected(item) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/ui/SplashActivity.kt b/app/src/main/java/com/hoc/weatherapp/ui/SplashActivity.kt index 7c02210..deef80a 100644 --- a/app/src/main/java/com/hoc/weatherapp/ui/SplashActivity.kt +++ b/app/src/main/java/com/hoc/weatherapp/ui/SplashActivity.kt @@ -9,9 +9,11 @@ import com.hoc.weatherapp.ui.main.MainActivity class SplashActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - startActivity(Intent(this, MainActivity::class.java).apply { - addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP) - }) + startActivity( + Intent(this, MainActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP) + } + ) finish() } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/ui/addcity/AddCityActivity.kt b/app/src/main/java/com/hoc/weatherapp/ui/addcity/AddCityActivity.kt index 1c7b262..6306583 100644 --- a/app/src/main/java/com/hoc/weatherapp/ui/addcity/AddCityActivity.kt +++ b/app/src/main/java/com/hoc/weatherapp/ui/addcity/AddCityActivity.kt @@ -28,8 +28,8 @@ import com.jakewharton.rxbinding3.view.clicks import com.tbruyelle.rxpermissions2.RxPermissions import io.reactivex.Observable import io.reactivex.subjects.PublishSubject -import org.koin.android.ext.android.get import java.util.concurrent.TimeUnit +import org.koin.android.ext.android.get @ExperimentalStdlibApi class AddCityActivity : diff --git a/app/src/main/java/com/hoc/weatherapp/ui/addcity/AddCityContract.kt b/app/src/main/java/com/hoc/weatherapp/ui/addcity/AddCityContract.kt index 1e8561f..9255c80 100644 --- a/app/src/main/java/com/hoc/weatherapp/ui/addcity/AddCityContract.kt +++ b/app/src/main/java/com/hoc/weatherapp/ui/addcity/AddCityContract.kt @@ -20,4 +20,4 @@ interface AddCityContract { fun render(state: ViewState) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/ui/addcity/AddCityPresenter.kt b/app/src/main/java/com/hoc/weatherapp/ui/addcity/AddCityPresenter.kt index dd664a2..bbbf092 100644 --- a/app/src/main/java/com/hoc/weatherapp/ui/addcity/AddCityPresenter.kt +++ b/app/src/main/java/com/hoc/weatherapp/ui/addcity/AddCityPresenter.kt @@ -23,21 +23,21 @@ import java.util.concurrent.TimeUnit import java.util.concurrent.TimeoutException class AddCityPresenter( - private val cityRepository: CityRepository, - private val currentWeatherRepository: CurrentWeatherRepository, - private val application: Application + private val cityRepository: CityRepository, + private val currentWeatherRepository: CurrentWeatherRepository, + private val application: Application ) : MviBasePresenter() { private val locationRequestLazy = MyUnsafeLazyImpl { LocationRequest() - .setInterval(500) - .setFastestInterval(500) - .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) - .setNumUpdates(1) + .setInterval(500) + .setFastestInterval(500) + .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) + .setNumUpdates(1) } private val locationSettingsRequestLazy = MyUnsafeLazyImpl { LocationSettingsRequest.Builder() - .addLocationRequest(locationRequest) - .build() + .addLocationRequest(locationRequest) + .build() } private val fusedLocationProviderClientLazy = MyUnsafeLazyImpl { LocationServices.getFusedLocationProviderClient(application) @@ -52,82 +52,82 @@ class AddCityPresenter( private val settingsClient by settingsClientLazy private val addCityTransformer = - ObservableTransformer, ViewState> { latLng -> - latLng - .flatMap { (latitude, longitude) -> - cityRepository - .addCityByLatLng(latitude, longitude) - .doOnSuccess { city -> - currentWeatherRepository - .refreshWeatherOf(city) - .subscribeBy( - onSuccess = { debug("refreshWeatherOf success.. $it", TAG) }, - onError = { debug("refreshWeatherOf failure... $it", TAG, it) } - ) - } - .toObservable() - .flatMapIterable { - listOf( - ViewState.AddCitySuccessfully(city = it, showMessage = true), - ViewState.AddCitySuccessfully(city = it, showMessage = false) - ) - } - .cast() - .onErrorResumeNext { throwable: Throwable -> - listOf( - ViewState.Error(showMessage = true, throwable = throwable), - ViewState.Error(showMessage = false, throwable = throwable) - ).toObservable() - } - .observeOn(AndroidSchedulers.mainThread()) - .startWith(ViewState.Loading) + ObservableTransformer, ViewState> { latLng -> + latLng + .flatMap { (latitude, longitude) -> + cityRepository + .addCityByLatLng(latitude, longitude) + .doOnSuccess { city -> + currentWeatherRepository + .refreshWeatherOf(city) + .subscribeBy( + onSuccess = { debug("refreshWeatherOf success.. $it", TAG) }, + onError = { debug("refreshWeatherOf failure... $it", TAG, it) } + ) } - } + .toObservable() + .flatMapIterable { + listOf( + ViewState.AddCitySuccessfully(city = it, showMessage = true), + ViewState.AddCitySuccessfully(city = it, showMessage = false) + ) + } + .cast() + .onErrorResumeNext { throwable: Throwable -> + listOf( + ViewState.Error(showMessage = true, throwable = throwable), + ViewState.Error(showMessage = false, throwable = throwable) + ).toObservable() + } + .observeOn(AndroidSchedulers.mainThread()) + .startWith(ViewState.Loading) + } + } private val addCurrentLocationProcessor = - ObservableTransformer { addCurrentLocationIntent -> - addCurrentLocationIntent - .exhaustMap { - application - .checkLocationSettingAndGetCurrentLocation( - settingsClient = settingsClient, - fusedLocationProviderClient = fusedLocationProviderClient, - locationRequest = locationRequest, - locationSettingsRequest = locationSettingsRequest - ) - .subscribeOn(AndroidSchedulers.mainThread()) - .timeout(5_000, TimeUnit.MILLISECONDS) // 5 seconds timeout - .retry { count, throwable -> throwable is TimeoutException && count < 3 } // Try 3 times - .toObservable() - .map { it.latitude to it.longitude } - .compose(addCityTransformer) - .onErrorResumeNext { throwable: Throwable -> - val newThrowable = if (throwable is TimeoutException) { - TimeoutException("timeout to get current location. Try again!") - } else { - throwable - } - listOf( - ViewState.Error(showMessage = true, throwable = newThrowable), - ViewState.Error(showMessage = false, throwable = newThrowable) - ).toObservable() - } - .observeOn(AndroidSchedulers.mainThread()) - .startWith(ViewState.Loading) + ObservableTransformer { addCurrentLocationIntent -> + addCurrentLocationIntent + .exhaustMap { + application + .checkLocationSettingAndGetCurrentLocation( + settingsClient = settingsClient, + fusedLocationProviderClient = fusedLocationProviderClient, + locationRequest = locationRequest, + locationSettingsRequest = locationSettingsRequest + ) + .subscribeOn(AndroidSchedulers.mainThread()) + .timeout(5_000, TimeUnit.MILLISECONDS) // 5 seconds timeout + .retry { count, throwable -> throwable is TimeoutException && count < 3 } // Try 3 times + .toObservable() + .map { it.latitude to it.longitude } + .compose(addCityTransformer) + .onErrorResumeNext { throwable: Throwable -> + val newThrowable = if (throwable is TimeoutException) { + TimeoutException("timeout to get current location. Try again!") + } else { + throwable + } + listOf( + ViewState.Error(showMessage = true, throwable = newThrowable), + ViewState.Error(showMessage = false, throwable = newThrowable) + ).toObservable() } - } + .observeOn(AndroidSchedulers.mainThread()) + .startWith(ViewState.Loading) + } + } override fun bindIntents() { val addCurrentLocation = - intent(View::addCurrentLocationIntent).compose(addCurrentLocationProcessor) + intent(View::addCurrentLocationIntent).compose(addCurrentLocationProcessor) val addCityByLatLng = intent(View::addCityByLatLngIntent).compose(addCityTransformer) subscribeViewState( - Observable.mergeArray(addCityByLatLng, addCurrentLocation) - .distinctUntilChanged() - .doOnNext { debug("viewState = $it", TAG) } - .observeOn(AndroidSchedulers.mainThread()), - View::render + Observable.mergeArray(addCityByLatLng, addCurrentLocation) + .distinctUntilChanged() + .doOnNext { debug("viewState = $it", TAG) } + .observeOn(AndroidSchedulers.mainThread()), + View::render ) } @@ -142,4 +142,4 @@ class AddCityPresenter( private companion object { private const val TAG = "__add_city__" } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/ui/cities/CitiesActivity.kt b/app/src/main/java/com/hoc/weatherapp/ui/cities/CitiesActivity.kt index 991ca42..ddd1f23 100644 --- a/app/src/main/java/com/hoc/weatherapp/ui/cities/CitiesActivity.kt +++ b/app/src/main/java/com/hoc/weatherapp/ui/cities/CitiesActivity.kt @@ -28,9 +28,9 @@ import com.hoc081098.viewbindingdelegate.viewBinding import io.reactivex.Observable import io.reactivex.rxkotlin.cast import io.reactivex.subjects.PublishSubject.create -import org.koin.android.ext.android.get import java.util.concurrent.TimeUnit import kotlin.LazyThreadSafetyMode.NONE +import org.koin.android.ext.android.get @ExperimentalStdlibApi class CitiesActivity : BaseMviActivity(R.layout.activity_cities), View { @@ -149,24 +149,24 @@ class CitiesActivity : BaseMviActivity(R.layout.activity_ }) val swipeController = SwipeController(object : - SwipeControllerActions { - override fun onLeftClicked(adapterPosition: Int) { - refreshPositionPublishSubject.onNext(adapterPosition) - } + SwipeControllerActions { + override fun onLeftClicked(adapterPosition: Int) { + refreshPositionPublishSubject.onNext(adapterPosition) + } - override fun onRightClicked(adapterPosition: Int) { - AlertDialog.Builder(this@CitiesActivity) - .setTitle("Delete city") - .setMessage("Do you want to delete this city") - .setIcon(R.drawable.ic_delete_black_24dp) - .setNegativeButton("Cancel") { dialog, _ -> dialog.dismiss() } - .setPositiveButton("Ok") { dialog, _ -> - dialog.dismiss() - deletePositionPublishSubject.onNext(adapterPosition) - } - .show() - } - }) + override fun onRightClicked(adapterPosition: Int) { + AlertDialog.Builder(this@CitiesActivity) + .setTitle("Delete city") + .setMessage("Do you want to delete this city") + .setIcon(R.drawable.ic_delete_black_24dp) + .setNegativeButton("Cancel") { dialog, _ -> dialog.dismiss() } + .setPositiveButton("Ok") { dialog, _ -> + dialog.dismiss() + deletePositionPublishSubject.onNext(adapterPosition) + } + .show() + } + }) ItemTouchHelper(swipeController).attachToRecyclerView(this) addItemDecoration(object : RecyclerView.ItemDecoration() { override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { @@ -180,7 +180,6 @@ class CitiesActivity : BaseMviActivity(R.layout.activity_ return when (item.itemId) { android.R.id.home -> true.also { finish() } else -> return super.onOptionsItemSelected(item) - } } diff --git a/app/src/main/java/com/hoc/weatherapp/ui/cities/CitiesContract.kt b/app/src/main/java/com/hoc/weatherapp/ui/cities/CitiesContract.kt index c32b49c..ef61150 100644 --- a/app/src/main/java/com/hoc/weatherapp/ui/cities/CitiesContract.kt +++ b/app/src/main/java/com/hoc/weatherapp/ui/cities/CitiesContract.kt @@ -9,29 +9,29 @@ interface CitiesContract { data class CityListItems(val items: List) : PartialStateChange() data class Error( - val throwable: Throwable, - val showMessage: Boolean + val throwable: Throwable, + val showMessage: Boolean ) : PartialStateChange() data class DeleteCity( - val showMessage: Boolean, - val deletedCity: City + val showMessage: Boolean, + val deletedCity: City ) : PartialStateChange() data class RefreshWeather( - val showMessage: Boolean, - val refreshCity: City + val showMessage: Boolean, + val refreshCity: City ) : PartialStateChange() } data class ViewState( - val cityListItems: List = emptyList(), - val error: Throwable? = null, - val showError: Boolean = false, - val showDeleteCitySuccessfully: Boolean = false, - val deletedCity: City? = null, - val showRefreshSuccessfully: Boolean = false, - val refreshCity: City? = null + val cityListItems: List = emptyList(), + val error: Throwable? = null, + val showError: Boolean = false, + val showDeleteCitySuccessfully: Boolean = false, + val deletedCity: City? = null, + val showRefreshSuccessfully: Boolean = false, + val refreshCity: City? = null ) sealed class SearchStringIntent { @@ -55,4 +55,4 @@ interface CitiesContract { fun render(state: ViewState) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/ui/cities/CitiesPresenter.kt b/app/src/main/java/com/hoc/weatherapp/ui/cities/CitiesPresenter.kt index cddee97..452e59d 100644 --- a/app/src/main/java/com/hoc/weatherapp/ui/cities/CitiesPresenter.kt +++ b/app/src/main/java/com/hoc/weatherapp/ui/cities/CitiesPresenter.kt @@ -9,191 +9,205 @@ import com.hoc.weatherapp.data.FiveDayForecastRepository import com.hoc.weatherapp.data.local.SettingPreferences import com.hoc.weatherapp.data.models.entity.City import com.hoc.weatherapp.data.models.entity.CityAndCurrentWeather -import com.hoc.weatherapp.ui.cities.CitiesContract.* -import com.hoc.weatherapp.ui.cities.CitiesContract.PartialStateChange.* +import com.hoc.weatherapp.ui.cities.CitiesContract.PartialStateChange +import com.hoc.weatherapp.ui.cities.CitiesContract.PartialStateChange.CityListItems +import com.hoc.weatherapp.ui.cities.CitiesContract.PartialStateChange.DeleteCity +import com.hoc.weatherapp.ui.cities.CitiesContract.PartialStateChange.Error +import com.hoc.weatherapp.ui.cities.CitiesContract.PartialStateChange.RefreshWeather +import com.hoc.weatherapp.ui.cities.CitiesContract.SearchStringIntent import com.hoc.weatherapp.ui.cities.CitiesContract.SearchStringIntent.InitialSearchStringIntent -import com.hoc.weatherapp.utils.* +import com.hoc.weatherapp.ui.cities.CitiesContract.View +import com.hoc.weatherapp.ui.cities.CitiesContract.ViewState +import com.hoc.weatherapp.utils.WEATHER_NOTIFICATION_ID +import com.hoc.weatherapp.utils.cancelNotificationById +import com.hoc.weatherapp.utils.debug +import com.hoc.weatherapp.utils.getOrNull +import com.hoc.weatherapp.utils.notOfType +import com.hoc.weatherapp.utils.showNotificationIfEnabled +import com.hoc.weatherapp.utils.toZonedDateTime import com.hoc.weatherapp.worker.WorkerUtil import io.reactivex.Observable import io.reactivex.ObservableTransformer import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.functions.BiFunction import io.reactivex.functions.Function -import io.reactivex.rxkotlin.* import io.reactivex.rxkotlin.Observables.combineLatest +import io.reactivex.rxkotlin.cast +import io.reactivex.rxkotlin.ofType +import io.reactivex.rxkotlin.subscribeBy +import io.reactivex.rxkotlin.withLatestFrom +import io.reactivex.rxkotlin.zipWith import java.util.concurrent.TimeUnit @ExperimentalStdlibApi class CitiesPresenter( - private val cityRepository: CityRepository, - private val currentWeatherRepository: CurrentWeatherRepository, - private val fiveDayForecastRepository: FiveDayForecastRepository, - private val settingPreferences: SettingPreferences, - private val androidApplication: Application + private val cityRepository: CityRepository, + private val currentWeatherRepository: CurrentWeatherRepository, + private val fiveDayForecastRepository: FiveDayForecastRepository, + private val settingPreferences: SettingPreferences, + private val androidApplication: Application ) : MviBasePresenter() { private val searchIntentProcessor = - ObservableTransformer> { intentObservable -> - intentObservable.publish { shared -> - Observable.mergeArray( - shared.ofType().take(1), - shared.notOfType() - ) - } - .cast() - .map { it.value } - .switchMap(currentWeatherRepository::getAllCityAndCurrentWeathers) - .share() + ObservableTransformer> { intentObservable -> + intentObservable.publish { shared -> + Observable.mergeArray( + shared.ofType().take(1), + shared.notOfType() + ) } + .cast() + .map { it.value } + .switchMap(currentWeatherRepository::getAllCityAndCurrentWeathers) + .share() + } private val cityListItemsPartialChange = - ObservableTransformer, PartialStateChange> { cityAndCurrentWeatherObservable -> - combineLatest( - cityRepository.getSelectedCity(), - cityAndCurrentWeatherObservable, - settingPreferences.temperatureUnitPreference.observable - ) - .map { (city, list, temperatureUnit) -> - list.map { - CityListItem( - city = it.city, - temperatureMin = temperatureUnit.format(it.currentWeather.temperatureMin), - temperatureMax = temperatureUnit.format(it.currentWeather.temperatureMax), - weatherDescription = it.currentWeather.description, - weatherConditionId = it.currentWeather.weatherConditionId, - weatherIcon = it.currentWeather.icon, - isSelected = it.city == city.getOrNull(), - lastUpdated = it.currentWeather.dataTime.toZonedDateTime(it.city.zoneId) - ) - } - } - .map(::CityListItems) - .cast() - .observeOn(AndroidSchedulers.mainThread()) - .onErrorResumeNext(showError) - } + ObservableTransformer, PartialStateChange> { cityAndCurrentWeatherObservable -> + combineLatest( + cityRepository.getSelectedCity(), + cityAndCurrentWeatherObservable, + settingPreferences.temperatureUnitPreference.observable + ) + .map { (city, list, temperatureUnit) -> + list.map { + CityListItem( + city = it.city, + temperatureMin = temperatureUnit.format(it.currentWeather.temperatureMin), + temperatureMax = temperatureUnit.format(it.currentWeather.temperatureMax), + weatherDescription = it.currentWeather.description, + weatherConditionId = it.currentWeather.weatherConditionId, + weatherIcon = it.currentWeather.icon, + isSelected = it.city == city.getOrNull(), + lastUpdated = it.currentWeather.dataTime.toZonedDateTime(it.city.zoneId) + ) + } + } + .map(::CityListItems) + .cast() + .observeOn(AndroidSchedulers.mainThread()) + .onErrorResumeNext(showError) + } private val changeSelectedCityProcessor: ObservableTransformer = - ObservableTransformer { intent -> - val getWeatherSingle = currentWeatherRepository - .refreshCurrentWeatherOfSelectedCity() - .zipWith(fiveDayForecastRepository.refreshFiveDayForecastOfSelectedCity()) - .doOnSuccess { (cityAndCurrentWeather) -> - if (settingPreferences.autoUpdatePreference.value) { - WorkerUtil.enqueueUpdateCurrentWeatherWorkRequest() - WorkerUtil.enqueueUpdateDailyWeatherWorkRequest() - } - androidApplication.showNotificationIfEnabled(cityAndCurrentWeather, settingPreferences) - } - intent.switchMap { city -> - cityRepository - .changeSelectedCity(city) - .andThen(getWeatherSingle) - .toObservable() - .onErrorResumeNext(Observable.empty()) + ObservableTransformer { intent -> + val getWeatherSingle = currentWeatherRepository + .refreshCurrentWeatherOfSelectedCity() + .zipWith(fiveDayForecastRepository.refreshFiveDayForecastOfSelectedCity()) + .doOnSuccess { (cityAndCurrentWeather) -> + if (settingPreferences.autoUpdatePreference.value) { + WorkerUtil.enqueueUpdateCurrentWeatherWorkRequest() + WorkerUtil.enqueueUpdateDailyWeatherWorkRequest() + } + androidApplication.showNotificationIfEnabled(cityAndCurrentWeather, settingPreferences) } + intent.switchMap { city -> + cityRepository + .changeSelectedCity(city) + .andThen(getWeatherSingle) + .toObservable() + .onErrorResumeNext(Observable.empty()) } + } override fun bindIntents() { - intent(View::changeSelectedCity) - .compose(changeSelectedCityProcessor) - .subscribeBy( - onNext = { debug("setupChangeSelectedCity onNext=$it", TAG) }, - onError = { debug("setupChangeSelectedCity onNext=$it", TAG) } - ) + .compose(changeSelectedCityProcessor) + .subscribeBy( + onNext = { debug("setupChangeSelectedCity onNext=$it", TAG) }, + onError = { debug("setupChangeSelectedCity onNext=$it", TAG) } + ) val cityAndCurrentWeathers = intent(View::searchStringIntent).compose(searchIntentProcessor) subscribeViewState( - Observable.mergeArray( - cityAndCurrentWeathers.compose(cityListItemsPartialChange), - deleteCityPartialChange(cityAndCurrentWeathers), - refreshWeather(cityAndCurrentWeathers) - ).scan(ViewState(), reducer) - .distinctUntilChanged() - .doOnNext { debug("CitiesPresenter ViewState = $it", TAG) } - .observeOn(AndroidSchedulers.mainThread()), - View::render + Observable.mergeArray( + cityAndCurrentWeathers.compose(cityListItemsPartialChange), + deleteCityPartialChange(cityAndCurrentWeathers), + refreshWeather(cityAndCurrentWeathers) + ).scan(ViewState(), reducer) + .distinctUntilChanged() + .doOnNext { debug("CitiesPresenter ViewState = $it", TAG) } + .observeOn(AndroidSchedulers.mainThread()), + View::render ) } private fun refreshWeather(cityAndCurrentWeathers: Observable>): Observable { return intent(View::refreshCurrentWeatherAtPosition) - .filter { it != RecyclerView.NO_POSITION } - .withLatestFrom(cityAndCurrentWeathers) - .map { (position, list) -> list[position].city } - .flatMap { city -> - currentWeatherRepository - .refreshWeatherOf(city) - .doOnSuccess { (cityAndCurrentWeather) -> - /** - * If refresh current selected city - */ - if (cityRepository.selectedCity == city) { - - if (settingPreferences.autoUpdatePreference.value) { - WorkerUtil.enqueueUpdateCurrentWeatherWorkRequest() - WorkerUtil.enqueueUpdateDailyWeatherWorkRequest() - } - - androidApplication.showNotificationIfEnabled( - cityAndCurrentWeather, - settingPreferences - ) - } + .filter { it != RecyclerView.NO_POSITION } + .withLatestFrom(cityAndCurrentWeathers) + .map { (position, list) -> list[position].city } + .flatMap { city -> + currentWeatherRepository + .refreshWeatherOf(city) + .doOnSuccess { (cityAndCurrentWeather) -> + /** + * If refresh current selected city + */ + if (cityRepository.selectedCity == city) { + if (settingPreferences.autoUpdatePreference.value) { + WorkerUtil.enqueueUpdateCurrentWeatherWorkRequest() + WorkerUtil.enqueueUpdateDailyWeatherWorkRequest() } - .map { it.first.city } - .toObservable() - .observeOn(AndroidSchedulers.mainThread()) - .flatMap { updatedCity -> - Observable - .timer(SNACKBAR_DURATION, TimeUnit.MILLISECONDS) - .map { - RefreshWeather( - showMessage = false, - refreshCity = updatedCity - ) - } - .startWith(RefreshWeather(showMessage = true, refreshCity = updatedCity)) + + androidApplication.showNotificationIfEnabled( + cityAndCurrentWeather, + settingPreferences + ) + } + } + .map { it.first.city } + .toObservable() + .observeOn(AndroidSchedulers.mainThread()) + .flatMap { updatedCity -> + Observable + .timer(SNACKBAR_DURATION, TimeUnit.MILLISECONDS) + .map { + RefreshWeather( + showMessage = false, + refreshCity = updatedCity + ) } - .onErrorResumeNext(showError) - } + .startWith(RefreshWeather(showMessage = true, refreshCity = updatedCity)) + } + .onErrorResumeNext(showError) + } } private fun deleteCityPartialChange(cityAndCurrentWeathers: Observable>): Observable { return intent(View::deleteCityAtPosition) - .filter { it != RecyclerView.NO_POSITION } - .withLatestFrom(cityAndCurrentWeathers) - .map { (position, list) -> list[position].city } - .flatMap { city -> - cityRepository - .deleteCity(city) - .doOnSuccess { - /** - * If delete selected city - */ - if (cityRepository.selectedCity === null) { - androidApplication.cancelNotificationById(WEATHER_NOTIFICATION_ID) - WorkerUtil.cancelUpdateCurrentWeatherWorkRequest() - WorkerUtil.cancelUpdateDailyWeatherWorkRequest() - } - } - .toObservable() - .observeOn(AndroidSchedulers.mainThread()) - .flatMap { deletedCity -> - Observable - .timer(SNACKBAR_DURATION, TimeUnit.MILLISECONDS) - .map { - DeleteCity( - showMessage = false, - deletedCity = deletedCity - ) - } - .startWith(DeleteCity(showMessage = true, deletedCity = deletedCity)) + .filter { it != RecyclerView.NO_POSITION } + .withLatestFrom(cityAndCurrentWeathers) + .map { (position, list) -> list[position].city } + .flatMap { city -> + cityRepository + .deleteCity(city) + .doOnSuccess { + /** + * If delete selected city + */ + if (cityRepository.selectedCity === null) { + androidApplication.cancelNotificationById(WEATHER_NOTIFICATION_ID) + WorkerUtil.cancelUpdateCurrentWeatherWorkRequest() + WorkerUtil.cancelUpdateDailyWeatherWorkRequest() + } + } + .toObservable() + .observeOn(AndroidSchedulers.mainThread()) + .flatMap { deletedCity -> + Observable + .timer(SNACKBAR_DURATION, TimeUnit.MILLISECONDS) + .map { + DeleteCity( + showMessage = false, + deletedCity = deletedCity + ) } - .onErrorResumeNext(showError) - } + .startWith(DeleteCity(showMessage = true, deletedCity = deletedCity)) + } + .onErrorResumeNext(showError) + } } private companion object { @@ -203,34 +217,33 @@ class CitiesPresenter( @JvmStatic private val reducer = - BiFunction { viewState, partialStateChange -> - when (partialStateChange) { - is CityListItems -> viewState.copy( - cityListItems = partialStateChange.items, - error = null - ) - is Error -> viewState.copy( - showError = partialStateChange.showMessage, - error = partialStateChange.throwable - ) - is DeleteCity -> viewState.copy( - showDeleteCitySuccessfully = partialStateChange.showMessage, - deletedCity = partialStateChange.deletedCity - ) - is RefreshWeather -> viewState.copy( - showRefreshSuccessfully = partialStateChange.showMessage, - refreshCity = partialStateChange.refreshCity - ) - } + BiFunction { viewState, partialStateChange -> + when (partialStateChange) { + is CityListItems -> viewState.copy( + cityListItems = partialStateChange.items, + error = null + ) + is Error -> viewState.copy( + showError = partialStateChange.showMessage, + error = partialStateChange.throwable + ) + is DeleteCity -> viewState.copy( + showDeleteCitySuccessfully = partialStateChange.showMessage, + deletedCity = partialStateChange.deletedCity + ) + is RefreshWeather -> viewState.copy( + showRefreshSuccessfully = partialStateChange.showMessage, + refreshCity = partialStateChange.refreshCity + ) } + } @JvmStatic private val showError = Function> { throwable -> Observable - .timer(SNACKBAR_DURATION, TimeUnit.MILLISECONDS) - .map { Error(showMessage = false, throwable = throwable) } - .startWith(Error(showMessage = true, throwable = throwable)) + .timer(SNACKBAR_DURATION, TimeUnit.MILLISECONDS) + .map { Error(showMessage = false, throwable = throwable) } + .startWith(Error(showMessage = true, throwable = throwable)) } } } - diff --git a/app/src/main/java/com/hoc/weatherapp/ui/cities/CityAdapter.kt b/app/src/main/java/com/hoc/weatherapp/ui/cities/CityAdapter.kt index 6595bfc..161e985 100644 --- a/app/src/main/java/com/hoc/weatherapp/ui/cities/CityAdapter.kt +++ b/app/src/main/java/com/hoc/weatherapp/ui/cities/CityAdapter.kt @@ -2,7 +2,6 @@ package com.hoc.weatherapp.ui.cities import android.annotation.SuppressLint import android.graphics.drawable.ColorDrawable -import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil @@ -19,27 +18,27 @@ import com.hoc.weatherapp.utils.themeColor import com.hoc.weatherapp.utils.ui.getIconDrawableFromCurrentWeather import com.hoc081098.viewbindingdelegate.inflateViewBinding import io.reactivex.subjects.PublishSubject +import java.util.Locale import org.threeten.bp.format.DateTimeFormatter -import java.util.* @ExperimentalStdlibApi class CitiesAdapter : ListAdapter(object : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: CityListItem, newItem: CityListItem): Boolean { - return oldItem.city.id == newItem.city.id - } + override fun areItemsTheSame(oldItem: CityListItem, newItem: CityListItem): Boolean { + return oldItem.city.id == newItem.city.id + } - override fun areContentsTheSame(oldItem: CityListItem, newItem: CityListItem): Boolean { - return oldItem == newItem - } + override fun areContentsTheSame(oldItem: CityListItem, newItem: CityListItem): Boolean { + return oldItem == newItem + } - override fun getChangePayload(oldItem: CityListItem, newItem: CityListItem): Any? { - return when { - newItem.sameExceptIsSelected(oldItem) -> newItem.isSelected - else -> null + override fun getChangePayload(oldItem: CityListItem, newItem: CityListItem): Any? { + return when { + newItem.sameExceptIsSelected(oldItem) -> newItem.isSelected + else -> null + } } - } -}) { + }) { private val _itemClickSubject = PublishSubject.create() val itemClickObservable get() = _itemClickSubject.hide()!! @@ -47,15 +46,16 @@ class CitiesAdapter : ListAdapter(object ViewHolder(parent inflateViewBinding false) override fun onBindViewHolder(holder: ViewHolder, position: Int) = - holder.bind(getItem(position)) + holder.bind(getItem(position)) override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList) { val isSelected = payloads.firstOrNull() as? Boolean ?: return onBindViewHolder(holder, position) holder.updateRadio(isSelected) } - inner class ViewHolder(private val binding: CityItemLayoutBinding) : RecyclerView.ViewHolder(binding.root), - View.OnClickListener { + inner class ViewHolder(private val binding: CityItemLayoutBinding) : + RecyclerView.ViewHolder(binding.root), + View.OnClickListener { init { itemView.setOnClickListener(this) } @@ -70,31 +70,36 @@ class CitiesAdapter : ListAdapter(object @SuppressLint("SetTextI18n") fun bind(item: CityListItem) = binding.run { textName.text = itemView.context.getString( - R.string.city_name_and_country, - item.city.name, - item.city.country + R.string.city_name_and_country, + item.city.name, + item.city.country ) - textMain.text = item.weatherDescription.capitalize(Locale.ROOT) + textMain.text = item.weatherDescription.replaceFirstChar { + if (it.isLowerCase()) it.titlecase( + Locale.ROOT + ) else it.toString() + } updateRadio(item.isSelected) - textLastUpdated.text = "${item.lastUpdated.format(TIME_FORMATTER)} (${item.lastUpdated.zone.id}): " + textLastUpdated.text = + "${item.lastUpdated.format(TIME_FORMATTER)} (${item.lastUpdated.zone.id}): " textTemps.text = "${item.temperatureMin} ~ ${item.temperatureMax}" Glide.with(itemView.context) - .load( - itemView.context.getIconDrawableFromCurrentWeather( - weatherConditionId = item.weatherConditionId, - weatherIcon = item.weatherIcon - ) - ) - .apply(RequestOptions.fitCenterTransform().centerCrop()) - .transition(DrawableTransitionOptions.withCrossFade()) - .apply( - itemView.context - .themeColor(R.attr.colorSecondary) - .let(::ColorDrawable) - .let(RequestOptions::placeholderOf) + .load( + itemView.context.getIconDrawableFromCurrentWeather( + weatherConditionId = item.weatherConditionId, + weatherIcon = item.weatherIcon ) - .into(imageIconCityItem) + ) + .apply(RequestOptions.fitCenterTransform().centerCrop()) + .transition(DrawableTransitionOptions.withCrossFade()) + .apply( + itemView.context + .themeColor(R.attr.colorSecondary) + .let(::ColorDrawable) + .let(RequestOptions::placeholderOf) + ) + .into(imageIconCityItem) Unit } diff --git a/app/src/main/java/com/hoc/weatherapp/ui/cities/CityListItem.kt b/app/src/main/java/com/hoc/weatherapp/ui/cities/CityListItem.kt index 282409e..b71ddfd 100644 --- a/app/src/main/java/com/hoc/weatherapp/ui/cities/CityListItem.kt +++ b/app/src/main/java/com/hoc/weatherapp/ui/cities/CityListItem.kt @@ -4,14 +4,14 @@ import com.hoc.weatherapp.data.models.entity.City import org.threeten.bp.ZonedDateTime data class CityListItem( - val city: City, - val temperatureMin: String, - val temperatureMax: String, - val weatherDescription: String, - val weatherConditionId: Long, - val weatherIcon: String, - val isSelected: Boolean = false, - val lastUpdated: ZonedDateTime + val city: City, + val temperatureMin: String, + val temperatureMax: String, + val weatherDescription: String, + val weatherConditionId: Long, + val weatherIcon: String, + val isSelected: Boolean = false, + val lastUpdated: ZonedDateTime ) fun CityListItem.sameExceptIsSelected(other: CityListItem): Boolean { @@ -26,4 +26,4 @@ fun CityListItem.sameExceptIsSelected(other: CityListItem): Boolean { if (lastUpdated != other.lastUpdated) return false return true -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/ui/main/ColorHolderSource.kt b/app/src/main/java/com/hoc/weatherapp/ui/main/ColorHolderSource.kt index e522baf..e4e5c22 100644 --- a/app/src/main/java/com/hoc/weatherapp/ui/main/ColorHolderSource.kt +++ b/app/src/main/java/com/hoc/weatherapp/ui/main/ColorHolderSource.kt @@ -9,12 +9,12 @@ import io.reactivex.subjects.BehaviorSubject class ColorHolderSource(androidApplication: Application) { private val subject = BehaviorSubject.createDefault( - androidApplication.themeColor(R.attr.colorPrimaryVariant) to - androidApplication.themeColor(R.attr.colorSecondary) + androidApplication.themeColor(R.attr.colorPrimaryVariant) to + androidApplication.themeColor(R.attr.colorSecondary) ) val colorObservable = subject.asObservable() @MainThread fun change(colors: Pair) = subject.onNext(colors) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/ui/main/MainActivity.kt b/app/src/main/java/com/hoc/weatherapp/ui/main/MainActivity.kt index 447ff75..830b8af 100644 --- a/app/src/main/java/com/hoc/weatherapp/ui/main/MainActivity.kt +++ b/app/src/main/java/com/hoc/weatherapp/ui/main/MainActivity.kt @@ -54,9 +54,12 @@ import org.koin.androidx.scope.createActivityRetainedScope import org.koin.core.scope.Scope @ExperimentalStdlibApi -class MainActivity : BaseMviActivity( - contentLayoutId = R.layout.activity_main -), MainContract.View, AndroidScopeComponent { +class MainActivity : + BaseMviActivity( + contentLayoutId = R.layout.activity_main + ), + MainContract.View, + AndroidScopeComponent { private val binding by viewBinding() private var mediaPlayer: MediaPlayer? = null @@ -156,7 +159,6 @@ class MainActivity : BaseMviActivity( } } - private fun updateBackground( weather: CurrentWeather, city: City @@ -190,7 +192,6 @@ class MainActivity : BaseMviActivity( .also { target1 = it } } - private fun stopSound() { runCatching { mediaPlayer?.takeIf { it.isPlaying }?.stop() diff --git a/app/src/main/java/com/hoc/weatherapp/ui/main/MainContract.kt b/app/src/main/java/com/hoc/weatherapp/ui/main/MainContract.kt index 7a57840..1d4c964 100644 --- a/app/src/main/java/com/hoc/weatherapp/ui/main/MainContract.kt +++ b/app/src/main/java/com/hoc/weatherapp/ui/main/MainContract.kt @@ -11,9 +11,9 @@ interface MainContract { abstract val vibrantColor: Int data class CityAndWeather( - val city: City, - val weather: CurrentWeather, - @ColorInt override val vibrantColor: Int + val city: City, + val weather: CurrentWeather, + @ColorInt override val vibrantColor: Int ) : ViewState() data class NoSelectedCity(@ColorInt override val vibrantColor: Int) : ViewState() @@ -24,4 +24,4 @@ interface MainContract { fun render(state: ViewState) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/ui/main/MainPresenter.kt b/app/src/main/java/com/hoc/weatherapp/ui/main/MainPresenter.kt index ba1071f..2f72c86 100644 --- a/app/src/main/java/com/hoc/weatherapp/ui/main/MainPresenter.kt +++ b/app/src/main/java/com/hoc/weatherapp/ui/main/MainPresenter.kt @@ -15,34 +15,34 @@ import io.reactivex.disposables.Disposable import io.reactivex.rxkotlin.Observables class MainPresenter( - currentWeatherRepository: CurrentWeatherRepository, - private val colorHolderSource: ColorHolderSource, - private val androidApplication: Application + currentWeatherRepository: CurrentWeatherRepository, + private val colorHolderSource: ColorHolderSource, + private val androidApplication: Application ) : MviBasePresenter() { private var disposable: Disposable? = null private val state = Observables.combineLatest( - source1 = currentWeatherRepository.getSelectedCityAndCurrentWeatherOfSelectedCity(), - source2 = colorHolderSource.colorObservable - ).map { - when (val optional = it.first) { - None -> NoSelectedCity(androidApplication.themeColor(R.attr.colorPrimaryVariant)) - is Some -> CityAndWeather( - city = optional.value.city, - weather = optional.value.currentWeather, - vibrantColor = it.second.first - ) - } - } - .distinctUntilChanged() - .doOnNext { debug("ViewState=$it", TAG) } - .observeOn(AndroidSchedulers.mainThread())!! + source1 = currentWeatherRepository.getSelectedCityAndCurrentWeatherOfSelectedCity(), + source2 = colorHolderSource.colorObservable + ).map { + when (val optional = it.first) { + None -> NoSelectedCity(androidApplication.themeColor(R.attr.colorPrimaryVariant)) + is Some -> CityAndWeather( + city = optional.value.city, + weather = optional.value.currentWeather, + vibrantColor = it.second.first + ) + } + } + .distinctUntilChanged() + .doOnNext { debug("ViewState=$it", TAG) } + .observeOn(AndroidSchedulers.mainThread())!! override fun bindIntents() { disposable = intent(MainContract.View::changeColorIntent) - .observeOn(AndroidSchedulers.mainThread()) - .doOnNext { debug("ChangeColor=$it", TAG) } - .subscribe(colorHolderSource::change) + .observeOn(AndroidSchedulers.mainThread()) + .doOnNext { debug("ChangeColor=$it", TAG) } + .subscribe(colorHolderSource::change) subscribeViewState(state, MainContract.View::render) } diff --git a/app/src/main/java/com/hoc/weatherapp/ui/main/chart/ChartContract.kt b/app/src/main/java/com/hoc/weatherapp/ui/main/chart/ChartContract.kt index fd3be42..ceaa698 100644 --- a/app/src/main/java/com/hoc/weatherapp/ui/main/chart/ChartContract.kt +++ b/app/src/main/java/com/hoc/weatherapp/ui/main/chart/ChartContract.kt @@ -8,13 +8,13 @@ import com.hoc.weatherapp.data.models.entity.DailyWeather interface ChartContract { data class ViewState( - val weathers: List, - val temperatureUnit: TemperatureUnit, - val pressureUnit: PressureUnit, - val speedUnit: SpeedUnit + val weathers: List, + val temperatureUnit: TemperatureUnit, + val pressureUnit: PressureUnit, + val speedUnit: SpeedUnit ) interface View : MvpView { fun render(viewState: ViewState) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/ui/main/chart/ChartFragment.kt b/app/src/main/java/com/hoc/weatherapp/ui/main/chart/ChartFragment.kt index d2b2526..4f45020 100644 --- a/app/src/main/java/com/hoc/weatherapp/ui/main/chart/ChartFragment.kt +++ b/app/src/main/java/com/hoc/weatherapp/ui/main/chart/ChartFragment.kt @@ -2,19 +2,10 @@ package com.hoc.weatherapp.ui.main.chart import android.annotation.SuppressLint import android.graphics.Color -import android.graphics.DashPathEffect -import android.graphics.Paint -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup import androidx.annotation.ColorInt import androidx.core.content.ContextCompat import androidx.core.graphics.ColorUtils -import com.db.williamchart.ExperimentalFeature -import com.db.williamchart.slidertooltip.SliderTooltip import com.db.williamchart.view.LineChartView -import com.hannesdorfmann.mosby3.mvi.MviFragment import com.hoc.weatherapp.R import com.hoc.weatherapp.data.models.entity.DailyWeather import com.hoc.weatherapp.databinding.FragmentChartBinding @@ -23,14 +14,14 @@ import com.hoc.weatherapp.utils.UnitConverter import com.hoc.weatherapp.utils.debug import com.hoc.weatherapp.utils.themeColor import com.hoc081098.viewbindingdelegate.viewBinding -import org.koin.android.ext.android.get import java.text.DecimalFormat -import java.util.* -import kotlin.collections.LinkedHashMap -import kotlin.math.ceil -import kotlin.math.floor +import java.util.Calendar +import java.util.Locale +import org.koin.android.ext.android.get -class ChartFragment : BaseMviFragment(R.layout.fragment_chart), ChartContract.View { +class ChartFragment : + BaseMviFragment(R.layout.fragment_chart), + ChartContract.View { private val binding by viewBinding() private val decimalFormat = DecimalFormat("#.#") @@ -43,53 +34,52 @@ class ChartFragment : BaseMviFragment(R.layo val gridColor = requireContext().themeColor(R.attr.colorSecondary) binding.textTemperature.text = - getString(R.string.temperature_chart_title, temperatureUnit.symbol()) + getString(R.string.temperature_chart_title, temperatureUnit.symbol()) drawChart( binding.chartTemperature, - weathers, - { UnitConverter.convertTemperature(it.temperature, temperatureUnit).toFloat() }, - ContextCompat.getColor( - requireContext(), - R.color.colorPrimary - ), - gridColor + weathers, + { UnitConverter.convertTemperature(it.temperature, temperatureUnit).toFloat() }, + ContextCompat.getColor( + requireContext(), + R.color.colorPrimary + ), + gridColor ) binding.textRain.text = getString(R.string.rain_chart_title) drawChart( - binding.chartRain, - weathers, - { it.rainVolumeForTheLast3Hours.toFloat() }, - ContextCompat.getColor( - requireContext(), - R.color.colorMaterialBlue500 - ), - gridColor + binding.chartRain, + weathers, + { it.rainVolumeForTheLast3Hours.toFloat() }, + ContextCompat.getColor( + requireContext(), + R.color.colorMaterialBlue500 + ), + gridColor ) binding.textPressure.text = getString(R.string.pressure_chart_title, pressureUnit.symbol()) drawChart( - binding.chartPressure, - weathers, - { UnitConverter.convertPressure(it.pressure, pressureUnit).toFloat() }, - ContextCompat.getColor( - requireContext(), - R.color.colorMaterialCyan400 - ), - gridColor + binding.chartPressure, + weathers, + { UnitConverter.convertPressure(it.pressure, pressureUnit).toFloat() }, + ContextCompat.getColor( + requireContext(), + R.color.colorMaterialCyan400 + ), + gridColor ) - binding.textWindSpeed.text = getString(R.string.wind_speed_chart_title, speedUnit.symbol()) drawChart( - binding.chartWindSpeed, - weathers, - { UnitConverter.convertSpeed(it.windSpeed, speedUnit).toFloat() }, - ContextCompat.getColor( - requireContext(), - R.color.colorDeepPurpleAccent700 - ), - gridColor + binding.chartWindSpeed, + weathers, + { UnitConverter.convertSpeed(it.windSpeed, speedUnit).toFloat() }, + ContextCompat.getColor( + requireContext(), + R.color.colorDeepPurpleAccent700 + ), + gridColor ) } @@ -97,11 +87,11 @@ class ChartFragment : BaseMviFragment(R.layo @SuppressLint("Range") private inline fun drawChart( - lineChartView: LineChartView, - dailyWeathers: List, - crossinline transform: (DailyWeather) -> Float, - @ColorInt lineColor: Int, - @ColorInt gridColor: Int + lineChartView: LineChartView, + dailyWeathers: List, + crossinline transform: (DailyWeather) -> Float, + @ColorInt lineColor: Int, + @ColorInt gridColor: Int ) { debug("::drawChart") @@ -113,16 +103,16 @@ class ChartFragment : BaseMviFragment(R.layo lineThickness = 4f this.lineColor = lineColor gradientFillColors = intArrayOf( - ColorUtils.setAlphaComponent(lineColor, 0x81), - Color.TRANSPARENT, + ColorUtils.setAlphaComponent(lineColor, 0x81), + Color.TRANSPARENT, ) animation.duration = animationDuration animate( - LinkedHashMap( - dailyWeathers.getLabels() - .zip(map) - .toMap() - ) + LinkedHashMap( + dailyWeathers.getLabels() + .zip(map) + .toMap() + ) ) // Grid @@ -161,9 +151,9 @@ class ChartFragment : BaseMviFragment(R.layo date != previousDate -> { previousDate = date instance.getDisplayName( - Calendar.DAY_OF_WEEK, - Calendar.SHORT, - Locale.getDefault() + Calendar.DAY_OF_WEEK, + Calendar.SHORT, + Locale.getDefault() ) } else -> " " @@ -174,4 +164,4 @@ class ChartFragment : BaseMviFragment(R.layo private companion object { const val animationDuration = 1000L } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/ui/main/chart/ChartPresenter.kt b/app/src/main/java/com/hoc/weatherapp/ui/main/chart/ChartPresenter.kt index 1312081..f5dc3e0 100644 --- a/app/src/main/java/com/hoc/weatherapp/ui/main/chart/ChartPresenter.kt +++ b/app/src/main/java/com/hoc/weatherapp/ui/main/chart/ChartPresenter.kt @@ -15,33 +15,34 @@ import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.rxkotlin.Observables.combineLatest class ChartPresenter( - fiveDayForecastRepository: FiveDayForecastRepository, - settingPreferences: SettingPreferences + fiveDayForecastRepository: FiveDayForecastRepository, + settingPreferences: SettingPreferences ) : MviBasePresenter() { private val viewState = combineLatest( - source1 = fiveDayForecastRepository.getFiveDayForecastOfSelectedCity(), - source2 = settingPreferences.temperatureUnitPreference.observable, - source3 = settingPreferences.speedUnitPreference.observable, - source4 = settingPreferences.pressureUnitPreference.observable, - combineFunction = { optional, temperatureUnit, speedUnit, pressureUnit -> - Tuple4( - temperatureUnit = temperatureUnit, - weathers = optional.getOrNull()?.second.orEmpty(), - pressureUnit = pressureUnit, - speedUnit = speedUnit - ) - }) - .map { - ViewState( - temperatureUnit = it.temperatureUnit, - weathers = it.weathers, - speedUnit = it.speedUnit, - pressureUnit = it.pressureUnit - ) - } - .distinctUntilChanged() - .doOnNext { debug("ViewState=$it", TAG) } - .observeOn(AndroidSchedulers.mainThread())!! + source1 = fiveDayForecastRepository.getFiveDayForecastOfSelectedCity(), + source2 = settingPreferences.temperatureUnitPreference.observable, + source3 = settingPreferences.speedUnitPreference.observable, + source4 = settingPreferences.pressureUnitPreference.observable, + combineFunction = { optional, temperatureUnit, speedUnit, pressureUnit -> + Tuple4( + temperatureUnit = temperatureUnit, + weathers = optional.getOrNull()?.second.orEmpty(), + pressureUnit = pressureUnit, + speedUnit = speedUnit + ) + } + ) + .map { + ViewState( + temperatureUnit = it.temperatureUnit, + weathers = it.weathers, + speedUnit = it.speedUnit, + pressureUnit = it.pressureUnit + ) + } + .distinctUntilChanged() + .doOnNext { debug("ViewState=$it", TAG) } + .observeOn(AndroidSchedulers.mainThread())!! override fun bindIntents() = subscribeViewState(viewState, View::render) @@ -49,10 +50,10 @@ class ChartPresenter( private const val TAG = "__chart__" private data class Tuple4( - val weathers: List, - val temperatureUnit: TemperatureUnit, - val speedUnit: SpeedUnit, - val pressureUnit: PressureUnit + val weathers: List, + val temperatureUnit: TemperatureUnit, + val speedUnit: SpeedUnit, + val pressureUnit: PressureUnit ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/ui/main/currentweather/CurrentWeather.kt b/app/src/main/java/com/hoc/weatherapp/ui/main/currentweather/CurrentWeather.kt index 30c463e..7ff20a5 100644 --- a/app/src/main/java/com/hoc/weatherapp/ui/main/currentweather/CurrentWeather.kt +++ b/app/src/main/java/com/hoc/weatherapp/ui/main/currentweather/CurrentWeather.kt @@ -1,20 +1,20 @@ package com.hoc.weatherapp.ui.main.currentweather data class CurrentWeather( - val temperatureString: String, - val pressureString: String, - val humidity: Long, - val rainVolumeForThe3HoursMm: Double, - val weatherConditionId: Long, - val weatherIcon: String, - val description: String, - val dataTimeString: String, - val zoneId: String, - /** - * m/s - */ - val winSpeed: Double, - val winSpeedString: String, - val winDirection: String, - val visibilityKm: Double -) \ No newline at end of file + val temperatureString: String, + val pressureString: String, + val humidity: Long, + val rainVolumeForThe3HoursMm: Double, + val weatherConditionId: Long, + val weatherIcon: String, + val description: String, + val dataTimeString: String, + val zoneId: String, + /** + * m/s + */ + val winSpeed: Double, + val winSpeedString: String, + val winDirection: String, + val visibilityKm: Double +) diff --git a/app/src/main/java/com/hoc/weatherapp/ui/main/currentweather/CurrentWeatherContract.kt b/app/src/main/java/com/hoc/weatherapp/ui/main/currentweather/CurrentWeatherContract.kt index 36d9235..9e652d0 100644 --- a/app/src/main/java/com/hoc/weatherapp/ui/main/currentweather/CurrentWeatherContract.kt +++ b/app/src/main/java/com/hoc/weatherapp/ui/main/currentweather/CurrentWeatherContract.kt @@ -6,8 +6,8 @@ import io.reactivex.Observable interface CurrentWeatherContract { sealed class PartialStateChange { data class Error( - val throwable: Throwable, - val showMessage: Boolean + val throwable: Throwable, + val showMessage: Boolean ) : PartialStateChange() data class Weather(val weather: CurrentWeather) : PartialStateChange() @@ -16,10 +16,10 @@ interface CurrentWeatherContract { } data class ViewState( - val weather: CurrentWeather? = null, - val error: Throwable? = null, - val showError: Boolean = false, - val showRefreshSuccessfully: Boolean = false + val weather: CurrentWeather? = null, + val error: Throwable? = null, + val showError: Boolean = false, + val showRefreshSuccessfully: Boolean = false ) sealed class RefreshIntent { @@ -27,10 +27,9 @@ interface CurrentWeatherContract { object UserRefreshIntent : RefreshIntent() } - interface View : MvpView { fun refreshCurrentWeatherIntent(): Observable fun render(state: ViewState) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/ui/main/currentweather/CurrentWeatherFragment.kt b/app/src/main/java/com/hoc/weatherapp/ui/main/currentweather/CurrentWeatherFragment.kt index 1d39fcd..335fd35 100644 --- a/app/src/main/java/com/hoc/weatherapp/ui/main/currentweather/CurrentWeatherFragment.kt +++ b/app/src/main/java/com/hoc/weatherapp/ui/main/currentweather/CurrentWeatherFragment.kt @@ -2,9 +2,7 @@ package com.hoc.weatherapp.ui.main.currentweather import android.annotation.SuppressLint import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import com.bumptech.glide.Glide import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions import com.bumptech.glide.request.RequestOptions @@ -25,15 +23,16 @@ import com.jakewharton.rxbinding3.swiperefreshlayout.refreshes import io.reactivex.Observable import io.reactivex.rxkotlin.cast import io.reactivex.subjects.PublishSubject -import org.koin.android.ext.android.get import java.text.DecimalFormat +import org.koin.android.ext.android.get private const val TAG = "currentweather" @ExperimentalStdlibApi -class CurrentWeatherFragment : BaseMviFragment(R.layout.fragment_current_weather), - CurrentWeatherContract.View { + CurrentWeatherContract.View { private val binding by viewBinding() private var errorSnackBar: Snackbar? = null @@ -42,10 +41,10 @@ class CurrentWeatherFragment : BaseMviFragment { return binding.swipeRefreshLayout.refreshes() - .map { RefreshIntent.UserRefreshIntent } - .cast() - .mergeWith(refreshInitial.doOnNext { debug("refreshes initial", TAG) }) - .doOnNext { debug("refreshes", TAG) } + .map { RefreshIntent.UserRefreshIntent } + .cast() + .mergeWith(refreshInitial.doOnNext { debug("refreshes initial", TAG) }) + .doOnNext { debug("refreshes", TAG) } } override fun render(state: ViewState) { @@ -63,8 +62,8 @@ class CurrentWeatherFragment : BaseMviFragment() { private val cityAndWeatherPartialChange = Observables.combineLatest( - source1 = settingPreferences.speedUnitPreference.observable, - source2 = settingPreferences.pressureUnitPreference.observable, - source3 = settingPreferences.temperatureUnitPreference.observable, - source4 = currentWeatherRepository.getSelectedCityAndCurrentWeatherOfSelectedCity(), - combineFunction = { speedUnit, pressureUnit, temperatureUnit, optional -> - Tuple4( - speedUnit, - pressureUnit, - temperatureUnit, - optional - ) - } + source1 = settingPreferences.speedUnitPreference.observable, + source2 = settingPreferences.pressureUnitPreference.observable, + source3 = settingPreferences.temperatureUnitPreference.observable, + source4 = currentWeatherRepository.getSelectedCityAndCurrentWeatherOfSelectedCity(), + combineFunction = { speedUnit, pressureUnit, temperatureUnit, optional -> + Tuple4( + speedUnit, + pressureUnit, + temperatureUnit, + optional + ) + } ).switchMap { (speedUnit, pressureUnit, temperatureUnit, optional) -> when (optional) { None -> showError(NoSelectedCityException) is Some -> Observable.just( - toCurrentWeather( - optional.value, - speedUnit, - pressureUnit, - temperatureUnit - ) + toCurrentWeather( + optional.value, + speedUnit, + pressureUnit, + temperatureUnit + ) ).map { PartialStateChange.Weather(it) } }.onErrorResumeNext(::showError) } private val refreshWeatherProcessor = - ObservableTransformer { intentObservable -> - intentObservable - .publish { shared -> - Observable.mergeArray( - shared.ofType() - .take(1) - .delay { cityRepository.getSelectedCity().filter { it is Some } }, - shared.notOfType() - ) + ObservableTransformer { intentObservable -> + intentObservable + .publish { shared -> + Observable.mergeArray( + shared.ofType() + .take(1) + .delay { cityRepository.getSelectedCity().filter { it is Some } }, + shared.notOfType() + ) + } + .exhaustMap { + currentWeatherRepository + .refreshCurrentWeatherOfSelectedCity() + .doOnSuccess { + if (settingPreferences.autoUpdatePreference.value) { + WorkerUtil.enqueueUpdateCurrentWeatherWorkRequest() + } + androidApplication.showNotificationIfEnabled(it, settingPreferences) } - .exhaustMap { - currentWeatherRepository - .refreshCurrentWeatherOfSelectedCity() - .doOnSuccess { - if (settingPreferences.autoUpdatePreference.value) { - WorkerUtil.enqueueUpdateCurrentWeatherWorkRequest() - } - androidApplication.showNotificationIfEnabled(it, settingPreferences) - } - .doOnError { - if (it is NoSelectedCityException) { - androidApplication.cancelNotificationById(WEATHER_NOTIFICATION_ID) - WorkerUtil.cancelUpdateCurrentWeatherWorkRequest() - WorkerUtil.cancelUpdateDailyWeatherWorkRequest() - } - } - .toObservable() - .observeOn(AndroidSchedulers.mainThread()) - .switchMap { - Observable - .timer(2_000, TimeUnit.MILLISECONDS) - .map { PartialStateChange.RefreshWeatherSuccess(showMessage = false) } - .startWith(PartialStateChange.RefreshWeatherSuccess(showMessage = true)) - } - .onErrorResumeNext(::showError) + .doOnError { + if (it is NoSelectedCityException) { + androidApplication.cancelNotificationById(WEATHER_NOTIFICATION_ID) + WorkerUtil.cancelUpdateCurrentWeatherWorkRequest() + WorkerUtil.cancelUpdateDailyWeatherWorkRequest() + } } - } + .toObservable() + .observeOn(AndroidSchedulers.mainThread()) + .switchMap { + Observable + .timer(2_000, TimeUnit.MILLISECONDS) + .map { PartialStateChange.RefreshWeatherSuccess(showMessage = false) } + .startWith(PartialStateChange.RefreshWeatherSuccess(showMessage = true)) + } + .onErrorResumeNext(::showError) + } + } override fun bindIntents() { subscribeViewState( - Observable.mergeArray( - intent(View::refreshCurrentWeatherIntent).compose(refreshWeatherProcessor), - cityAndWeatherPartialChange - ).scan(ViewState(), reducer) - .distinctUntilChanged() - .doOnNext { debug("ViewState=$it", TAG) } - .observeOn(AndroidSchedulers.mainThread()), - View::render + Observable.mergeArray( + intent(View::refreshCurrentWeatherIntent).compose(refreshWeatherProcessor), + cityAndWeatherPartialChange + ).scan(ViewState(), reducer) + .distinctUntilChanged() + .doOnNext { debug("ViewState=$it", TAG) } + .observeOn(AndroidSchedulers.mainThread()), + View::render ) } @@ -118,74 +129,78 @@ class CurrentWeatherPresenter( private val LAST_UPDATED_FORMATTER = DateTimeFormatter.ofPattern("dd/MM/yy HH:mm") private data class Tuple4( - val speedUnit: SpeedUnit, - val pressureUnit: PressureUnit, - val temperatureUnit: TemperatureUnit, - val optional: Optional + val speedUnit: SpeedUnit, + val pressureUnit: PressureUnit, + val temperatureUnit: TemperatureUnit, + val optional: Optional ) @JvmStatic private val reducer = - BiFunction { viewState, partialStateChange -> - when (partialStateChange) { - is PartialStateChange.Error -> viewState.copy( - showError = partialStateChange.showMessage, - error = partialStateChange.throwable, - weather = if (partialStateChange.throwable is NoSelectedCityException) { - null - } else { - viewState.weather - } - ) - is PartialStateChange.Weather -> viewState.copy( - weather = partialStateChange.weather, - error = null - ) - is PartialStateChange.RefreshWeatherSuccess -> viewState.copy( - showRefreshSuccessfully = partialStateChange.showMessage, - error = null - ) - } + BiFunction { viewState, partialStateChange -> + when (partialStateChange) { + is PartialStateChange.Error -> viewState.copy( + showError = partialStateChange.showMessage, + error = partialStateChange.throwable, + weather = if (partialStateChange.throwable is NoSelectedCityException) { + null + } else { + viewState.weather + } + ) + is PartialStateChange.Weather -> viewState.copy( + weather = partialStateChange.weather, + error = null + ) + is PartialStateChange.RefreshWeatherSuccess -> viewState.copy( + showRefreshSuccessfully = partialStateChange.showMessage, + error = null + ) } + } @JvmStatic private fun toCurrentWeather( - cityAndCurrentWeather: CityAndCurrentWeather, - speedUnit: SpeedUnit, - pressureUnit: PressureUnit, - temperatureUnit: TemperatureUnit + cityAndCurrentWeather: CityAndCurrentWeather, + speedUnit: SpeedUnit, + pressureUnit: PressureUnit, + temperatureUnit: TemperatureUnit ): CurrentWeather { val weather = cityAndCurrentWeather.currentWeather val dataTimeString = weather - .dataTime - .toZonedDateTime(cityAndCurrentWeather.city.zoneId) - .format(LAST_UPDATED_FORMATTER) + .dataTime + .toZonedDateTime(cityAndCurrentWeather.city.zoneId) + .format(LAST_UPDATED_FORMATTER) return CurrentWeather( - temperatureString = temperatureUnit.format(weather.temperature), - pressureString = pressureUnit.format(weather.pressure), - rainVolumeForThe3HoursMm = weather.rainVolumeForThe3Hours, - visibilityKm = weather.visibility / 1_000, - humidity = weather.humidity, - description = weather.description.capitalize(Locale.ROOT), - dataTimeString = dataTimeString, - weatherConditionId = weather.weatherConditionId, - weatherIcon = weather.icon, - winSpeed = weather.winSpeed, - winSpeedString = speedUnit.format(weather.winSpeed), - winDirection = WindDirection.fromDegrees(weather.winDegrees).toString(), - zoneId = cityAndCurrentWeather.city.zoneId + temperatureString = temperatureUnit.format(weather.temperature), + pressureString = pressureUnit.format(weather.pressure), + rainVolumeForThe3HoursMm = weather.rainVolumeForThe3Hours, + visibilityKm = weather.visibility / 1_000, + humidity = weather.humidity, + description = weather.description.replaceFirstChar { + if (it.isLowerCase()) it.titlecase( + Locale.ROOT + ) else it.toString() + }, + dataTimeString = dataTimeString, + weatherConditionId = weather.weatherConditionId, + weatherIcon = weather.icon, + winSpeed = weather.winSpeed, + winSpeedString = speedUnit.format(weather.winSpeed), + winDirection = WindDirection.fromDegrees(weather.winDegrees).toString(), + zoneId = cityAndCurrentWeather.city.zoneId ) } @JvmStatic private fun showError(throwable: Throwable): Observable { return Observable.timer(2_000, TimeUnit.MILLISECONDS) - .map { - PartialStateChange.Error(throwable = throwable, showMessage = false) - } - .startWith( - PartialStateChange.Error(throwable = throwable, showMessage = true) - ) + .map { + PartialStateChange.Error(throwable = throwable, showMessage = false) + } + .startWith( + PartialStateChange.Error(throwable = throwable, showMessage = true) + ) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/ui/main/fivedayforecast/DailyDetailActivity.kt b/app/src/main/java/com/hoc/weatherapp/ui/main/fivedayforecast/DailyDetailActivity.kt index 70770f3..bed5d83 100644 --- a/app/src/main/java/com/hoc/weatherapp/ui/main/fivedayforecast/DailyDetailActivity.kt +++ b/app/src/main/java/com/hoc/weatherapp/ui/main/fivedayforecast/DailyDetailActivity.kt @@ -113,4 +113,4 @@ class DailyDetailActivity : BaseAppCompatActivity(R.layout.activity_detail_daily textView.text = pair.second } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/ui/main/fivedayforecast/DailyWeatherAdapter.kt b/app/src/main/java/com/hoc/weatherapp/ui/main/fivedayforecast/DailyWeatherAdapter.kt index 5437fc7..af31abc 100644 --- a/app/src/main/java/com/hoc/weatherapp/ui/main/fivedayforecast/DailyWeatherAdapter.kt +++ b/app/src/main/java/com/hoc/weatherapp/ui/main/fivedayforecast/DailyWeatherAdapter.kt @@ -26,23 +26,25 @@ import io.reactivex.subjects.PublishSubject import org.threeten.bp.ZonedDateTime import org.threeten.bp.format.DateTimeFormatter -class DailyWeatherAdapter : ListAdapter( - object : DiffUtil.ItemCallback() { - override fun areItemsTheSame( - oldItem: DailyWeatherListItem, - newItem: DailyWeatherListItem - ) = when { - oldItem is DailyWeatherListItem.Weather && newItem is DailyWeatherListItem.Weather -> oldItem.dataTime == newItem.dataTime - oldItem is DailyWeatherListItem.Header && newItem is DailyWeatherListItem.Header -> oldItem.date == newItem.date - else -> oldItem == newItem - } +class DailyWeatherAdapter : + ListAdapter( + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: DailyWeatherListItem, + newItem: DailyWeatherListItem + ) = when { + oldItem is DailyWeatherListItem.Weather && newItem is DailyWeatherListItem.Weather -> oldItem.dataTime == newItem.dataTime + oldItem is DailyWeatherListItem.Header && newItem is DailyWeatherListItem.Header -> oldItem.date == newItem.date + else -> oldItem == newItem + } - override fun areContentsTheSame( - oldItem: DailyWeatherListItem, - newItem: DailyWeatherListItem - ) = newItem == oldItem - } -), HeaderItemDecoration.StickyHeaderInterface { + override fun areContentsTheSame( + oldItem: DailyWeatherListItem, + newItem: DailyWeatherListItem + ) = newItem == oldItem + } + ), + HeaderItemDecoration.StickyHeaderInterface { override fun viewBinding(parent: RecyclerView): DailyWeatherHeaderLayoutBinding = parent inflateViewBinding false @@ -56,8 +58,10 @@ class DailyWeatherAdapter : ListAdapter) : PartialStateChange() @@ -16,10 +16,10 @@ interface DailyWeatherContract { } data class ViewState( - val dailyWeatherListItem: List? = null, - val error: Throwable? = null, - val showError: Boolean = false, - val showRefreshSuccessfully: Boolean = false + val dailyWeatherListItem: List? = null, + val error: Throwable? = null, + val showError: Boolean = false, + val showRefreshSuccessfully: Boolean = false ) sealed class RefreshIntent { @@ -32,4 +32,4 @@ interface DailyWeatherContract { fun render(viewState: ViewState) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/ui/main/fivedayforecast/DailyWeatherListItem.kt b/app/src/main/java/com/hoc/weatherapp/ui/main/fivedayforecast/DailyWeatherListItem.kt index 2d05fa1..5c90207 100644 --- a/app/src/main/java/com/hoc/weatherapp/ui/main/fivedayforecast/DailyWeatherListItem.kt +++ b/app/src/main/java/com/hoc/weatherapp/ui/main/fivedayforecast/DailyWeatherListItem.kt @@ -8,23 +8,23 @@ import org.threeten.bp.ZonedDateTime sealed class DailyWeatherListItem { @Parcelize data class Weather( - val colors: Pair, - val weatherIcon: String, - val dataTime: ZonedDateTime, - val weatherDescription: String, - val temperatureMin: String, - val temperatureMax: String, - val temperature: String, - val pressure: String, - val seaLevel: String, - val groundLevel: String, - val humidity: String, - val main: String, - val cloudiness: String, - val winSpeed: String, - val windDirection: WindDirection, - val rainVolumeForTheLast3Hours: String, - val snowVolumeForTheLast3Hours: String + val colors: Pair, + val weatherIcon: String, + val dataTime: ZonedDateTime, + val weatherDescription: String, + val temperatureMin: String, + val temperatureMax: String, + val temperature: String, + val pressure: String, + val seaLevel: String, + val groundLevel: String, + val humidity: String, + val main: String, + val cloudiness: String, + val winSpeed: String, + val windDirection: WindDirection, + val rainVolumeForTheLast3Hours: String, + val snowVolumeForTheLast3Hours: String ) : DailyWeatherListItem(), Parcelable data class Header(val date: ZonedDateTime) : DailyWeatherListItem() diff --git a/app/src/main/java/com/hoc/weatherapp/ui/main/fivedayforecast/DailyWeatherPresenter.kt b/app/src/main/java/com/hoc/weatherapp/ui/main/fivedayforecast/DailyWeatherPresenter.kt index b2ddd76..3d2532c 100644 --- a/app/src/main/java/com/hoc/weatherapp/ui/main/fivedayforecast/DailyWeatherPresenter.kt +++ b/app/src/main/java/com/hoc/weatherapp/ui/main/fivedayforecast/DailyWeatherPresenter.kt @@ -13,8 +13,18 @@ import com.hoc.weatherapp.data.models.WindDirection import com.hoc.weatherapp.data.models.entity.City import com.hoc.weatherapp.data.models.entity.DailyWeather import com.hoc.weatherapp.ui.main.ColorHolderSource -import com.hoc.weatherapp.ui.main.fivedayforecast.DailyWeatherContract.* -import com.hoc.weatherapp.utils.* +import com.hoc.weatherapp.ui.main.fivedayforecast.DailyWeatherContract.PartialStateChange +import com.hoc.weatherapp.ui.main.fivedayforecast.DailyWeatherContract.RefreshIntent +import com.hoc.weatherapp.ui.main.fivedayforecast.DailyWeatherContract.View +import com.hoc.weatherapp.ui.main.fivedayforecast.DailyWeatherContract.ViewState +import com.hoc.weatherapp.utils.Some +import com.hoc.weatherapp.utils.WEATHER_NOTIFICATION_ID +import com.hoc.weatherapp.utils.cancelNotificationById +import com.hoc.weatherapp.utils.debug +import com.hoc.weatherapp.utils.exhaustMap +import com.hoc.weatherapp.utils.notOfType +import com.hoc.weatherapp.utils.toZonedDateTime +import com.hoc.weatherapp.utils.trim import com.hoc.weatherapp.worker.WorkerUtil import io.reactivex.Observable import io.reactivex.ObservableTransformer @@ -23,79 +33,79 @@ import io.reactivex.functions.BiFunction import io.reactivex.functions.Function import io.reactivex.rxkotlin.Observables import io.reactivex.rxkotlin.ofType -import java.util.* +import java.util.Locale import java.util.concurrent.TimeUnit @ExperimentalStdlibApi class DailyWeatherPresenter( - private val fiveDayForecastRepository: FiveDayForecastRepository, - private val cityRepository: CityRepository, - private val settingPreferences: SettingPreferences, - colorHolderSource: ColorHolderSource, - private val androidApplication: Application + private val fiveDayForecastRepository: FiveDayForecastRepository, + private val cityRepository: CityRepository, + private val settingPreferences: SettingPreferences, + colorHolderSource: ColorHolderSource, + private val androidApplication: Application ) : MviBasePresenter() { private val refreshWeatherProcessor = - ObservableTransformer { intentObservable -> - intentObservable - .publish { shared -> - Observable.mergeArray( - shared.ofType() - .take(1) - .delay { cityRepository.getSelectedCity().filter { it is Some } }, - shared.notOfType() - ) + ObservableTransformer { intentObservable -> + intentObservable + .publish { shared -> + Observable.mergeArray( + shared.ofType() + .take(1) + .delay { cityRepository.getSelectedCity().filter { it is Some } }, + shared.notOfType() + ) + } + .exhaustMap { + fiveDayForecastRepository + .refreshFiveDayForecastOfSelectedCity() + .doOnSuccess { + if (settingPreferences.autoUpdatePreference.value) { + WorkerUtil.enqueueUpdateDailyWeatherWorkRequest() + } } - .exhaustMap { - fiveDayForecastRepository - .refreshFiveDayForecastOfSelectedCity() - .doOnSuccess { - if (settingPreferences.autoUpdatePreference.value) { - WorkerUtil.enqueueUpdateDailyWeatherWorkRequest() - } - } - .doOnError { - if (it is NoSelectedCityException) { - androidApplication.cancelNotificationById(WEATHER_NOTIFICATION_ID) - WorkerUtil.cancelUpdateCurrentWeatherWorkRequest() - WorkerUtil.cancelUpdateDailyWeatherWorkRequest() - } - } - .toObservable() - .observeOn(AndroidSchedulers.mainThread()) - .switchMap { - Observable - .timer(2_000, TimeUnit.MILLISECONDS) - .map { PartialStateChange.RefreshWeatherSuccess(showMessage = false) } - .startWith(PartialStateChange.RefreshWeatherSuccess(showMessage = true)) - } - .onErrorResumeNext(showError) + .doOnError { + if (it is NoSelectedCityException) { + androidApplication.cancelNotificationById(WEATHER_NOTIFICATION_ID) + WorkerUtil.cancelUpdateCurrentWeatherWorkRequest() + WorkerUtil.cancelUpdateDailyWeatherWorkRequest() + } } - } + .toObservable() + .observeOn(AndroidSchedulers.mainThread()) + .switchMap { + Observable + .timer(2_000, TimeUnit.MILLISECONDS) + .map { PartialStateChange.RefreshWeatherSuccess(showMessage = false) } + .startWith(PartialStateChange.RefreshWeatherSuccess(showMessage = true)) + } + .onErrorResumeNext(showError) + } + } private val weatherChangePartialState = Observables.combineLatest( - source1 = fiveDayForecastRepository.getFiveDayForecastOfSelectedCity() - .ofType>>>() - .map { it.value }, - source2 = settingPreferences.temperatureUnitPreference.observable, - source3 = settingPreferences.speedUnitPreference.observable, - source4 = settingPreferences.pressureUnitPreference.observable, - source5 = colorHolderSource.colorObservable, - combineFunction = { list, temperatureUnit, speedUnit, pressureUnit, color -> - Tuple5(list, temperatureUnit, speedUnit, pressureUnit, color) - } + source1 = fiveDayForecastRepository.getFiveDayForecastOfSelectedCity() + .ofType>>>() + .map { it.value }, + source2 = settingPreferences.temperatureUnitPreference.observable, + source3 = settingPreferences.speedUnitPreference.observable, + source4 = settingPreferences.pressureUnitPreference.observable, + source5 = colorHolderSource.colorObservable, + combineFunction = { list, temperatureUnit, speedUnit, pressureUnit, color -> + Tuple5(list, temperatureUnit, speedUnit, pressureUnit, color) + } ).map(tupleToWeatherPartialChange).onErrorResumeNext(showError) override fun bindIntents() { subscribeViewState( - Observable.mergeArray( - weatherChangePartialState, - intent(View::refreshDailyWeatherIntent).compose(refreshWeatherProcessor) - ).scan(ViewState(), reducer) - .distinctUntilChanged() - .doOnNext { debug("ViewState=$it", TAG) } - .observeOn(AndroidSchedulers.mainThread()), - View::render + Observable.mergeArray( + weatherChangePartialState, + intent(View::refreshDailyWeatherIntent).compose(refreshWeatherProcessor) + ).scan(ViewState(), reducer) + .distinctUntilChanged() + .doOnNext { debug("ViewState=$it", TAG) } + .observeOn(AndroidSchedulers.mainThread()), + View::render ) } @@ -103,11 +113,11 @@ class DailyWeatherPresenter( private const val TAG = "__five_day_forecast__" private data class Tuple5( - val weathers: Pair>, - val temperatureUnit: TemperatureUnit, - val speedUnit: SpeedUnit, - val pressureUnit: PressureUnit, - val colors: Pair + val weathers: Pair>, + val temperatureUnit: TemperatureUnit, + val speedUnit: SpeedUnit, + val pressureUnit: PressureUnit, + val colors: Pair ) @ExperimentalStdlibApi @@ -115,72 +125,76 @@ class DailyWeatherPresenter( private val tupleToWeatherPartialChange = Function { tuple5 -> val (cityAndWeathers, temperatureUnit, windSpeedUnit, pressureUnit, colors) = tuple5 cityAndWeathers - .second - .groupBy { it.timeOfDataForecasted.trim() } - .toSortedMap() - .flatMap { (date, weathers) -> - val zoneId = cityAndWeathers.first.zoneId + .second + .groupBy { it.timeOfDataForecasted.trim() } + .toSortedMap() + .flatMap { (date, weathers) -> + val zoneId = cityAndWeathers.first.zoneId - listOf(DailyWeatherListItem.Header(date.toZonedDateTime(zoneId))) + - weathers.map { - DailyWeatherListItem.Weather( - weatherIcon = it.icon, - main = it.main, - weatherDescription = it.description.capitalize(Locale.ROOT), - temperatureMin = temperatureUnit.format(it.temperatureMin), - temperatureMax = temperatureUnit.format(it.temperatureMax), - temperature = temperatureUnit.format(it.temperature), - dataTime = it.timeOfDataForecasted.toZonedDateTime(zoneId), - cloudiness = "${it.cloudiness}%", - humidity = "${it.humidity}%", - rainVolumeForTheLast3Hours = "${it.rainVolumeForTheLast3Hours}mm", - snowVolumeForTheLast3Hours = "${it.snowVolumeForTheLast3Hours}mm", - windDirection = WindDirection.fromDegrees(it.winDegrees), - groundLevel = pressureUnit.format(it.groundLevel), - pressure = pressureUnit.format(it.pressure), - seaLevel = pressureUnit.format(it.seaLevel), - winSpeed = windSpeedUnit.format(it.windSpeed), - colors = colors - ) - } - } - .let(PartialStateChange::Weather) + listOf(DailyWeatherListItem.Header(date.toZonedDateTime(zoneId))) + + weathers.map { + DailyWeatherListItem.Weather( + weatherIcon = it.icon, + main = it.main, + weatherDescription = it.description.replaceFirstChar { + if (it.isLowerCase()) it.titlecase( + Locale.ROOT + ) else it.toString() + }, + temperatureMin = temperatureUnit.format(it.temperatureMin), + temperatureMax = temperatureUnit.format(it.temperatureMax), + temperature = temperatureUnit.format(it.temperature), + dataTime = it.timeOfDataForecasted.toZonedDateTime(zoneId), + cloudiness = "${it.cloudiness}%", + humidity = "${it.humidity}%", + rainVolumeForTheLast3Hours = "${it.rainVolumeForTheLast3Hours}mm", + snowVolumeForTheLast3Hours = "${it.snowVolumeForTheLast3Hours}mm", + windDirection = WindDirection.fromDegrees(it.winDegrees), + groundLevel = pressureUnit.format(it.groundLevel), + pressure = pressureUnit.format(it.pressure), + seaLevel = pressureUnit.format(it.seaLevel), + winSpeed = windSpeedUnit.format(it.windSpeed), + colors = colors + ) + } + } + .let(PartialStateChange::Weather) } @JvmStatic private val reducer = - BiFunction { viewState, partialStateChange -> - when (partialStateChange) { - is PartialStateChange.Error -> viewState.copy( - showError = partialStateChange.showMessage, - error = partialStateChange.throwable - ) - is PartialStateChange.Weather -> viewState.copy( - dailyWeatherListItem = partialStateChange.dailyWeatherListItem, - error = null - ) - is PartialStateChange.RefreshWeatherSuccess -> viewState.copy( - showRefreshSuccessfully = partialStateChange.showMessage, - error = null - ) - } + BiFunction { viewState, partialStateChange -> + when (partialStateChange) { + is PartialStateChange.Error -> viewState.copy( + showError = partialStateChange.showMessage, + error = partialStateChange.throwable + ) + is PartialStateChange.Weather -> viewState.copy( + dailyWeatherListItem = partialStateChange.dailyWeatherListItem, + error = null + ) + is PartialStateChange.RefreshWeatherSuccess -> viewState.copy( + showRefreshSuccessfully = partialStateChange.showMessage, + error = null + ) } + } @JvmStatic private val showError = Function> { throwable -> Observable.timer(2_000, TimeUnit.MILLISECONDS) - .map { - PartialStateChange.Error( - showMessage = false, - throwable = throwable - ) - } - .startWith( - PartialStateChange.Error( - showMessage = true, - throwable = throwable - ) + .map { + PartialStateChange.Error( + showMessage = false, + throwable = throwable + ) + } + .startWith( + PartialStateChange.Error( + showMessage = true, + throwable = throwable ) + ) } } } diff --git a/app/src/main/java/com/hoc/weatherapp/ui/map/MapActivity.kt b/app/src/main/java/com/hoc/weatherapp/ui/map/MapActivity.kt index dddc344..4baf1ef 100644 --- a/app/src/main/java/com/hoc/weatherapp/ui/map/MapActivity.kt +++ b/app/src/main/java/com/hoc/weatherapp/ui/map/MapActivity.kt @@ -33,15 +33,15 @@ class MapActivity : BaseAppCompatActivity( binding.bottomNav.setOnNavigationItemSelectedListener { binding.webView.loadUrl( - when (it.itemId) { - R.id.rain_map -> - "javascript:map.removeLayer(windLayer);map.removeLayer(tempLayer);map.addLayer(rainLayer);" - R.id.wind_map -> - "javascript:map.removeLayer(rainLayer);map.removeLayer(tempLayer);map.addLayer(windLayer);" - R.id.temperature_map -> - "javascript:map.removeLayer(windLayer);map.removeLayer(rainLayer);map.addLayer(tempLayer);" - else -> throw IllegalStateException() - } + when (it.itemId) { + R.id.rain_map -> + "javascript:map.removeLayer(windLayer);map.removeLayer(tempLayer);map.addLayer(rainLayer);" + R.id.wind_map -> + "javascript:map.removeLayer(rainLayer);map.removeLayer(tempLayer);map.addLayer(windLayer);" + R.id.temperature_map -> + "javascript:map.removeLayer(windLayer);map.removeLayer(rainLayer);map.addLayer(tempLayer);" + else -> throw IllegalStateException() + } ) true } @@ -59,8 +59,10 @@ class MapActivity : BaseAppCompatActivity( @SuppressLint("SetJavaScriptEnabled") settings.javaScriptEnabled = true loadUrl( - "file:///android_asset/map.html?lat=${city?.lat ?: 0.0}&lon=${city?.lng - ?: 0.0}&k=2.0&appid=${getString(R.string.app_id)}" + "file:///android_asset/map.html?lat=${city?.lat ?: 0.0}&lon=${ + city?.lng + ?: 0.0 + }&k=2.0&appid=${getString(R.string.app_id)}" ) setInitialScale(1) settings.loadWithOverviewMode = true @@ -74,4 +76,4 @@ class MapActivity : BaseAppCompatActivity( } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/ui/setting/SettingsActivity.kt b/app/src/main/java/com/hoc/weatherapp/ui/setting/SettingsActivity.kt index 29f68ed..3e79e21 100644 --- a/app/src/main/java/com/hoc/weatherapp/ui/setting/SettingsActivity.kt +++ b/app/src/main/java/com/hoc/weatherapp/ui/setting/SettingsActivity.kt @@ -19,7 +19,14 @@ import com.hoc.weatherapp.data.models.PressureUnit import com.hoc.weatherapp.data.models.SpeedUnit import com.hoc.weatherapp.data.models.TemperatureUnit import com.hoc.weatherapp.ui.BaseAppCompatActivity -import com.hoc.weatherapp.utils.* +import com.hoc.weatherapp.utils.ACTION_CANCEL_NOTIFICATION +import com.hoc.weatherapp.utils.None +import com.hoc.weatherapp.utils.Some +import com.hoc.weatherapp.utils.WEATHER_NOTIFICATION_ID +import com.hoc.weatherapp.utils.cancelNotificationById +import com.hoc.weatherapp.utils.debug +import com.hoc.weatherapp.utils.map +import com.hoc.weatherapp.utils.showOrUpdateNotification import com.hoc.weatherapp.worker.WorkerUtil.cancelUpdateCurrentWeatherWorkRequest import com.hoc.weatherapp.worker.WorkerUtil.cancelUpdateDailyWeatherWorkRequest import com.hoc.weatherapp.worker.WorkerUtil.enqueueUpdateCurrentWeatherWorkRequest @@ -31,8 +38,8 @@ import io.reactivex.rxkotlin.addTo import io.reactivex.rxkotlin.subscribeBy import io.reactivex.rxkotlin.withLatestFrom import io.reactivex.subjects.PublishSubject -import org.koin.android.ext.android.inject import kotlin.LazyThreadSafetyMode.NONE +import org.koin.android.ext.android.inject @ExperimentalStdlibApi class SettingsActivity : BaseAppCompatActivity( @@ -49,8 +56,8 @@ class SettingsActivity : BaseAppCompatActivity( if (supportFragmentManager.findFragmentById(android.R.id.content) === null) { supportFragmentManager.beginTransaction() - .add(android.R.id.content, SettingFragment()) - .commit() + .add(android.R.id.content, SettingFragment()) + .commit() } } @@ -77,7 +84,7 @@ class SettingsActivity : BaseAppCompatActivity( } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) = - setPreferencesFromResource(R.xml.preferences, rootKey) + setPreferencesFromResource(R.xml.preferences, rootKey) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -87,25 +94,30 @@ class SettingsActivity : BaseAppCompatActivity( */ showNotificationPreference.onPreferenceChangeListener = this - findPreference(getString(R.string.key_temperature_unit))!!.onPreferenceChangeListener = this - findPreference(getString(R.string.key_pressure_unit))!!.onPreferenceChangeListener = this - findPreference(getString(R.string.key_speed_unit))!!.onPreferenceChangeListener = this - findPreference(getString(R.string.key_auto_update))!!.onPreferenceChangeListener = this - findPreference(getString(R.string.key_dark_theme))!!.onPreferenceChangeListener = this + findPreference(getString(R.string.key_temperature_unit))!!.onPreferenceChangeListener = + this + findPreference(getString(R.string.key_pressure_unit))!!.onPreferenceChangeListener = + this + findPreference(getString(R.string.key_speed_unit))!!.onPreferenceChangeListener = + this + findPreference(getString(R.string.key_auto_update))!!.onPreferenceChangeListener = + this + findPreference(getString(R.string.key_dark_theme))!!.onPreferenceChangeListener = + this findPreference(getString(R.string.key_sound_notification))!!.run { onPreferenceChangeListener = this@SettingFragment /** * Only show `enable sound notification` when `show notification` is enabled */ settingPreferences.showNotificationPreference.observable - .observeOn(AndroidSchedulers.mainThread()) - .subscribeBy(onNext = { isVisible = it }) - .addTo(compositeDisposable) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeBy(onNext = { isVisible = it }) + .addTo(compositeDisposable) } findPreference("About")!!.setOnPreferenceClickListener { Intent(Intent.ACTION_VIEW) - .apply { data = Uri.parse("https://github.com/hoc081098/WeatherApp.git") } - .let { startActivity(it); true } + .apply { data = Uri.parse("https://github.com/hoc081098/WeatherApp.git") } + .let { startActivity(it); true } } /** @@ -116,31 +128,31 @@ class SettingsActivity : BaseAppCompatActivity( private fun setupSettingShowNotificationAndChangeTemperatureUnit() { Observable.mergeArray( - showNotificationS - .switchMap { showNotification -> - if (showNotification) { - repository - .getSelectedCityAndCurrentWeatherOfSelectedCity() - .take(1) - .withLatestFrom( - settingPreferences.temperatureUnitPreference.observable, - settingPreferences.soundNotificationPreference.observable - ) - .map { triple -> triple.first.map { Triple(it, triple.second, triple.third) } } - } else { - Observable.just(None) - } - }, - tempUnitS - .withLatestFrom(settingPreferences.showNotificationPreference.observable) - .filter { it.second } - .switchMap { pair1 -> - repository - .getSelectedCityAndCurrentWeatherOfSelectedCity() - .take(1) - .withLatestFrom(settingPreferences.soundNotificationPreference.observable) - .map { pair2 -> pair2.first.map { Triple(it, pair1.first, pair2.second) } } - } + showNotificationS + .switchMap { showNotification -> + if (showNotification) { + repository + .getSelectedCityAndCurrentWeatherOfSelectedCity() + .take(1) + .withLatestFrom( + settingPreferences.temperatureUnitPreference.observable, + settingPreferences.soundNotificationPreference.observable + ) + .map { triple -> triple.first.map { Triple(it, triple.second, triple.third) } } + } else { + Observable.just(None) + } + }, + tempUnitS + .withLatestFrom(settingPreferences.showNotificationPreference.observable) + .filter { it.second } + .switchMap { pair1 -> + repository + .getSelectedCityAndCurrentWeatherOfSelectedCity() + .take(1) + .withLatestFrom(settingPreferences.soundNotificationPreference.observable) + .map { pair2 -> pair2.first.map { Triple(it, pair1.first, pair2.second) } } + } ).subscribeBy(onNext = { debug("setting $it", "SETTINGS") @@ -150,10 +162,10 @@ class SettingsActivity : BaseAppCompatActivity( is Some -> it.value.let { triple -> triple.first.run { context.showOrUpdateNotification( - weather = currentWeather, - city = city, - unit = triple.second, - popUpAndSound = triple.third + weather = currentWeather, + city = city, + unit = triple.second, + popUpAndSound = triple.third ) } } @@ -168,16 +180,16 @@ class SettingsActivity : BaseAppCompatActivity( */ showNotificationPreference.isChecked = settingPreferences.showNotificationPreference.value LocalBroadcastManager - .getInstance(requireContext()) - .registerReceiver(broadcastReceiver, IntentFilter(ACTION_CANCEL_NOTIFICATION)) + .getInstance(requireContext()) + .registerReceiver(broadcastReceiver, IntentFilter(ACTION_CANCEL_NOTIFICATION)) } override fun onPause() { super.onPause() LocalBroadcastManager - .getInstance(requireContext()) - .unregisterReceiver(broadcastReceiver) + .getInstance(requireContext()) + .unregisterReceiver(broadcastReceiver) } override fun onDestroyView() { diff --git a/app/src/main/java/com/hoc/weatherapp/utils/DateUtil.kt b/app/src/main/java/com/hoc/weatherapp/utils/DateUtil.kt index a793e9c..02ddeef 100644 --- a/app/src/main/java/com/hoc/weatherapp/utils/DateUtil.kt +++ b/app/src/main/java/com/hoc/weatherapp/utils/DateUtil.kt @@ -1,9 +1,9 @@ package com.hoc.weatherapp.utils +import java.util.* import org.threeten.bp.Instant import org.threeten.bp.ZoneId import org.threeten.bp.ZonedDateTime -import java.util.* private val CALENDAR = Calendar.getInstance() @@ -21,5 +21,5 @@ fun Date.trim(): Date { fun Date.toZonedDateTime(zoneId: String): ZonedDateTime { return Instant.ofEpochMilli(time) - .atZone(runCatching { ZoneId.of(zoneId) }.getOrElse { ZoneId.systemDefault() }) + .atZone(runCatching { ZoneId.of(zoneId) }.getOrElse { ZoneId.systemDefault() }) } diff --git a/app/src/main/java/com/hoc/weatherapp/utils/ExtensionFuncs.kt b/app/src/main/java/com/hoc/weatherapp/utils/ExtensionFuncs.kt index f2be8a1..2bd8d84 100644 --- a/app/src/main/java/com/hoc/weatherapp/utils/ExtensionFuncs.kt +++ b/app/src/main/java/com/hoc/weatherapp/utils/ExtensionFuncs.kt @@ -21,7 +21,12 @@ import androidx.annotation.IntDef import androidx.annotation.RequiresApi import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment -import com.google.android.gms.location.* +import com.google.android.gms.location.FusedLocationProviderClient +import com.google.android.gms.location.LocationCallback +import com.google.android.gms.location.LocationRequest +import com.google.android.gms.location.LocationResult +import com.google.android.gms.location.LocationSettingsRequest +import com.google.android.gms.location.SettingsClient import com.google.android.material.snackbar.Snackbar import com.hoc.weatherapp.BuildConfig import io.reactivex.BackpressureStrategy @@ -40,103 +45,106 @@ annotation class SnackbarDuration @Suppress("ControlFlowWithEmptyBody", "ControlFlowWithEmptyBody") fun Context.toast(message: CharSequence, @ToastDuration duration: Int = Toast.LENGTH_SHORT): Toast = - Toast.makeText( - this, - message, - duration - ).apply(Toast::show) + Toast.makeText( + this, + message, + duration + ).apply(Toast::show) fun View.snackBar(message: CharSequence, @SnackbarDuration duration: Int = Snackbar.LENGTH_SHORT) = - Snackbar.make( - this, - message, - duration - ).apply(Snackbar::show) + Snackbar.make( + this, + message, + duration + ).apply(Snackbar::show) fun Fragment.toast(message: CharSequence) = context?.toast(message) inline fun Context.startActivity() = - startActivity(Intent(this, T::class.java)) + startActivity(Intent(this, T::class.java)) @Suppress("ControlFlowWithEmptyBody") inline fun T.debug(msg: Any?, tag: String? = null, throwable: Throwable? = null) { if (BuildConfig.DEBUG) { Log.d(tag ?: this::class.java.simpleName, msg.toString(), throwable) } else { - //Not logging in release mode + // Not logging in release mode } } -object AccessLocationPermissionDeniedException : Exception("need granted access location permission") +object AccessLocationPermissionDeniedException : + Exception("need granted access location permission") @SuppressLint("MissingPermission") fun Context.checkLocationSettingAndGetCurrentLocation( - settingsClient: SettingsClient, - locationSettingsRequest: LocationSettingsRequest, - fusedLocationProviderClient: FusedLocationProviderClient, - locationRequest: LocationRequest + settingsClient: SettingsClient, + locationSettingsRequest: LocationSettingsRequest, + fusedLocationProviderClient: FusedLocationProviderClient, + locationRequest: LocationRequest ): Single { @Suppress("ControlFlowWithEmptyBody") return Observable - .create { emitter -> - settingsClient - .checkLocationSettings(locationSettingsRequest) - .addOnFailureListener { - if (!emitter.isDisposed) { - emitter.onError(it) + .create { emitter -> + settingsClient + .checkLocationSettings(locationSettingsRequest) + .addOnFailureListener { + if (!emitter.isDisposed) { + emitter.onError(it) + } + } + .addOnSuccessListener { + if (isAccessLocationPermissionDenied(emitter)) return@addOnSuccessListener + + fusedLocationProviderClient + .lastLocation + .addOnSuccessListener { lastLocation -> + if (lastLocation != null && !emitter.isDisposed) { + emitter.onNext(lastLocation) + emitter.onComplete() } } - .addOnSuccessListener { - - if (isAccessLocationPermissionDenied(emitter)) return@addOnSuccessListener - - fusedLocationProviderClient - .lastLocation - .addOnSuccessListener { lastLocation -> - if (lastLocation != null && !emitter.isDisposed) { - emitter.onNext(lastLocation) - emitter.onComplete() - } - } - - if (isAccessLocationPermissionDenied(emitter)) return@addOnSuccessListener - - val callback = object : LocationCallback() { - override fun onLocationResult(locationResult: LocationResult) { - val lastLocation = locationResult.lastLocation ?: return - - if (!emitter.isDisposed) { - emitter.onNext(lastLocation) - emitter.onComplete() - debug( - "Get location successfully: $lastLocation", - "Context::checkLocationSettingAndGetCurrentLocation" - ) - } - } - } - fusedLocationProviderClient.requestLocationUpdates( - locationRequest, - callback, - null /* LOOPER */ - ) - emitter.setCancellable { + if (isAccessLocationPermissionDenied(emitter)) return@addOnSuccessListener + + val callback = object : LocationCallback() { + override fun onLocationResult(locationResult: LocationResult) { + val lastLocation = locationResult.lastLocation ?: return + + if (!emitter.isDisposed) { + emitter.onNext(lastLocation) + emitter.onComplete() debug( - "removeLocationUpdates", - "Context::checkLocationSettingAndGetCurrentLocation" + "Get location successfully: $lastLocation", + "Context::checkLocationSettingAndGetCurrentLocation" ) - fusedLocationProviderClient.removeLocationUpdates(callback) } } - } - .take(1) - .singleOrError() + } + fusedLocationProviderClient.requestLocationUpdates( + locationRequest, + callback, + null /* LOOPER */ + ) + + emitter.setCancellable { + debug( + "removeLocationUpdates", + "Context::checkLocationSettingAndGetCurrentLocation" + ) + fusedLocationProviderClient.removeLocationUpdates(callback) + } + } + } + .take(1) + .singleOrError() } private fun Context.isAccessLocationPermissionDenied(emitter: ObservableEmitter): Boolean { - if (ContextCompat.checkSelfPermission(this, ACCESS_FINE_LOCATION) != PERMISSION_GRANTED - && ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PERMISSION_GRANTED + if (ContextCompat.checkSelfPermission(this, ACCESS_FINE_LOCATION) != PERMISSION_GRANTED && + ContextCompat.checkSelfPermission( + this, + Manifest.permission.ACCESS_COARSE_LOCATION + ) != PERMISSION_GRANTED ) { if (!emitter.isDisposed) { emitter.onError(AccessLocationPermissionDeniedException) @@ -147,13 +155,13 @@ private fun Context.isAccessLocationPermissionDenied(emitter: ObservableEmitter< } inline fun Observable<*>.notOfType(): Observable = - filter { !R::class.java.isInstance(it) } + filter { !R::class.java.isInstance(it) } inline fun Observable.exhaustMap(crossinline transform: (T) -> Observable): Observable { return this - .toFlowable(BackpressureStrategy.DROP) - .flatMap({ transform(it).toFlowable(BackpressureStrategy.MISSING) }, 1) - .toObservable() + .toFlowable(BackpressureStrategy.DROP) + .flatMap({ transform(it).toFlowable(BackpressureStrategy.MISSING) }, 1) + .toObservable() } @Suppress("nothing_to_inline") @@ -165,7 +173,6 @@ fun Context.themeColor(@AttrRes attrRes: Int): Int { return typedValue.data } - fun Drawable.setColorFilter(color: Int, mode: Mode = Mode.SRC_ATOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { colorFilter = BlendModeColorFilter(color, mode.getBlendMode()) @@ -199,46 +206,46 @@ enum class Mode { @RequiresApi(Build.VERSION_CODES.Q) fun getBlendMode(): BlendMode = - when (this) { - CLEAR -> BlendMode.CLEAR - SRC -> BlendMode.SRC - DST -> BlendMode.DST - SRC_OVER -> BlendMode.SRC_OVER - DST_OVER -> BlendMode.DST_OVER - SRC_IN -> BlendMode.SRC_IN - DST_IN -> BlendMode.DST_IN - SRC_OUT -> BlendMode.SRC_OUT - DST_OUT -> BlendMode.DST_OUT - SRC_ATOP -> BlendMode.SRC_ATOP - DST_ATOP -> BlendMode.DST_ATOP - XOR -> BlendMode.XOR - DARKEN -> BlendMode.DARKEN - LIGHTEN -> BlendMode.LIGHTEN - MULTIPLY -> BlendMode.MULTIPLY - SCREEN -> BlendMode.SCREEN - ADD -> BlendMode.PLUS - OVERLAY -> BlendMode.OVERLAY - } + when (this) { + CLEAR -> BlendMode.CLEAR + SRC -> BlendMode.SRC + DST -> BlendMode.DST + SRC_OVER -> BlendMode.SRC_OVER + DST_OVER -> BlendMode.DST_OVER + SRC_IN -> BlendMode.SRC_IN + DST_IN -> BlendMode.DST_IN + SRC_OUT -> BlendMode.SRC_OUT + DST_OUT -> BlendMode.DST_OUT + SRC_ATOP -> BlendMode.SRC_ATOP + DST_ATOP -> BlendMode.DST_ATOP + XOR -> BlendMode.XOR + DARKEN -> BlendMode.DARKEN + LIGHTEN -> BlendMode.LIGHTEN + MULTIPLY -> BlendMode.MULTIPLY + SCREEN -> BlendMode.SCREEN + ADD -> BlendMode.PLUS + OVERLAY -> BlendMode.OVERLAY + } fun getPorterDuffMode(): PorterDuff.Mode = - when (this) { - CLEAR -> PorterDuff.Mode.CLEAR - SRC -> PorterDuff.Mode.SRC - DST -> PorterDuff.Mode.DST - SRC_OVER -> PorterDuff.Mode.SRC_OVER - DST_OVER -> PorterDuff.Mode.DST_OVER - SRC_IN -> PorterDuff.Mode.SRC_IN - DST_IN -> PorterDuff.Mode.DST_IN - SRC_OUT -> PorterDuff.Mode.SRC_OUT - DST_OUT -> PorterDuff.Mode.DST_OUT - SRC_ATOP -> PorterDuff.Mode.SRC_ATOP - DST_ATOP -> PorterDuff.Mode.DST_ATOP - XOR -> PorterDuff.Mode.XOR - DARKEN -> PorterDuff.Mode.DARKEN - LIGHTEN -> PorterDuff.Mode.LIGHTEN - MULTIPLY -> PorterDuff.Mode.MULTIPLY - SCREEN -> PorterDuff.Mode.SCREEN - ADD -> PorterDuff.Mode.ADD - OVERLAY -> PorterDuff.Mode.OVERLAY - } + when (this) { + CLEAR -> PorterDuff.Mode.CLEAR + SRC -> PorterDuff.Mode.SRC + DST -> PorterDuff.Mode.DST + SRC_OVER -> PorterDuff.Mode.SRC_OVER + DST_OVER -> PorterDuff.Mode.DST_OVER + SRC_IN -> PorterDuff.Mode.SRC_IN + DST_IN -> PorterDuff.Mode.DST_IN + SRC_OUT -> PorterDuff.Mode.SRC_OUT + DST_OUT -> PorterDuff.Mode.DST_OUT + SRC_ATOP -> PorterDuff.Mode.SRC_ATOP + DST_ATOP -> PorterDuff.Mode.DST_ATOP + XOR -> PorterDuff.Mode.XOR + DARKEN -> PorterDuff.Mode.DARKEN + LIGHTEN -> PorterDuff.Mode.LIGHTEN + MULTIPLY -> PorterDuff.Mode.MULTIPLY + SCREEN -> PorterDuff.Mode.SCREEN + ADD -> PorterDuff.Mode.ADD + OVERLAY -> PorterDuff.Mode.OVERLAY + } } diff --git a/app/src/main/java/com/hoc/weatherapp/utils/MyUnsafeLazyImpl.kt b/app/src/main/java/com/hoc/weatherapp/utils/MyUnsafeLazyImpl.kt index 1f80b77..0f1e32d 100644 --- a/app/src/main/java/com/hoc/weatherapp/utils/MyUnsafeLazyImpl.kt +++ b/app/src/main/java/com/hoc/weatherapp/utils/MyUnsafeLazyImpl.kt @@ -26,11 +26,11 @@ internal class MyUnsafeLazyImpl(initializer: () -> T) : Lazy, Serializ override fun isInitialized(): Boolean = _value !== UninitializedValue override fun toString(): String = - if (isInitialized()) value.toString() else "Lazy value not initialized yet." + if (isInitialized()) value.toString() else "Lazy value not initialized yet." private fun writeReplace(): Any = InitializedLazyImpl(value) fun cleanUp() { initializer = null } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/utils/NotificationUtil.kt b/app/src/main/java/com/hoc/weatherapp/utils/NotificationUtil.kt index 94e7abd..f5c63ae 100644 --- a/app/src/main/java/com/hoc/weatherapp/utils/NotificationUtil.kt +++ b/app/src/main/java/com/hoc/weatherapp/utils/NotificationUtil.kt @@ -18,8 +18,8 @@ import com.hoc.weatherapp.data.models.entity.CityAndCurrentWeather import com.hoc.weatherapp.data.models.entity.CurrentWeather import com.hoc.weatherapp.ui.SplashActivity import com.hoc.weatherapp.utils.ui.getIconDrawableFromCurrentWeather -import org.threeten.bp.format.DateTimeFormatter import java.util.* +import org.threeten.bp.format.DateTimeFormatter const val WEATHER_NOTIFICATION_ID = 2 const val ACTION_CANCEL_NOTIFICATION = "com.hoc.weatherapp.CancelNotificationReceiver" @@ -29,105 +29,104 @@ private const val TAG = "__notification__" @ExperimentalStdlibApi fun Context.showOrUpdateNotification( - weather: CurrentWeather, - city: City, - unit: TemperatureUnit, - popUpAndSound: Boolean // TODO:something is wrong + weather: CurrentWeather, + city: City, + unit: TemperatureUnit, + popUpAndSound: Boolean // TODO:something is wrong ) { val temperature = unit.format(weather.temperature) val text = HtmlCompat.fromHtml( - """$temperature + """$temperature |
|${weather.description.capitalize(Locale.ROOT)} |
|Update time: ${weather.dataTime.toZonedDateTime(city.zoneId).format(DATE_TIME_FORMATTER)} - """.trimMargin(), - HtmlCompat.FROM_HTML_MODE_LEGACY + """.trimMargin(), + HtmlCompat.FROM_HTML_MODE_LEGACY ) val notification = NotificationCompat.Builder(this, getString(R.string.notification_channel_id)) - .setSmallIcon( - getIconDrawableFromCurrentWeather( - weatherConditionId = weather.weatherConditionId, - weatherIcon = weather.icon - ) - ) - .setContentTitle("${city.name} - ${city.country}") - .setContentText(temperature) - .setStyle(NotificationCompat.BigTextStyle().bigText(text)) - .addAction( - R.drawable.ic_close, - "Dismiss", - PendingIntent.getBroadcast( - this, - 0, - Intent(this, CancelNotificationReceiver::class.java).apply { - action = ACTION_CANCEL_NOTIFICATION - }, - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE - } else { - PendingIntent.FLAG_CANCEL_CURRENT - } - ) + .setSmallIcon( + getIconDrawableFromCurrentWeather( + weatherConditionId = weather.weatherConditionId, + weatherIcon = weather.icon ) - .setAutoCancel(false) - .setOngoing(true) - .setWhen(System.currentTimeMillis()) - .apply { - if (popUpAndSound) { - priority = NotificationCompat.PRIORITY_HIGH - setDefaults(NotificationCompat.DEFAULT_ALL) - setSound(RingtoneManager.getDefaultUri(TYPE_NOTIFICATION)) + ) + .setContentTitle("${city.name} - ${city.country}") + .setContentText(temperature) + .setStyle(NotificationCompat.BigTextStyle().bigText(text)) + .addAction( + R.drawable.ic_close, + "Dismiss", + PendingIntent.getBroadcast( + this, + 0, + Intent(this, CancelNotificationReceiver::class.java).apply { + action = ACTION_CANCEL_NOTIFICATION + }, + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE + } else { + PendingIntent.FLAG_CANCEL_CURRENT } + ) + ) + .setAutoCancel(false) + .setOngoing(true) + .setWhen(System.currentTimeMillis()) + .apply { + if (popUpAndSound) { + priority = NotificationCompat.PRIORITY_HIGH + setDefaults(NotificationCompat.DEFAULT_ALL) + setSound(RingtoneManager.getDefaultUri(TYPE_NOTIFICATION)) + } - val resultPendingIntent = PendingIntent.getActivity( - this@showOrUpdateNotification, - 0, - Intent(applicationContext, SplashActivity::class.java), - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE - } else { - PendingIntent.FLAG_CANCEL_CURRENT - } - ) - setContentIntent(resultPendingIntent) - }.build() + val resultPendingIntent = PendingIntent.getActivity( + this@showOrUpdateNotification, + 0, + Intent(applicationContext, SplashActivity::class.java), + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE + } else { + PendingIntent.FLAG_CANCEL_CURRENT + } + ) + setContentIntent(resultPendingIntent) + }.build() debug( - ".showOrUpdateNotification weather = [$weather], city = [$city], unit = [$unit], popUpAndSound = [$popUpAndSound]", - TAG + ".showOrUpdateNotification weather = [$weather], city = [$city], unit = [$unit], popUpAndSound = [$popUpAndSound]", + TAG ) debug( - ".showOrUpdateNotification notification = [$notification]", - TAG + ".showOrUpdateNotification notification = [$notification]", + TAG ) (applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).notify( - WEATHER_NOTIFICATION_ID, - notification + WEATHER_NOTIFICATION_ID, + notification ) } fun Context.cancelNotificationById(id: Int) = - (applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager) - .cancel(id).also { debug(".cancelNotificationById id = [$id]", TAG) } - + (applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager) + .cancel(id).also { debug(".cancelNotificationById id = [$id]", TAG) } @ExperimentalStdlibApi fun Context.showNotificationIfEnabled( - cityAndCurrentWeather: CityAndCurrentWeather, - settingPreferences: SettingPreferences + cityAndCurrentWeather: CityAndCurrentWeather, + settingPreferences: SettingPreferences ) { debug(".showNotificationIfEnabled", TAG) debug( - "cityAndCurrentWeather = [$cityAndCurrentWeather], settingPreferences = [$settingPreferences]", - TAG + "cityAndCurrentWeather = [$cityAndCurrentWeather], settingPreferences = [$settingPreferences]", + TAG ) if (settingPreferences.showNotificationPreference.value) { showOrUpdateNotification( - weather = cityAndCurrentWeather.currentWeather, - city = cityAndCurrentWeather.city, - unit = settingPreferences.temperatureUnitPreference.value, - popUpAndSound = settingPreferences.soundNotificationPreference.value + weather = cityAndCurrentWeather.currentWeather, + city = cityAndCurrentWeather.city, + unit = settingPreferences.temperatureUnitPreference.value, + popUpAndSound = settingPreferences.soundNotificationPreference.value ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/utils/SharedPrefExtension.kt b/app/src/main/java/com/hoc/weatherapp/utils/SharedPrefExtension.kt index 6065b5f..15f9f55 100644 --- a/app/src/main/java/com/hoc/weatherapp/utils/SharedPrefExtension.kt +++ b/app/src/main/java/com/hoc/weatherapp/utils/SharedPrefExtension.kt @@ -7,76 +7,77 @@ import kotlin.reflect.KProperty @Suppress("UNCHECKED_CAST") inline fun SharedPreferences.delegate( - key: String? = null, - default: T? = null + key: String? = null, + default: T? = null ): ReadWriteProperty = when ( - val kClass = T::class) { + val kClass = T::class +) { Int::class -> delegateVar( - SharedPreferences::getInt, - SharedPreferences.Editor::putInt, - (default as? Int) ?: 0, - key + SharedPreferences::getInt, + SharedPreferences.Editor::putInt, + (default as? Int) ?: 0, + key ) Long::class -> delegateVar( - SharedPreferences::getLong, - SharedPreferences.Editor::putLong, - (default as? Long) ?: 0, - key + SharedPreferences::getLong, + SharedPreferences.Editor::putLong, + (default as? Long) ?: 0, + key ) Float::class -> delegateVar( - SharedPreferences::getFloat, - SharedPreferences.Editor::putFloat, - (default as? Float) ?: 0f, - key + SharedPreferences::getFloat, + SharedPreferences.Editor::putFloat, + (default as? Float) ?: 0f, + key ) Double::class -> delegateVar( - SharedPreferences::getDouble, - SharedPreferences.Editor::putDouble, - (default as? Double) ?: 0.0, - key + SharedPreferences::getDouble, + SharedPreferences.Editor::putDouble, + (default as? Double) ?: 0.0, + key ) Boolean::class -> delegateVar( - SharedPreferences::getBoolean, - SharedPreferences.Editor::putBoolean, - (default as? Boolean) ?: false, - key + SharedPreferences::getBoolean, + SharedPreferences.Editor::putBoolean, + (default as? Boolean) ?: false, + key ) String::class -> delegateVar( - SharedPreferences::getString, - SharedPreferences.Editor::putString, - (default as? String).orEmpty(), - key + SharedPreferences::getString, + SharedPreferences.Editor::putString, + (default as? String).orEmpty(), + key ) Set::class -> delegateVar>( - { k, defaultValue -> getStringSet(k, defaultValue)?.toSet() ?: emptySet() }, - SharedPreferences.Editor::putStringSet, - (default as? Set<*>).orEmpty().filterIsInstanceTo(mutableSetOf()), - key + { k, defaultValue -> getStringSet(k, defaultValue)?.toSet() ?: emptySet() }, + SharedPreferences.Editor::putStringSet, + (default as? Set<*>).orEmpty().filterIsInstanceTo(mutableSetOf()), + key ) else -> throw IllegalStateException("Not support for type ${kClass.java.simpleName}") } as ReadWriteProperty @PublishedApi internal fun SharedPreferences.Editor.putDouble( - key: String, - value: Double + key: String, + value: Double ): SharedPreferences.Editor = putLong(key, value.toRawBits()) @PublishedApi internal fun SharedPreferences.getDouble( - key: String, - defaultValue: Double + key: String, + defaultValue: Double ): Double = Double.fromBits(getLong(key, defaultValue.toRawBits())) fun SharedPreferences.delegateVar( - getter: SharedPreferences.(key: String, defaultValue: T) -> T, - setter: SharedPreferences.Editor.(key: String, value: T) -> SharedPreferences.Editor, - defaultValue: T, - key: String? = null + getter: SharedPreferences.(key: String, defaultValue: T) -> T, + setter: SharedPreferences.Editor.(key: String, value: T) -> SharedPreferences.Editor, + defaultValue: T, + key: String? = null ): ReadWriteProperty { return object : ReadWriteProperty { override fun getValue(thisRef: Any, property: KProperty<*>) = - getter(key ?: property.name, defaultValue) + getter(key ?: property.name, defaultValue) override fun setValue(thisRef: Any, property: KProperty<*>, value: T) { val k = key ?: property.name @@ -88,12 +89,12 @@ fun SharedPreferences.delegateVar( } fun SharedPreferences.delegateVal( - getter: SharedPreferences.(key: String, defaultValue: T) -> T, - defaultValue: T, - key: String? = null + getter: SharedPreferences.(key: String, defaultValue: T) -> T, + defaultValue: T, + key: String? = null ): ReadOnlyProperty { return object : ReadOnlyProperty { override fun getValue(thisRef: Any, property: KProperty<*>) = - getter(key ?: property.name, defaultValue) + getter(key ?: property.name, defaultValue) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/utils/UnitConverter.kt b/app/src/main/java/com/hoc/weatherapp/utils/UnitConverter.kt index 4a606d8..55ca080 100644 --- a/app/src/main/java/com/hoc/weatherapp/utils/UnitConverter.kt +++ b/app/src/main/java/com/hoc/weatherapp/utils/UnitConverter.kt @@ -4,9 +4,13 @@ import com.hoc.weatherapp.data.models.PressureUnit import com.hoc.weatherapp.data.models.PressureUnit.HPA import com.hoc.weatherapp.data.models.PressureUnit.MM_HG import com.hoc.weatherapp.data.models.SpeedUnit -import com.hoc.weatherapp.data.models.SpeedUnit.* +import com.hoc.weatherapp.data.models.SpeedUnit.KILOMETERS_PER_HOUR +import com.hoc.weatherapp.data.models.SpeedUnit.METERS_PER_SECOND +import com.hoc.weatherapp.data.models.SpeedUnit.MILES_PER_HOUR import com.hoc.weatherapp.data.models.TemperatureUnit -import com.hoc.weatherapp.data.models.TemperatureUnit.* +import com.hoc.weatherapp.data.models.TemperatureUnit.CELSIUS +import com.hoc.weatherapp.data.models.TemperatureUnit.FAHRENHEIT +import com.hoc.weatherapp.data.models.TemperatureUnit.KELVIN object UnitConverter { /** @@ -40,4 +44,4 @@ object UnitConverter { MM_HG -> pressureIn_hPa * 0.75006157584566 } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/utils/blur/BlurImageUtil.kt b/app/src/main/java/com/hoc/weatherapp/utils/blur/BlurImageUtil.kt index f2b266b..8355643 100644 --- a/app/src/main/java/com/hoc/weatherapp/utils/blur/BlurImageUtil.kt +++ b/app/src/main/java/com/hoc/weatherapp/utils/blur/BlurImageUtil.kt @@ -14,9 +14,9 @@ object BlurImageUtil { try { val output = Bitmap.createBitmap( - bitmap.width, - bitmap.height, - Bitmap.Config.ARGB_8888 + bitmap.width, + bitmap.height, + Bitmap.Config.ARGB_8888 ) // Create a RenderScript context. @@ -28,11 +28,11 @@ object BlurImageUtil { // Use the ScriptIntrinsicBlur intrinsic. ScriptIntrinsicBlur.create(rsContext, Element.U8_4(rsContext)) - .run { - setRadius(radius) - setInput(inAlloc) - forEach(outAlloc) - } + .run { + setRadius(radius) + setInput(inAlloc) + forEach(outAlloc) + } // Copy to the output bitmap from the allocation. outAlloc.copyTo(output) diff --git a/app/src/main/java/com/hoc/weatherapp/utils/blur/GlideBlurTransformation.kt b/app/src/main/java/com/hoc/weatherapp/utils/blur/GlideBlurTransformation.kt index 361a599..2d8730b 100644 --- a/app/src/main/java/com/hoc/weatherapp/utils/blur/GlideBlurTransformation.kt +++ b/app/src/main/java/com/hoc/weatherapp/utils/blur/GlideBlurTransformation.kt @@ -7,12 +7,12 @@ import com.bumptech.glide.load.resource.bitmap.BitmapTransformation import java.security.MessageDigest class GlideBlurTransformation(private val context: Context, private val radius: Float) : - BitmapTransformation() { + BitmapTransformation() { override fun transform( - pool: BitmapPool, - toTransform: Bitmap, - outWidth: Int, - outHeight: Int + pool: BitmapPool, + toTransform: Bitmap, + outWidth: Int, + outHeight: Int ): Bitmap { return BlurImageUtil.blurRenderScript(toTransform, radius, context) } @@ -22,7 +22,7 @@ class GlideBlurTransformation(private val context: Context, private val radius: } override fun equals(other: Any?): Boolean = - this === other || radius == (other as? GlideBlurTransformation)?.radius + this === other || radius == (other as? GlideBlurTransformation)?.radius override fun hashCode(): Int = 31 * radius.hashCode() + ID.hashCode() diff --git a/app/src/main/java/com/hoc/weatherapp/utils/ui/BackgroundAndSoundUtil.kt b/app/src/main/java/com/hoc/weatherapp/utils/ui/BackgroundAndSoundUtil.kt index dc5d02c..c8a47ee 100644 --- a/app/src/main/java/com/hoc/weatherapp/utils/ui/BackgroundAndSoundUtil.kt +++ b/app/src/main/java/com/hoc/weatherapp/utils/ui/BackgroundAndSoundUtil.kt @@ -23,8 +23,8 @@ import org.threeten.bp.ZonedDateTime */ private fun isDay( - w: CurrentWeather, - city: City + w: CurrentWeather, + city: City ): Boolean { val now = ZonedDateTime.now(ZoneId.of(city.zoneId)) return now in w.sunrise.toZonedDateTime(city.zoneId)..w.sunset.toZonedDateTime(city.zoneId) @@ -33,9 +33,9 @@ private fun isDay( @DrawableRes fun getBackgroundDrawableFromWeather(weather: CurrentWeather, city: City): Int { return when { - weather.weatherConditionId == 800L - && isDay(weather, city) - && weather.temperature > 35 + 273.15 /* 35℃ */ -> { + weather.weatherConditionId == 800L && + isDay(weather, city) && + weather.temperature > 35 + 273.15 /* 35℃ */ -> { R.drawable.hot_bg } @@ -69,8 +69,8 @@ fun getBackgroundDrawableFromWeather(weather: CurrentWeather, city: City): Int { @DrawableRes fun Context.getIconDrawableFromCurrentWeather( - weatherConditionId: Long, - weatherIcon: String + weatherConditionId: Long, + weatherIcon: String ): Int { if (weatherConditionId == 741L) { return R.drawable.weather_foggy @@ -85,18 +85,18 @@ fun Context.getIconDrawableFromCurrentWeather( return R.drawable.weather_tornado } return resources.getIdentifier( - "weather_icon_$weatherIcon", - "drawable", - packageName + "weather_icon_$weatherIcon", + "drawable", + packageName ).takeIf { it != 0 } ?: R.drawable.weather_icon_null } @DrawableRes fun Context.getIconDrawableFromDailyWeather(icon: String): Int { return resources.getIdentifier( - "weather_icon_$icon", - "drawable", - packageName + "weather_icon_$icon", + "drawable", + packageName ).takeIf { it != 0 } ?: R.drawable.weather_icon_null } diff --git a/app/src/main/java/com/hoc/weatherapp/utils/ui/CustomViewPager.kt b/app/src/main/java/com/hoc/weatherapp/utils/ui/CustomViewPager.kt index 674dfeb..e2a7d70 100644 --- a/app/src/main/java/com/hoc/weatherapp/utils/ui/CustomViewPager.kt +++ b/app/src/main/java/com/hoc/weatherapp/utils/ui/CustomViewPager.kt @@ -7,8 +7,8 @@ import android.view.MotionEvent import androidx.viewpager.widget.ViewPager class CustomViewPager @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null + context: Context, + attrs: AttributeSet? = null ) : ViewPager(context, attrs) { var pagingEnable = false @@ -20,4 +20,4 @@ class CustomViewPager @JvmOverloads constructor( override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { return pagingEnable && super.onInterceptTouchEvent(ev) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/utils/ui/HeaderItemDecoration.kt b/app/src/main/java/com/hoc/weatherapp/utils/ui/HeaderItemDecoration.kt index 23ff077..ca3f2e9 100644 --- a/app/src/main/java/com/hoc/weatherapp/utils/ui/HeaderItemDecoration.kt +++ b/app/src/main/java/com/hoc/weatherapp/utils/ui/HeaderItemDecoration.kt @@ -29,8 +29,8 @@ class HeaderItemDecoration(private val listener: StickyHeaderI val childInContact = getChildInContact(parent, headerView.bottom) - if (childInContact != null - && parent.getChildAdapterPosition(childInContact) + if (childInContact != null && + parent.getChildAdapterPosition(childInContact) .let { it != RecyclerView.NO_POSITION && listener.isHeader(it) } ) { moveHeader(c, headerView, childInContact) @@ -62,7 +62,6 @@ class HeaderItemDecoration(private val listener: StickyHeaderI } private fun fixLayoutSize(parent: ViewGroup, view: View) { - // Specs for parent (RecyclerView) val widthSpec = View.MeasureSpec.makeMeasureSpec(parent.width, View.MeasureSpec.EXACTLY) val heightSpec = View.MeasureSpec.makeMeasureSpec(parent.height, View.MeasureSpec.UNSPECIFIED) @@ -93,4 +92,4 @@ class HeaderItemDecoration(private val listener: StickyHeaderI fun isHeader(itemPosition: Int): Boolean } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/utils/ui/SwipeController.kt b/app/src/main/java/com/hoc/weatherapp/utils/ui/SwipeController.kt index c565a5d..e2dbb86 100644 --- a/app/src/main/java/com/hoc/weatherapp/utils/ui/SwipeController.kt +++ b/app/src/main/java/com/hoc/weatherapp/utils/ui/SwipeController.kt @@ -8,7 +8,9 @@ import android.graphics.RectF import android.view.MotionEvent import androidx.core.content.ContextCompat import androidx.recyclerview.widget.ItemTouchHelper -import androidx.recyclerview.widget.ItemTouchHelper.* +import androidx.recyclerview.widget.ItemTouchHelper.ACTION_STATE_SWIPE +import androidx.recyclerview.widget.ItemTouchHelper.LEFT +import androidx.recyclerview.widget.ItemTouchHelper.RIGHT import androidx.recyclerview.widget.RecyclerView import com.hoc.weatherapp.R @@ -24,25 +26,25 @@ interface SwipeControllerActions { } class SwipeController(private val buttonsActions: SwipeControllerActions) : - ItemTouchHelper.Callback() { + ItemTouchHelper.Callback() { private var buttonShowedState: ButtonState = - ButtonState.GONE + ButtonState.GONE private var swipeBack: Boolean = false private val buttonWidth = 192f private var currentViewHolder: RecyclerView.ViewHolder? = null private var buttonInstance: RectF? = null override fun getMovementFlags( - recyclerView: RecyclerView, - viewHolder: RecyclerView.ViewHolder + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder ): Int { return makeMovementFlags(0, LEFT or RIGHT) } override fun onMove( - recyclerView: RecyclerView, - viewHolder: RecyclerView.ViewHolder, - target: RecyclerView.ViewHolder + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder ): Boolean { return false } @@ -59,13 +61,13 @@ class SwipeController(private val buttonsActions: SwipeControllerActions) : } override fun onChildDraw( - c: Canvas, - recyclerView: RecyclerView, - viewHolder: RecyclerView.ViewHolder, - dX: Float, - dY: Float, - actionState: Int, - isCurrentlyActive: Boolean + c: Canvas, + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + dX: Float, + dY: Float, + actionState: Int, + isCurrentlyActive: Boolean ) { @Suppress("NAME_SHADOWING") var dX = dX @@ -74,23 +76,23 @@ class SwipeController(private val buttonsActions: SwipeControllerActions) : if (buttonShowedState == ButtonState.LEFT_VISIBLE) dX = maxOf(dX, buttonWidth) if (buttonShowedState == ButtonState.RIGHT_VISIBLE) dX = minOf(dX, -buttonWidth) super.onChildDraw( - c, - recyclerView, - viewHolder, - dX, - dY, - actionState, - isCurrentlyActive + c, + recyclerView, + viewHolder, + dX, + dY, + actionState, + isCurrentlyActive ) } else { setTouchListener( - c, - recyclerView, - viewHolder, - dX, - dY, - actionState, - isCurrentlyActive + c, + recyclerView, + viewHolder, + dX, + dY, + actionState, + isCurrentlyActive ) } } @@ -103,34 +105,36 @@ class SwipeController(private val buttonsActions: SwipeControllerActions) : @SuppressLint("ClickableViewAccessibility") private fun setTouchListener( - c: Canvas, - recyclerView: RecyclerView, - viewHolder: RecyclerView.ViewHolder, - dX: Float, - dY: Float, - actionState: Int, - isCurrentlyActive: Boolean + c: Canvas, + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + dX: Float, + dY: Float, + actionState: Int, + isCurrentlyActive: Boolean ) { recyclerView.setOnTouchListener { _, event -> swipeBack = event.action == MotionEvent.ACTION_CANCEL || event.action == - MotionEvent.ACTION_UP + MotionEvent.ACTION_UP if (swipeBack) { when { - dX < -buttonWidth -> buttonShowedState = + dX < -buttonWidth -> + buttonShowedState = ButtonState.RIGHT_VISIBLE - dX > buttonWidth -> buttonShowedState = + dX > buttonWidth -> + buttonShowedState = ButtonState.LEFT_VISIBLE } if (buttonShowedState != ButtonState.GONE) { setTouchDownListener( - c, - recyclerView, - viewHolder, - dX, - dY, - actionState, - isCurrentlyActive + c, + recyclerView, + viewHolder, + dX, + dY, + actionState, + isCurrentlyActive ) setItemsClickable(recyclerView, false) } @@ -141,24 +145,24 @@ class SwipeController(private val buttonsActions: SwipeControllerActions) : @SuppressLint("ClickableViewAccessibility") private fun setTouchDownListener( - c: Canvas, - recyclerView: RecyclerView, - viewHolder: RecyclerView.ViewHolder, - dX: Float, - dY: Float, - actionState: Int, - isCurrentlyActive: Boolean + c: Canvas, + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + dX: Float, + dY: Float, + actionState: Int, + isCurrentlyActive: Boolean ) { recyclerView.setOnTouchListener { _, event -> if (event.action == MotionEvent.ACTION_DOWN && event.action != MotionEvent.ACTION_MOVE) { setTouchUpListener( - c, - recyclerView, - viewHolder, - dX, - dY, - actionState, - isCurrentlyActive + c, + recyclerView, + viewHolder, + dX, + dY, + actionState, + isCurrentlyActive ) } false @@ -167,24 +171,24 @@ class SwipeController(private val buttonsActions: SwipeControllerActions) : @SuppressLint("ClickableViewAccessibility") private fun setTouchUpListener( - c: Canvas, - recyclerView: RecyclerView, - viewHolder: RecyclerView.ViewHolder, - @Suppress("UNUSED_PARAMETER") dX: Float, - dY: Float, - actionState: Int, - isCurrentlyActive: Boolean + c: Canvas, + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + @Suppress("UNUSED_PARAMETER") dX: Float, + dY: Float, + actionState: Int, + isCurrentlyActive: Boolean ) { recyclerView.setOnTouchListener { _, event -> if (event.action == MotionEvent.ACTION_UP) { super@SwipeController.onChildDraw( - c, - recyclerView, - viewHolder, - 0f, - dY, - actionState, - isCurrentlyActive + c, + recyclerView, + viewHolder, + 0f, + dY, + actionState, + isCurrentlyActive ) recyclerView.setOnTouchListener { _, _ -> false } setItemsClickable(recyclerView, true) @@ -218,20 +222,20 @@ class SwipeController(private val buttonsActions: SwipeControllerActions) : val p = Paint() val leftButton = RectF( - itemView.left.toFloat() + 4, - itemView.top.toFloat() + 8, - itemView.left + buttonWidthWithoutPadding, - itemView.bottom.toFloat() - 8 + itemView.left.toFloat() + 4, + itemView.top.toFloat() + 8, + itemView.left + buttonWidthWithoutPadding, + itemView.bottom.toFloat() - 8 ) p.color = ContextCompat.getColor(itemView.context, R.color.colorPrimary) c.drawRoundRect(leftButton, corners, corners, p) drawText("REFRESH", c, leftButton, p) val rightButton = RectF( - itemView.right - buttonWidthWithoutPadding, - itemView.top.toFloat() + 8, - itemView.right.toFloat() - 4, - itemView.bottom.toFloat() - 8 + itemView.right - buttonWidthWithoutPadding, + itemView.top.toFloat() + 8, + itemView.right.toFloat() - 4, + itemView.bottom.toFloat() - 8 ) p.color = ContextCompat.getColor(itemView.context, R.color.colorAccent) c.drawRoundRect(rightButton, corners, corners, p) @@ -260,4 +264,4 @@ class SwipeController(private val buttonsActions: SwipeControllerActions) : fun onDraw(c: Canvas) { currentViewHolder?.let { drawButtons(c, it) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/utils/ui/WindmillView.kt b/app/src/main/java/com/hoc/weatherapp/utils/ui/WindmillView.kt index ca700eb..778f333 100644 --- a/app/src/main/java/com/hoc/weatherapp/utils/ui/WindmillView.kt +++ b/app/src/main/java/com/hoc/weatherapp/utils/ui/WindmillView.kt @@ -44,4 +44,4 @@ class WindmillView @JvmOverloads constructor( private const val MAX_ANIM_DURATION = 10_000 private const val MAX_WIND_SPEED = 20.0 } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/utils/ui/ZoomOutPageTransformer.kt b/app/src/main/java/com/hoc/weatherapp/utils/ui/ZoomOutPageTransformer.kt index b20582b..c839d3f 100644 --- a/app/src/main/java/com/hoc/weatherapp/utils/ui/ZoomOutPageTransformer.kt +++ b/app/src/main/java/com/hoc/weatherapp/utils/ui/ZoomOutPageTransformer.kt @@ -31,7 +31,7 @@ class ZoomOutPageTransformer : ViewPager.PageTransformer { // Fade the page relative to its size. alpha = MIN_ALPHA + (scaleFactor - MIN_SCALE) / (1 - MIN_SCALE) * - (1 - MIN_ALPHA) + (1 - MIN_ALPHA) } } else -> // (1,+Infinity] @@ -44,4 +44,4 @@ class ZoomOutPageTransformer : ViewPager.PageTransformer { private const val MIN_SCALE = 0.85f private const val MIN_ALPHA = 0.5f } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/utils/ui/rxbinding.kt b/app/src/main/java/com/hoc/weatherapp/utils/ui/rxbinding.kt index 5b1c626..d40af8a 100644 --- a/app/src/main/java/com/hoc/weatherapp/utils/ui/rxbinding.kt +++ b/app/src/main/java/com/hoc/weatherapp/utils/ui/rxbinding.kt @@ -12,9 +12,9 @@ fun checkMainThread(observer: Observer<*>): Boolean { if (Looper.myLooper() != Looper.getMainLooper()) { observer.onSubscribe(Disposables.empty()) observer.onError( - IllegalStateException( - "Expected to be called on the main thread but was ${Thread.currentThread().name}" - ) + IllegalStateException( + "Expected to be called on the main thread but was ${Thread.currentThread().name}" + ) ) return false } @@ -27,7 +27,7 @@ fun MaterialSearchView.textChanges(): Observable { } internal class MaterialSearchViewObservable(private val view: MaterialSearchView) : - Observable() { + Observable() { override fun subscribeActual(observer: Observer) { if (!checkMainThread(observer)) { return @@ -39,10 +39,10 @@ internal class MaterialSearchViewObservable(private val view: MaterialSearchView } private class Listener( - private val view: MaterialSearchView, - private val observer: Observer + private val view: MaterialSearchView, + private val observer: Observer ) : - MainThreadDisposable(), MaterialSearchView.OnQueryTextListener { + MainThreadDisposable(), MaterialSearchView.OnQueryTextListener { override fun onQueryTextChange(newText: String?): Boolean { return newText?.let { if (!isDisposed) { @@ -56,4 +56,4 @@ internal class MaterialSearchViewObservable(private val view: MaterialSearchView override fun onDispose() = view.setOnQueryTextListener(null) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/worker/UpdateCurrentWeatherWorker.kt b/app/src/main/java/com/hoc/weatherapp/worker/UpdateCurrentWeatherWorker.kt index 593fb13..24f6ca9 100644 --- a/app/src/main/java/com/hoc/weatherapp/worker/UpdateCurrentWeatherWorker.kt +++ b/app/src/main/java/com/hoc/weatherapp/worker/UpdateCurrentWeatherWorker.kt @@ -1,6 +1,5 @@ package com.hoc.weatherapp.worker -import android.app.Application import android.content.Context import androidx.work.RxWorker import androidx.work.WorkerParameters @@ -20,8 +19,8 @@ import org.koin.core.component.inject import org.threeten.bp.LocalDateTime class UpdateCurrentWeatherWorker( - context: Context, - workerParams: WorkerParameters + context: Context, + workerParams: WorkerParameters ) : RxWorker(context, workerParams), KoinComponent { private val currentWeatherRepository by inject() @@ -34,23 +33,23 @@ class UpdateCurrentWeatherWorker( @ExperimentalStdlibApi override fun createWork(): Single { return currentWeatherRepository - .refreshCurrentWeatherOfSelectedCity() - .doOnSubscribe { debug("[RUNNING] doWork ${LocalDateTime.now()}", TAG) } - .doOnSuccess { - debug("[SUCCESS] doWork $it", TAG) - applicationContext.showNotificationIfEnabled(it, settingPreferences) - } - .doOnError { - debug("[FAILURE] doWork $it", TAG) + .refreshCurrentWeatherOfSelectedCity() + .doOnSubscribe { debug("[RUNNING] doWork ${LocalDateTime.now()}", TAG) } + .doOnSuccess { + debug("[SUCCESS] doWork $it", TAG) + applicationContext.showNotificationIfEnabled(it, settingPreferences) + } + .doOnError { + debug("[FAILURE] doWork $it", TAG) - if (it is NoSelectedCityException) { - debug("[FAILURE] cancel work request and notification", TAG) - applicationContext.cancelNotificationById(WEATHER_NOTIFICATION_ID) - cancelUpdateCurrentWeatherWorkRequest() - } + if (it is NoSelectedCityException) { + debug("[FAILURE] cancel work request and notification", TAG) + applicationContext.cancelNotificationById(WEATHER_NOTIFICATION_ID) + cancelUpdateCurrentWeatherWorkRequest() } - .map { Result.success(workDataOf(RESULT to "Update current success")) } - .onErrorReturn { Result.failure(workDataOf(RESULT to "Update current failure: ${it.message}")) } + } + .map { Result.success(workDataOf(RESULT to "Update current success")) } + .onErrorReturn { Result.failure(workDataOf(RESULT to "Update current failure: ${it.message}")) } } companion object { @@ -58,4 +57,4 @@ class UpdateCurrentWeatherWorker( const val TAG = "com.hoc.weatherapp.worker.UpdateCurrentWeatherWorker" private const val RESULT = "RESULT" } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/worker/UpdateDailyWeatherWorker.kt b/app/src/main/java/com/hoc/weatherapp/worker/UpdateDailyWeatherWorker.kt index fc0c31d..51c9d9c 100644 --- a/app/src/main/java/com/hoc/weatherapp/worker/UpdateDailyWeatherWorker.kt +++ b/app/src/main/java/com/hoc/weatherapp/worker/UpdateDailyWeatherWorker.kt @@ -1,6 +1,5 @@ package com.hoc.weatherapp.worker -import android.app.Application import android.content.Context import androidx.work.RxWorker import androidx.work.WorkerParameters @@ -18,8 +17,8 @@ import org.koin.core.component.inject import org.threeten.bp.LocalDateTime class UpdateDailyWeatherWorker( - context: Context, - workerParams: WorkerParameters + context: Context, + workerParams: WorkerParameters ) : RxWorker(context, workerParams), KoinComponent { private val fiveDayForecastRepository by inject() @@ -29,20 +28,20 @@ class UpdateDailyWeatherWorker( override fun createWork(): Single { return fiveDayForecastRepository - .refreshFiveDayForecastOfSelectedCity() - .doOnSubscribe { debug("[RUNNING] doWork ${LocalDateTime.now()}", TAG) } - .doOnSuccess { debug("[SUCCESS] doWork $it", TAG) } - .doOnError { - debug("[FAILURE] doWork $it", TAG) + .refreshFiveDayForecastOfSelectedCity() + .doOnSubscribe { debug("[RUNNING] doWork ${LocalDateTime.now()}", TAG) } + .doOnSuccess { debug("[SUCCESS] doWork $it", TAG) } + .doOnError { + debug("[FAILURE] doWork $it", TAG) - if (it is NoSelectedCityException) { - debug("[FAILURE] cancel work request and notification", TAG) - applicationContext.cancelNotificationById(WEATHER_NOTIFICATION_ID) - cancelUpdateDailyWeatherWorkRequest() - } + if (it is NoSelectedCityException) { + debug("[FAILURE] cancel work request and notification", TAG) + applicationContext.cancelNotificationById(WEATHER_NOTIFICATION_ID) + cancelUpdateDailyWeatherWorkRequest() } - .map { Result.success(workDataOf(RESULT to "Update daily success")) } - .onErrorReturn { Result.failure(workDataOf(RESULT to "Update daily failure: ${it.message}")) } + } + .map { Result.success(workDataOf(RESULT to "Update daily success")) } + .onErrorReturn { Result.failure(workDataOf(RESULT to "Update daily failure: ${it.message}")) } } companion object { @@ -50,4 +49,4 @@ class UpdateDailyWeatherWorker( const val TAG = "com.hoc.weatherapp.worker.UpdateDailyWeatherWorker" const val RESULT = "RESULT" } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/hoc/weatherapp/worker/WorkerUtil.kt b/app/src/main/java/com/hoc/weatherapp/worker/WorkerUtil.kt index 764c146..343f3f6 100644 --- a/app/src/main/java/com/hoc/weatherapp/worker/WorkerUtil.kt +++ b/app/src/main/java/com/hoc/weatherapp/worker/WorkerUtil.kt @@ -1,6 +1,10 @@ package com.hoc.weatherapp.worker -import androidx.work.* +import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.ListenableWorker +import androidx.work.Operation +import androidx.work.PeriodicWorkRequestBuilder +import androidx.work.WorkManager import com.google.common.util.concurrent.ListenableFuture import com.hoc.weatherapp.utils.debug import java.util.concurrent.Executor @@ -27,24 +31,24 @@ object WorkerUtil { * [Operation.State.SUCCESS] state. */ private inline fun enqueuePeriodic( - tag: String, - uniqueName: String + tag: String, + uniqueName: String ): ListenableFuture { val request = PeriodicWorkRequestBuilder( - repeatInterval = 20, - repeatIntervalTimeUnit = TimeUnit.MINUTES, - flexTimeInterval = 5, - flexTimeIntervalUnit = TimeUnit.MINUTES + repeatInterval = 20, + repeatIntervalTimeUnit = TimeUnit.MINUTES, + flexTimeInterval = 5, + flexTimeIntervalUnit = TimeUnit.MINUTES ).addTag(tag).build() return WorkManager - .getInstance() - .enqueueUniquePeriodicWork( - uniqueName, - ExistingPeriodicWorkPolicy.REPLACE, - request - ) - .result + .getInstance() + .enqueueUniquePeriodicWork( + uniqueName, + ExistingPeriodicWorkPolicy.REPLACE, + request + ) + .result } /** @@ -72,8 +76,8 @@ object WorkerUtil { val tag = UpdateCurrentWeatherWorker.TAG enqueuePeriodic( - uniqueName = UpdateCurrentWeatherWorker.UNIQUE_WORK_NAME, - tag = tag + uniqueName = UpdateCurrentWeatherWorker.UNIQUE_WORK_NAME, + tag = tag ) then { debug("[SUCCESS] enqueue current", tag) } } @@ -85,8 +89,8 @@ object WorkerUtil { val tag = UpdateDailyWeatherWorker.TAG enqueuePeriodic( - uniqueName = UpdateDailyWeatherWorker.UNIQUE_WORK_NAME, - tag = tag + uniqueName = UpdateDailyWeatherWorker.UNIQUE_WORK_NAME, + tag = tag ) then { debug("[SUCCESS] enqueue daily", tag) } } diff --git a/app/src/main/res/anim/windmill.xml b/app/src/main/res/anim/windmill.xml index 5369742..b9235e1 100644 --- a/app/src/main/res/anim/windmill.xml +++ b/app/src/main/res/anim/windmill.xml @@ -6,4 +6,4 @@ android:pivotY="34.62%" android:repeatCount="infinite" android:toDegrees="360" /> - \ No newline at end of file + diff --git a/app/src/main/res/drawable/daily_weather_divider.xml b/app/src/main/res/drawable/daily_weather_divider.xml index 7fd007b..42b4d96 100644 --- a/app/src/main/res/drawable/daily_weather_divider.xml +++ b/app/src/main/res/drawable/daily_weather_divider.xml @@ -5,4 +5,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/drawable/default_bg.xml b/app/src/main/res/drawable/default_bg.xml index 05fbddf..276b059 100644 --- a/app/src/main/res/drawable/default_bg.xml +++ b/app/src/main/res/drawable/default_bg.xml @@ -6,4 +6,4 @@ android:angle="45" android:endColor="#11998e" android:startColor="#38ef7d" /> - \ No newline at end of file + diff --git a/app/src/main/res/drawable/gradient_shape.xml b/app/src/main/res/drawable/gradient_shape.xml index a078f72..9fbbf1b 100644 --- a/app/src/main/res/drawable/gradient_shape.xml +++ b/app/src/main/res/drawable/gradient_shape.xml @@ -7,4 +7,4 @@ android:endColor="@android:color/transparent" android:startColor="#2f000000" /> - \ No newline at end of file + diff --git a/app/src/main/res/drawable/ic_detail_hum.xml b/app/src/main/res/drawable/ic_detail_hum.xml index 1f47543..326a71c 100644 --- a/app/src/main/res/drawable/ic_detail_hum.xml +++ b/app/src/main/res/drawable/ic_detail_hum.xml @@ -7,4 +7,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/drawable/ic_detail_pcpn.xml b/app/src/main/res/drawable/ic_detail_pcpn.xml index 11d9a59..60da247 100644 --- a/app/src/main/res/drawable/ic_detail_pcpn.xml +++ b/app/src/main/res/drawable/ic_detail_pcpn.xml @@ -9,4 +9,4 @@ android:fillColor="#ffffff" android:pathData="M90.142,47.534c0,9.472-7.458,17.167-16.623,17.167H26.481C17.004,64.7,9.858,57.682,9.858,48.362 c0-7.282,4.332-13.624,10.76-16.103c-0.13-0.791-0.197-1.594-0.197-2.405c0-7.842,6.184-14.222,13.786-14.222 c2.737,0,5.358,0.819,7.593,2.348c3.656-6.058,10.142-9.848,17.168-9.848c11.145,0,20.21,9.354,20.21,20.853 c0,0.659-0.078,1.537-0.166,2.321C85.562,33.683,90.142,40.213,90.142,47.534z M73.947,35.475c-0.978-0.226-1.616-1.196-1.462-2.218 c0.135-0.878,0.332-2.467,0.332-3.237c0-8.385-6.609-15.206-14.731-15.206c-5.771,0-11.04,3.506-13.426,8.931 c-0.257,0.584-0.774,1.001-1.385,1.117c-0.61,0.12-1.239-0.083-1.68-0.532c-1.667-1.7-3.876-2.637-6.218-2.637 c-4.875,0-8.842,4.094-8.842,9.124c0,0.998,0.155,1.978,0.459,2.909c0.171,0.522,0.122,1.096-0.139,1.578 c-0.258,0.481-0.702,0.83-1.222,0.956c-5.172,1.268-8.785,6.011-8.785,11.534c0,6.316,4.92,11.068,11.444,11.068h43.136 c6.31,0,11.443-5.298,11.443-11.828C82.87,41.535,79.118,36.671,73.947,35.475z M27.388,71.884c0.227-1.299,1.724-2.188,3.311-2.02 c1.602,0.184,2.718,1.385,2.491,2.684l-1.514,8.691c-0.206,1.186-1.461,2.042-2.897,2.042c-0.137,0-0.274-0.009-0.414-0.022 c-1.602-0.185-2.717-1.386-2.49-2.684L27.388,71.884z M41.096,71.882c0.227-1.301,1.722-2.199,3.313-2.018 c1.602,0.186,2.716,1.387,2.489,2.686l-3.028,17.278c-0.208,1.187-1.462,2.04-2.897,2.04c-0.137,0-0.276-0.008-0.416-0.023 c-1.602-0.186-2.716-1.386-2.489-2.685L41.096,71.882z M53.366,71.882c0.228-1.301,1.714-2.199,3.313-2.018 c1.602,0.186,2.715,1.387,2.488,2.686l-3.029,17.278c-0.209,1.187-1.463,2.04-2.896,2.04c-0.138,0-0.275-0.008-0.416-0.023 c-1.603-0.186-2.716-1.386-2.489-2.685L53.366,71.882z M66.073,71.884c0.227-1.299,1.721-2.188,3.311-2.02 c1.603,0.184,2.718,1.385,2.49,2.684l-1.514,8.691c-0.207,1.186-1.461,2.042-2.896,2.042c-0.139,0-0.274-0.009-0.414-0.022 c-1.603-0.185-2.719-1.386-2.49-2.684L66.073,71.884z" tools:ignore="VectorPath" /> - \ No newline at end of file + diff --git a/app/src/main/res/drawable/ic_detail_pressure.xml b/app/src/main/res/drawable/ic_detail_pressure.xml index d0b06cf..c21c4a6 100644 --- a/app/src/main/res/drawable/ic_detail_pressure.xml +++ b/app/src/main/res/drawable/ic_detail_pressure.xml @@ -7,4 +7,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/drawable/ic_detail_visibility.xml b/app/src/main/res/drawable/ic_detail_visibility.xml index 71c9d10..982e448 100644 --- a/app/src/main/res/drawable/ic_detail_visibility.xml +++ b/app/src/main/res/drawable/ic_detail_visibility.xml @@ -13,4 +13,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/drawable/ic_humidity_black_24dp.xml b/app/src/main/res/drawable/ic_humidity_black_24dp.xml index 1728640..0147e65 100644 --- a/app/src/main/res/drawable/ic_humidity_black_24dp.xml +++ b/app/src/main/res/drawable/ic_humidity_black_24dp.xml @@ -7,4 +7,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/drawable/ic_pressure_black_24dp.xml b/app/src/main/res/drawable/ic_pressure_black_24dp.xml index c7b3d4e..f1d407b 100644 --- a/app/src/main/res/drawable/ic_pressure_black_24dp.xml +++ b/app/src/main/res/drawable/ic_pressure_black_24dp.xml @@ -7,4 +7,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/drawable/ic_sync_black_24dp.xml b/app/src/main/res/drawable/ic_sync_black_24dp.xml index 5ede6fb..472ae15 100644 --- a/app/src/main/res/drawable/ic_sync_black_24dp.xml +++ b/app/src/main/res/drawable/ic_sync_black_24dp.xml @@ -6,4 +6,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/drawable/ic_thermometer_black_24dp.xml b/app/src/main/res/drawable/ic_thermometer_black_24dp.xml index fa3f0a1..bb3d049 100644 --- a/app/src/main/res/drawable/ic_thermometer_black_24dp.xml +++ b/app/src/main/res/drawable/ic_thermometer_black_24dp.xml @@ -7,4 +7,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/drawable/ic_thermometer_white_24dp.xml b/app/src/main/res/drawable/ic_thermometer_white_24dp.xml index 854d0a8..c8d91d1 100644 --- a/app/src/main/res/drawable/ic_thermometer_white_24dp.xml +++ b/app/src/main/res/drawable/ic_thermometer_white_24dp.xml @@ -7,4 +7,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/drawable/ic_water_black_24dp.xml b/app/src/main/res/drawable/ic_water_black_24dp.xml index ae73bda..a7ecfb9 100644 --- a/app/src/main/res/drawable/ic_water_black_24dp.xml +++ b/app/src/main/res/drawable/ic_water_black_24dp.xml @@ -7,4 +7,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/drawable/ic_water_white_24dp.xml b/app/src/main/res/drawable/ic_water_white_24dp.xml index 571d67c..97a958e 100644 --- a/app/src/main/res/drawable/ic_water_white_24dp.xml +++ b/app/src/main/res/drawable/ic_water_white_24dp.xml @@ -7,4 +7,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/drawable/ic_windmill_blade.xml b/app/src/main/res/drawable/ic_windmill_blade.xml index 4f66f2d..07383ef 100644 --- a/app/src/main/res/drawable/ic_windmill_blade.xml +++ b/app/src/main/res/drawable/ic_windmill_blade.xml @@ -7,4 +7,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/drawable/ic_windmill_column.xml b/app/src/main/res/drawable/ic_windmill_column.xml index 0cba564..422eab1 100644 --- a/app/src/main/res/drawable/ic_windmill_column.xml +++ b/app/src/main/res/drawable/ic_windmill_column.xml @@ -7,4 +7,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/drawable/ic_windy_black_24dp.xml b/app/src/main/res/drawable/ic_windy_black_24dp.xml index f7123d6..5c06e57 100644 --- a/app/src/main/res/drawable/ic_windy_black_24dp.xml +++ b/app/src/main/res/drawable/ic_windy_black_24dp.xml @@ -7,4 +7,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/drawable/ic_windy_white_24dp.xml b/app/src/main/res/drawable/ic_windy_white_24dp.xml index 19922b5..06f3846 100644 --- a/app/src/main/res/drawable/ic_windy_white_24dp.xml +++ b/app/src/main/res/drawable/ic_windy_white_24dp.xml @@ -7,4 +7,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/drawable/splash_background.xml b/app/src/main/res/drawable/splash_background.xml index 293e20e..c5c86d1 100644 --- a/app/src/main/res/drawable/splash_background.xml +++ b/app/src/main/res/drawable/splash_background.xml @@ -9,4 +9,4 @@ android:src="@drawable/splash_icon" /> - \ No newline at end of file + diff --git a/app/src/main/res/drawable/vertial_scrollbar_thumb.xml b/app/src/main/res/drawable/vertial_scrollbar_thumb.xml index 8ea63ba..97538f9 100644 --- a/app/src/main/res/drawable/vertial_scrollbar_thumb.xml +++ b/app/src/main/res/drawable/vertial_scrollbar_thumb.xml @@ -8,4 +8,4 @@ android:width="4dp" android:height="48dp" /> - \ No newline at end of file + diff --git a/app/src/main/res/drawable/white_background_ripple.xml b/app/src/main/res/drawable/white_background_ripple.xml index 8c4e7c5..2b3ce7b 100644 --- a/app/src/main/res/drawable/white_background_ripple.xml +++ b/app/src/main/res/drawable/white_background_ripple.xml @@ -8,4 +8,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/layout/activity_add_city.xml b/app/src/main/res/layout/activity_add_city.xml index 08b2db6..42d509d 100644 --- a/app/src/main/res/layout/activity_add_city.xml +++ b/app/src/main/res/layout/activity_add_city.xml @@ -65,4 +65,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/layout/activity_cities.xml b/app/src/main/res/layout/activity_cities.xml index 941db4d..0f32c10 100644 --- a/app/src/main/res/layout/activity_cities.xml +++ b/app/src/main/res/layout/activity_cities.xml @@ -55,4 +55,4 @@ app:backgroundTint="?attr/colorSecondary" app:elevation="6dp" app:fabSize="normal" /> - \ No newline at end of file + diff --git a/app/src/main/res/layout/activity_detail_daily_weather.xml b/app/src/main/res/layout/activity_detail_daily_weather.xml index 545c18b..ef73b5a 100644 --- a/app/src/main/res/layout/activity_detail_daily_weather.xml +++ b/app/src/main/res/layout/activity_detail_daily_weather.xml @@ -131,4 +131,4 @@ tools:listitem="@layout/detail_item_layout" /> - \ No newline at end of file + diff --git a/app/src/main/res/layout/activity_live_weather.xml b/app/src/main/res/layout/activity_live_weather.xml index 58a1647..a964cdb 100644 --- a/app/src/main/res/layout/activity_live_weather.xml +++ b/app/src/main/res/layout/activity_live_weather.xml @@ -48,4 +48,4 @@ app:layout_constraintTop_toBottomOf="@+id/toolbar" /> - \ No newline at end of file + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 1b54935..e719603 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -81,4 +81,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/layout/activity_map.xml b/app/src/main/res/layout/activity_map.xml index bc46071..0b4eeff 100644 --- a/app/src/main/res/layout/activity_map.xml +++ b/app/src/main/res/layout/activity_map.xml @@ -19,4 +19,4 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:menu="@menu/menu_bottom_nav_map" /> - \ No newline at end of file + diff --git a/app/src/main/res/layout/city_item_layout.xml b/app/src/main/res/layout/city_item_layout.xml index a01d4e4..bcdb95b 100644 --- a/app/src/main/res/layout/city_item_layout.xml +++ b/app/src/main/res/layout/city_item_layout.xml @@ -117,4 +117,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/layout/daily_weather_header_layout.xml b/app/src/main/res/layout/daily_weather_header_layout.xml index 332bad1..6062685 100644 --- a/app/src/main/res/layout/daily_weather_header_layout.xml +++ b/app/src/main/res/layout/daily_weather_header_layout.xml @@ -20,4 +20,4 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:text="20/08/2018 09:00" /> - \ No newline at end of file + diff --git a/app/src/main/res/layout/daily_weather_item_layout.xml b/app/src/main/res/layout/daily_weather_item_layout.xml index 597b4ad..e6f04c2 100644 --- a/app/src/main/res/layout/daily_weather_item_layout.xml +++ b/app/src/main/res/layout/daily_weather_item_layout.xml @@ -94,4 +94,4 @@ app:layout_constraintTop_toBottomOf="@+id/text_temp_max" tools:text="20℃" /> - \ No newline at end of file + diff --git a/app/src/main/res/layout/detail_item_layout.xml b/app/src/main/res/layout/detail_item_layout.xml index 9e0c61a..2ac989f 100644 --- a/app/src/main/res/layout/detail_item_layout.xml +++ b/app/src/main/res/layout/detail_item_layout.xml @@ -37,4 +37,4 @@ app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toEndOf="@+id/imageView5" app:layout_constraintTop_toTopOf="parent" /> - \ No newline at end of file + diff --git a/app/src/main/res/layout/fragment_chart.xml b/app/src/main/res/layout/fragment_chart.xml index 9bb39e8..5d146c9 100644 --- a/app/src/main/res/layout/fragment_chart.xml +++ b/app/src/main/res/layout/fragment_chart.xml @@ -200,4 +200,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/layout/fragment_current_weather.xml b/app/src/main/res/layout/fragment_current_weather.xml index a650977..303ad55 100644 --- a/app/src/main/res/layout/fragment_current_weather.xml +++ b/app/src/main/res/layout/fragment_current_weather.xml @@ -541,5 +541,3 @@ - - diff --git a/app/src/main/res/layout/fragment_daily_weather.xml b/app/src/main/res/layout/fragment_daily_weather.xml index 813d4b1..5a32129 100644 --- a/app/src/main/res/layout/fragment_daily_weather.xml +++ b/app/src/main/res/layout/fragment_daily_weather.xml @@ -16,4 +16,4 @@ android:scrollbarThumbVertical="@drawable/vertial_scrollbar_thumb" android:scrollbars="vertical" tools:listitem="@layout/daily_weather_header_layout" /> - \ No newline at end of file + diff --git a/app/src/main/res/layout/some_city_layout.xml b/app/src/main/res/layout/some_city_layout.xml index d2126ac..54486c3 100644 --- a/app/src/main/res/layout/some_city_layout.xml +++ b/app/src/main/res/layout/some_city_layout.xml @@ -58,4 +58,4 @@ app:layout_constraintStart_toStartOf="@id/button_my_loction" app:layout_constraintTop_toTopOf="@id/button_my_loction" /> - \ No newline at end of file + diff --git a/app/src/main/res/layout/splash_demo.xml b/app/src/main/res/layout/splash_demo.xml index 38bf2ea..08ebe63 100644 --- a/app/src/main/res/layout/splash_demo.xml +++ b/app/src/main/res/layout/splash_demo.xml @@ -4,6 +4,6 @@ android:layout_height="match_parent" android:background="@drawable/splash_background" android:orientation="vertical"> - - \ No newline at end of file + + diff --git a/app/src/main/res/layout/windmill_layout.xml b/app/src/main/res/layout/windmill_layout.xml index 434e2a4..3c1f4fc 100644 --- a/app/src/main/res/layout/windmill_layout.xml +++ b/app/src/main/res/layout/windmill_layout.xml @@ -14,4 +14,4 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_windmill_column" /> - \ No newline at end of file + diff --git a/app/src/main/res/menu/menu_bottom_nav_map.xml b/app/src/main/res/menu/menu_bottom_nav_map.xml index c188118..e6fed3b 100644 --- a/app/src/main/res/menu/menu_bottom_nav_map.xml +++ b/app/src/main/res/menu/menu_bottom_nav_map.xml @@ -16,4 +16,4 @@ android:id="@+id/temperature_map" android:icon="@drawable/ic_thermometer_white_24dp" android:title="@string/temperature" /> - \ No newline at end of file + diff --git a/app/src/main/res/menu/menu_location.xml b/app/src/main/res/menu/menu_location.xml index 63fb3c6..b70b6ee 100644 --- a/app/src/main/res/menu/menu_location.xml +++ b/app/src/main/res/menu/menu_location.xml @@ -8,4 +8,4 @@ android:title="@string/search" app:showAsAction="always" /> - \ No newline at end of file + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 8412efd..a7bd82c 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -2,4 +2,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index 8412efd..a7bd82c 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -2,4 +2,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 8559b15..922c542 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -71,4 +71,4 @@ android:title="Petrus Nguyễn Thái Học" /> - \ No newline at end of file + diff --git a/app/src/test/java/com/hoc/weatherapp/ExampleUnitTest.kt b/app/src/test/java/com/hoc/weatherapp/ExampleUnitTest.kt index c79c136..25729cf 100644 --- a/app/src/test/java/com/hoc/weatherapp/ExampleUnitTest.kt +++ b/app/src/test/java/com/hoc/weatherapp/ExampleUnitTest.kt @@ -9,8 +9,8 @@ import org.junit.Test * See [testing documentation](http://d.android.com/tools/testing). */ class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } } diff --git a/build.gradle b/build.gradle index 386f5af..ff9388a 100644 --- a/build.gradle +++ b/build.gradle @@ -12,6 +12,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:7.3.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "com.diffplug.spotless:spotless-plugin-gradle:6.11.0" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } @@ -24,8 +25,6 @@ allprojects { maven { url 'https://jitpack.io' } jcenter() } -} -task clean(type: Delete) { - delete rootProject.buildDir + apply(from: "$rootDir/spotless.gradle") } diff --git a/spotless.gradle b/spotless.gradle new file mode 100644 index 0000000..a1c289a --- /dev/null +++ b/spotless.gradle @@ -0,0 +1,83 @@ +apply plugin: "com.diffplug.spotless" + +final Set EDITOR_CONFIG_KEYS = [ + "ij_kotlin_imports_layout", + "indent_size", + "end_of_line", + "charset", + "continuation_indent_size", + "disabled_rules" +] + +final ktlintVersion = '0.46.1' + +spotless { + kotlin { + target("**/*.kt") + + // TODO this should all come from editorconfig https://github.com/diffplug/spotless/issues/142 + final data = [ + "indent_size" : "2", + "continuation_indent_size": "4", + "ij_kotlin_imports_layout": "*", + "end_of_line" : "lf", + "charset" : "utf-8", + "disabled_rules" : [ + "experimental:package-name", + "experimental:type-parameter-list-spacing", + "experimental:comment-wrapping", + "trailing-comma", + "filename", + "annotation" + ].join(",") + ] + + ktlint(ktlintVersion) + .setUseExperimental(true) + .userData(data.subMap(data.keySet() - EDITOR_CONFIG_KEYS)) + .editorConfigOverride(data.subMap(EDITOR_CONFIG_KEYS)) + + trimTrailingWhitespace() + indentWithSpaces() + endWithNewline() + } + + format("xml") { + target("**/res/**/*.xml") + + trimTrailingWhitespace() + indentWithSpaces() + endWithNewline() + } + + groovyGradle { + target("*.gradle") + + trimTrailingWhitespace() + indentWithSpaces() + endWithNewline() + } + + kotlinGradle { + target("**/*.gradle.kts", "*.gradle.kts") + + // TODO this should all come from editorconfig https://github.com/diffplug/spotless/issues/142 + final data = [ + "indent_size" : "2", + "continuation_indent_size": "4", + "ij_kotlin_imports_layout": "*", + "end_of_line" : "lf", + "charset" : "utf-8", + "disabled_rules" : "no-wildcard-imports", + ] + + ktlint(ktlintVersion) + .setUseExperimental(true) + .userData(data.subMap(data.keySet() - EDITOR_CONFIG_KEYS)) + .editorConfigOverride(data.subMap(EDITOR_CONFIG_KEYS)) + + trimTrailingWhitespace() + indentWithSpaces() + endWithNewline() + } +}