├── domain
├── .gitignore
├── src
│ ├── main
│ │ └── kotlin
│ │ │ └── com
│ │ │ └── antyzero
│ │ │ └── smoksmog
│ │ │ ├── storage
│ │ │ ├── PersistentStorage.kt
│ │ │ ├── Storage.kt
│ │ │ └── model
│ │ │ │ ├── Module.kt
│ │ │ │ └── Item.kt
│ │ │ ├── model
│ │ │ └── Page.kt
│ │ │ ├── location
│ │ │ ├── LocationProvider.kt
│ │ │ └── SimpleLocationProvider.kt
│ │ │ └── SmokSmog.kt
│ ├── integrationTest
│ │ └── kotlin
│ │ │ └── com
│ │ │ └── antyzero
│ │ │ └── smoksmog
│ │ │ ├── StringRandom.kt
│ │ │ ├── api
│ │ │ └── ApiIntegrationTest.kt
│ │ │ └── DataCollectionTest.kt
│ └── test
│ │ └── kotlin
│ │ └── com
│ │ └── antyzero
│ │ └── smoksmog
│ │ ├── SmokSmogTest.kt
│ │ └── storage
│ │ └── PersistentStorageTest.kt
└── build.gradle
├── android
├── .gitignore
├── src
│ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── res
│ │ │ ├── mipmap-hdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── drawable
│ │ │ │ └── gradient_pollution.xml
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ └── colors.xml
│ │ │ └── values-pl
│ │ │ │ └── strings.xml
│ │ └── kotlin
│ │ │ └── smoksmog
│ │ │ ├── air
│ │ │ ├── ValueCheck.kt
│ │ │ ├── AirQualityIndex.kt
│ │ │ └── AirQuality.kt
│ │ │ └── logger
│ │ │ ├── Logger.kt
│ │ │ ├── SilentLogger.kt
│ │ │ ├── AndroidLogger.kt
│ │ │ ├── LevelBlockingLogger.kt
│ │ │ └── AggregatingLogger.kt
│ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── antyzero
│ │ │ └── smoksmog
│ │ │ └── android
│ │ │ └── ApplicationTest.java
│ └── test
│ │ └── java
│ │ └── smoksmog
│ │ └── air
│ │ └── AirQualityIndexTest.kt
├── proguard-rules.pro
└── build.gradle
├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── kotlin
│ │ │ ├── error
│ │ │ │ └── NonExistentClass.kt
│ │ │ └── com
│ │ │ │ ├── antyzero
│ │ │ │ └── smoksmog
│ │ │ │ │ ├── dsl
│ │ │ │ │ ├── AnyExtension.kt
│ │ │ │ │ ├── RxExtension.kt
│ │ │ │ │ ├── ViewExtension.kt
│ │ │ │ │ ├── RemoteViewsExtension.kt
│ │ │ │ │ ├── PreferenceExtension.kt
│ │ │ │ │ ├── ViewHolderExtension.kt
│ │ │ │ │ ├── DependencyInjectionExtension.kt
│ │ │ │ │ ├── ContextExtension.kt
│ │ │ │ │ ├── ActivityExtension.kt
│ │ │ │ │ ├── CompatExtension.kt
│ │ │ │ │ ├── DimenUtils.kt
│ │ │ │ │ └── DialogFramgmentExtension.kt
│ │ │ │ │ ├── changelog
│ │ │ │ │ ├── model
│ │ │ │ │ │ ├── ChangeType.kt
│ │ │ │ │ │ ├── Changelog.kt
│ │ │ │ │ │ ├── Change.kt
│ │ │ │ │ │ └── Version.kt
│ │ │ │ │ └── ChangelogReader.kt
│ │ │ │ │ ├── i18n
│ │ │ │ │ ├── time
│ │ │ │ │ │ ├── Countdown.kt
│ │ │ │ │ │ ├── CountdownProvider.kt
│ │ │ │ │ │ ├── EnglishCountdown.kt
│ │ │ │ │ │ └── PolishCountdown.kt
│ │ │ │ │ ├── LocaleProvider.kt
│ │ │ │ │ ├── ContextLocaleProvider.kt
│ │ │ │ │ └── I18nModule.kt
│ │ │ │ │ ├── ui
│ │ │ │ │ ├── screen
│ │ │ │ │ │ ├── start
│ │ │ │ │ │ │ ├── TitleProvider.kt
│ │ │ │ │ │ │ ├── item
│ │ │ │ │ │ │ │ ├── ViewDelegate.kt
│ │ │ │ │ │ │ │ ├── ListViewHolder.kt
│ │ │ │ │ │ │ │ ├── AirQualityViewDelegate.kt
│ │ │ │ │ │ │ │ └── ParticulateViewDelegate.kt
│ │ │ │ │ │ │ ├── fragment
│ │ │ │ │ │ │ │ ├── LocationStationFragmentComponent.kt
│ │ │ │ │ │ │ │ └── NetworkStationFragment.kt
│ │ │ │ │ │ │ ├── PageSave.kt
│ │ │ │ │ │ │ └── StationSlideAdapter.kt
│ │ │ │ │ │ ├── FragmentModule.kt
│ │ │ │ │ │ ├── SupportFragmentModule.kt
│ │ │ │ │ │ ├── order
│ │ │ │ │ │ │ ├── ItemTouchHelperAdapter.kt
│ │ │ │ │ │ │ ├── OnStartDragListener.kt
│ │ │ │ │ │ │ ├── OrderItemViewHolder.kt
│ │ │ │ │ │ │ └── SimpleItemTouchHelperCallback.kt
│ │ │ │ │ │ ├── about
│ │ │ │ │ │ │ ├── AboutActivityComponent.kt
│ │ │ │ │ │ │ └── AboutActivity.kt
│ │ │ │ │ │ ├── FragmentComponent.kt
│ │ │ │ │ │ ├── history
│ │ │ │ │ │ │ └── HistoryAdapter.kt
│ │ │ │ │ │ ├── ActivityComponent.kt
│ │ │ │ │ │ ├── ActivityModule.kt
│ │ │ │ │ │ ├── settings
│ │ │ │ │ │ │ └── SettingsActivity.kt
│ │ │ │ │ │ └── SimpleStationAdapter.kt
│ │ │ │ │ ├── BasePreferenceFragment.kt
│ │ │ │ │ ├── typeface
│ │ │ │ │ │ └── TypefaceProvider.kt
│ │ │ │ │ ├── widget
│ │ │ │ │ │ ├── WidgetModule.kt
│ │ │ │ │ │ ├── StationWidgetData.kt
│ │ │ │ │ │ ├── StationWidget.kt
│ │ │ │ │ │ └── StationWidgetService.kt
│ │ │ │ │ ├── BaseFragment.kt
│ │ │ │ │ ├── dialog
│ │ │ │ │ │ ├── AirQualityDialog.kt
│ │ │ │ │ │ ├── BaseDialog.kt
│ │ │ │ │ │ ├── FacebookDialog.kt
│ │ │ │ │ │ ├── InfoDialog.kt
│ │ │ │ │ │ └── AboutDialog.kt
│ │ │ │ │ └── BaseActivity.kt
│ │ │ │ │ ├── firebase
│ │ │ │ │ ├── SmokSmogFirebaseMessagingService.kt
│ │ │ │ │ ├── SmokSmogFirebaseInstanceIdService.kt
│ │ │ │ │ └── FirebaseEvents.kt
│ │ │ │ │ ├── eventbus
│ │ │ │ │ ├── EventBusModule.kt
│ │ │ │ │ └── RxBus.kt
│ │ │ │ │ ├── user
│ │ │ │ │ ├── UserModule.kt
│ │ │ │ │ └── User.kt
│ │ │ │ │ ├── settings
│ │ │ │ │ ├── SettingsModule.kt
│ │ │ │ │ └── Percent.kt
│ │ │ │ │ ├── error
│ │ │ │ │ ├── ErrorReporter.kt
│ │ │ │ │ └── SnackBarErrorReporter.kt
│ │ │ │ │ ├── fabric
│ │ │ │ │ ├── StationShowEvent.kt
│ │ │ │ │ └── FabricModule.kt
│ │ │ │ │ ├── job
│ │ │ │ │ ├── JobModule.kt
│ │ │ │ │ └── SmokSmogJobService.kt
│ │ │ │ │ ├── google
│ │ │ │ │ └── GoogleModule.kt
│ │ │ │ │ ├── tracking
│ │ │ │ │ └── Tracking.kt
│ │ │ │ │ ├── permission
│ │ │ │ │ └── PermissionHelper.kt
│ │ │ │ │ ├── logger
│ │ │ │ │ └── LoggerModule.kt
│ │ │ │ │ ├── ApplicationModule.kt
│ │ │ │ │ ├── utils
│ │ │ │ │ └── TextUtils.kt
│ │ │ │ │ └── ApplicationComponent.kt
│ │ │ │ └── firebase
│ │ │ │ └── jobdispatcher
│ │ │ │ └── TriggerConfigurator.kt
│ │ ├── assets
│ │ │ └── fonts
│ │ │ │ ├── Lato-Light.ttf
│ │ │ │ ├── Roboto-Thin.ttf
│ │ │ │ ├── RobotoCondensed-Light.ttf
│ │ │ │ └── RobotoCondensed-Regular.ttf
│ │ └── res
│ │ │ ├── drawable-hdpi
│ │ │ ├── smoksmog.png
│ │ │ ├── ic_add_white_48dp.png
│ │ │ ├── ic_info_black_24dp.png
│ │ │ ├── ic_menu_white_24dp.png
│ │ │ ├── ic_sync_black_24dp.png
│ │ │ ├── ic_refresh_white_48dp.png
│ │ │ ├── ic_search_white_24dp.png
│ │ │ ├── ic_timeline_white_36dp.png
│ │ │ ├── ic_info_outline_white_36dp.png
│ │ │ ├── ic_my_location_white_48dp.png
│ │ │ └── ic_notifications_black_24dp.png
│ │ │ ├── drawable-mdpi
│ │ │ ├── smoksmog.png
│ │ │ ├── ic_add_white_48dp.png
│ │ │ ├── ic_info_black_24dp.png
│ │ │ ├── ic_menu_white_24dp.png
│ │ │ ├── ic_sync_black_24dp.png
│ │ │ ├── ic_refresh_white_48dp.png
│ │ │ ├── ic_search_white_24dp.png
│ │ │ ├── ic_timeline_white_36dp.png
│ │ │ ├── ic_info_outline_white_36dp.png
│ │ │ ├── ic_my_location_white_48dp.png
│ │ │ └── ic_notifications_black_24dp.png
│ │ │ ├── drawable-nodpi
│ │ │ └── facebook.png
│ │ │ ├── drawable-xhdpi
│ │ │ ├── smoksmog.png
│ │ │ ├── ic_add_white_48dp.png
│ │ │ ├── ic_info_black_24dp.png
│ │ │ ├── ic_menu_white_24dp.png
│ │ │ ├── ic_search_white_24dp.png
│ │ │ ├── ic_sync_black_24dp.png
│ │ │ ├── ic_refresh_white_48dp.png
│ │ │ ├── ic_timeline_white_36dp.png
│ │ │ ├── ic_my_location_white_48dp.png
│ │ │ ├── ic_info_outline_white_36dp.png
│ │ │ └── ic_notifications_black_24dp.png
│ │ │ ├── drawable-xxhdpi
│ │ │ ├── smoksmog.png
│ │ │ ├── ic_add_white_48dp.png
│ │ │ ├── ic_info_black_24dp.png
│ │ │ ├── ic_menu_white_24dp.png
│ │ │ ├── ic_sync_black_24dp.png
│ │ │ ├── ic_refresh_white_48dp.png
│ │ │ ├── ic_search_white_24dp.png
│ │ │ ├── ic_timeline_white_36dp.png
│ │ │ ├── ic_info_outline_white_36dp.png
│ │ │ ├── ic_my_location_white_48dp.png
│ │ │ └── ic_notifications_black_24dp.png
│ │ │ ├── drawable-xxxhdpi
│ │ │ ├── smoksmog.png
│ │ │ ├── ic_add_white_48dp.png
│ │ │ ├── ic_info_black_24dp.png
│ │ │ ├── ic_menu_white_24dp.png
│ │ │ ├── ic_sync_black_24dp.png
│ │ │ ├── ic_search_white_24dp.png
│ │ │ ├── ic_refresh_white_48dp.png
│ │ │ ├── ic_timeline_white_36dp.png
│ │ │ ├── ic_my_location_white_48dp.png
│ │ │ ├── ic_info_outline_white_36dp.png
│ │ │ └── ic_notifications_black_24dp.png
│ │ │ ├── values-land
│ │ │ └── styles.xml
│ │ │ ├── layout
│ │ │ ├── dialog_toolbar.xml
│ │ │ ├── view_recyclerview.xml
│ │ │ ├── item_chart.xml
│ │ │ ├── toolbar.xml
│ │ │ ├── activity_pick_station.xml
│ │ │ ├── activity_settings.xml
│ │ │ ├── activity_base.xml
│ │ │ ├── activity_history.xml
│ │ │ ├── item_order.xml
│ │ │ ├── dialog_info_air_quality.xml
│ │ │ ├── activity_order.xml
│ │ │ ├── widget_station.xml
│ │ │ └── dialog_info_facebook.xml
│ │ │ ├── values-v21
│ │ │ └── themes.xml
│ │ │ ├── values-v19
│ │ │ └── themes.xml
│ │ │ ├── drawable
│ │ │ ├── triangle.xml
│ │ │ ├── shape_oval.xml
│ │ │ ├── shape_oval_iron.xml
│ │ │ ├── ic_signal_cellular_4_bar_black_24dp.xml
│ │ │ ├── shape_oval_iron_border.xml
│ │ │ └── ic_share_white_24dp.xml
│ │ │ ├── values-w820dp
│ │ │ └── dimens.xml
│ │ │ ├── drawable-v21
│ │ │ ├── ic_info_black_24dp.xml
│ │ │ ├── ic_notifications_black_24dp.xml
│ │ │ └── ic_sync_black_24dp.xml
│ │ │ ├── menu
│ │ │ ├── pick_station.xml
│ │ │ └── main.xml
│ │ │ ├── values
│ │ │ ├── constants.xml
│ │ │ ├── themes.xml
│ │ │ ├── dimens.xml
│ │ │ ├── arrays.xml
│ │ │ ├── preferences.xml
│ │ │ ├── styles.xml
│ │ │ └── about.xml
│ │ │ ├── xml
│ │ │ ├── widget_station.xml
│ │ │ ├── settings_old_general.xml
│ │ │ └── settings_general.xml
│ │ │ └── values-pl
│ │ │ └── about.xml
│ ├── test
│ │ ├── kotlin
│ │ │ └── com
│ │ │ │ └── antyzero
│ │ │ │ └── smoksmog
│ │ │ │ ├── dumb.kt
│ │ │ │ └── changelog
│ │ │ │ └── ChangelogReaderTest.kt
│ │ └── resources
│ │ │ └── changelog_simple.json
│ ├── debug
│ │ ├── res
│ │ │ └── values
│ │ │ │ └── constants.xml
│ │ └── AndroidManifest.xml
│ └── androidTest
│ │ ├── java
│ │ ├── com
│ │ │ └── antyzero
│ │ │ │ └── smoksmog
│ │ │ │ ├── utils
│ │ │ │ └── Resources.java
│ │ │ │ ├── rules
│ │ │ │ ├── rx
│ │ │ │ │ └── SchedulersHook.java
│ │ │ │ ├── SpoonRule.java
│ │ │ │ └── RxSchedulerTestRule.java
│ │ │ │ ├── CustomTestRunner.java
│ │ │ │ ├── screen
│ │ │ │ ├── HistoryActivityTestRule.java
│ │ │ │ ├── SettingsActivityTest.java
│ │ │ │ ├── HistoryActivityTest.java
│ │ │ │ └── StartActivityTest.java
│ │ │ │ └── migration
│ │ │ │ └── OldToNewStationListTest.java
│ │ └── rx
│ │ │ └── plugins
│ │ │ └── RxJavaTestPlugins.java
│ │ └── resources
│ │ ├── particulates-1.json
│ │ └── station-4.json
├── debug.keystore
└── proguard-rules.pro
├── settings.gradle
├── lint.xml
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── README.md
├── network
├── src
│ ├── main
│ │ └── kotlin
│ │ │ └── pl
│ │ │ └── malopolska
│ │ │ └── smoksmog
│ │ │ ├── model
│ │ │ ├── Description.kt
│ │ │ ├── History.kt
│ │ │ ├── Station.kt
│ │ │ ├── ParticulateEnum.kt
│ │ │ └── Particulate.kt
│ │ │ ├── ApiUtils.kt
│ │ │ ├── Api.kt
│ │ │ ├── DateTimeDeserializer.kt
│ │ │ └── utils
│ │ │ └── StationUtils.kt
│ └── test
│ │ ├── java
│ │ └── pl
│ │ │ └── malopolska
│ │ │ └── smoksmog
│ │ │ ├── model
│ │ │ └── ParticulateEnumTest.java
│ │ │ ├── RestClientTest.java
│ │ │ └── TestUtils.java
│ │ └── resources
│ │ ├── responseParticulateDescription.json
│ │ └── responseStation.json
└── build.gradle
├── .gitignore
├── gradle.properties
├── .travis.yml-disabled
└── Jenkinsfile
/domain/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | fabric.properties
3 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':network', ':android', ':domain'
2 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/error/NonExistentClass.kt:
--------------------------------------------------------------------------------
1 | package error
2 |
3 | class NonExistentClass
--------------------------------------------------------------------------------
/app/debug.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/debug.keystore
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/lint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/assets/fonts/Lato-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/assets/fonts/Lato-Light.ttf
--------------------------------------------------------------------------------
/app/src/main/assets/fonts/Roboto-Thin.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/assets/fonts/Roboto-Thin.ttf
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/smoksmog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-hdpi/smoksmog.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/smoksmog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-mdpi/smoksmog.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/facebook.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-nodpi/facebook.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/smoksmog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xhdpi/smoksmog.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/smoksmog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xxhdpi/smoksmog.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/smoksmog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xxxhdpi/smoksmog.png
--------------------------------------------------------------------------------
/app/src/test/kotlin/com/antyzero/smoksmog/dumb.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog
2 |
3 | /**
4 | * Created by iwopolanski on 26.01.2017.
5 | */
6 |
--------------------------------------------------------------------------------
/android/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/android/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/android/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/dsl/AnyExtension.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.dsl
2 |
3 |
4 | fun Any.tag(): String = this.javaClass.simpleName
--------------------------------------------------------------------------------
/android/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/android/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/android/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/assets/fonts/RobotoCondensed-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/assets/fonts/RobotoCondensed-Light.ttf
--------------------------------------------------------------------------------
/app/src/main/assets/fonts/RobotoCondensed-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/assets/fonts/RobotoCondensed-Regular.ttf
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_add_white_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-hdpi/ic_add_white_48dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_info_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-hdpi/ic_info_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_menu_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-hdpi/ic_menu_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_sync_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-hdpi/ic_sync_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_add_white_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-mdpi/ic_add_white_48dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_info_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-mdpi/ic_info_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_menu_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-mdpi/ic_menu_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_sync_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-mdpi/ic_sync_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_add_white_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xhdpi/ic_add_white_48dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_refresh_white_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-hdpi/ic_refresh_white_48dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_search_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-hdpi/ic_search_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_refresh_white_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-mdpi/ic_refresh_white_48dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_search_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-mdpi/ic_search_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_info_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xhdpi/ic_info_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_menu_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xhdpi/ic_menu_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_search_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xhdpi/ic_search_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_sync_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xhdpi/ic_sync_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_add_white_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xxhdpi/ic_add_white_48dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_info_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xxhdpi/ic_info_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_menu_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xxhdpi/ic_menu_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_sync_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xxhdpi/ic_sync_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_add_white_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xxxhdpi/ic_add_white_48dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_info_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xxxhdpi/ic_info_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_menu_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xxxhdpi/ic_menu_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_sync_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xxxhdpi/ic_sync_black_24dp.png
--------------------------------------------------------------------------------
/android/src/main/kotlin/smoksmog/air/ValueCheck.kt:
--------------------------------------------------------------------------------
1 | package smoksmog.air
2 |
3 |
4 | interface ValueCheck {
5 |
6 | fun isValueInRange(value: Double): Boolean
7 | }
8 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/changelog/model/ChangeType.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.changelog.model
2 |
3 |
4 | enum class ChangeType {
5 | FIX, NEW
6 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_timeline_white_36dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-hdpi/ic_timeline_white_36dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_timeline_white_36dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-mdpi/ic_timeline_white_36dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_refresh_white_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xhdpi/ic_refresh_white_48dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_timeline_white_36dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xhdpi/ic_timeline_white_36dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_refresh_white_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xxhdpi/ic_refresh_white_48dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_search_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xxhdpi/ic_search_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_search_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xxxhdpi/ic_search_white_24dp.png
--------------------------------------------------------------------------------
/app/src/debug/res/values/constants.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | {D}SmokSmog
4 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_info_outline_white_36dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-hdpi/ic_info_outline_white_36dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_my_location_white_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-hdpi/ic_my_location_white_48dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_info_outline_white_36dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-mdpi/ic_info_outline_white_36dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_my_location_white_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-mdpi/ic_my_location_white_48dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_my_location_white_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xhdpi/ic_my_location_white_48dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_timeline_white_36dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xxhdpi/ic_timeline_white_36dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_refresh_white_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xxxhdpi/ic_refresh_white_48dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_timeline_white_36dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xxxhdpi/ic_timeline_white_36dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_notifications_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-hdpi/ic_notifications_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_notifications_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-mdpi/ic_notifications_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_info_outline_white_36dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xhdpi/ic_info_outline_white_36dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_notifications_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xhdpi/ic_notifications_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_info_outline_white_36dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xxhdpi/ic_info_outline_white_36dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_my_location_white_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xxhdpi/ic_my_location_white_48dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_my_location_white_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xxxhdpi/ic_my_location_white_48dp.png
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/antyzero/smoksmog/utils/Resources.java:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.utils;
2 |
3 | /**
4 | *
5 | */
6 | public class Resources {
7 |
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_notifications_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xxhdpi/ic_notifications_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_info_outline_white_36dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xxxhdpi/ic_info_outline_white_36dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_notifications_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmokSmog/smoksmog-android/HEAD/app/src/main/res/drawable-xxxhdpi/ic_notifications_black_24dp.png
--------------------------------------------------------------------------------
/domain/src/main/kotlin/com/antyzero/smoksmog/storage/PersistentStorage.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.storage
2 |
3 | interface PersistentStorage : Storage {
4 |
5 | fun clear()
6 | }
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/changelog/model/Changelog.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.changelog.model
2 |
3 | data class Changelog(
4 | val versions: List = listOf())
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/i18n/time/Countdown.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.i18n.time
2 |
3 | interface Countdown {
4 |
5 | operator fun get(givenSeconds: Int): String
6 | }
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # smoksmog-android
2 |
3 | [](https://travis-ci.org/SmokSmog/smoksmog-android)
4 |
5 | SmokSmog application for Android.
6 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/i18n/LocaleProvider.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.i18n
2 |
3 | import java.util.*
4 |
5 |
6 | interface LocaleProvider {
7 |
8 | fun get(): Locale
9 | }
--------------------------------------------------------------------------------
/network/src/main/kotlin/pl/malopolska/smoksmog/model/Description.kt:
--------------------------------------------------------------------------------
1 | package pl.malopolska.smoksmog.model
2 |
3 | data class Description(
4 | val desc: String) {
5 | override fun toString() = desc
6 | }
7 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/dsl/RxExtension.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.dsl
2 |
3 | import rx.Observable
4 |
5 | /**
6 | * Rx related
7 | */
8 |
9 | fun T.observable() = Observable.just(this)
10 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/screen/start/TitleProvider.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.screen.start
2 |
3 | interface TitleProvider {
4 |
5 | val title: String
6 |
7 | val subtitle: String
8 | }
9 |
--------------------------------------------------------------------------------
/network/src/main/kotlin/pl/malopolska/smoksmog/model/History.kt:
--------------------------------------------------------------------------------
1 | package pl.malopolska.smoksmog.model
2 |
3 | import org.joda.time.LocalDate
4 |
5 | data class History(
6 | val value: Float,
7 | val date: LocalDate) {
8 | }
9 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/BasePreferenceFragment.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui
2 |
3 |
4 | import com.trello.rxlifecycle.components.RxPreferenceFragment
5 |
6 | abstract class BasePreferenceFragment : RxPreferenceFragment()
7 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/screen/FragmentModule.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.screen
2 |
3 | import android.app.Fragment
4 |
5 | import dagger.Module
6 |
7 | @Module
8 | class FragmentModule(private val fragment: Fragment)
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values-land/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
--------------------------------------------------------------------------------
/domain/src/main/kotlin/com/antyzero/smoksmog/model/Page.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.model
2 |
3 | import com.antyzero.smoksmog.storage.model.Item
4 | import pl.malopolska.smoksmog.model.Station
5 |
6 | data class Page(val item: Item, val station: Station)
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/changelog/model/Change.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.changelog.model
2 |
3 |
4 | data class Change(
5 | val type: ChangeType,
6 | val text: String,
7 | val translations: Map = mapOf())
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_toolbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/screen/SupportFragmentModule.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.screen
2 |
3 | import android.support.v4.app.Fragment
4 |
5 | import dagger.Module
6 |
7 | @Module
8 | class SupportFragmentModule(private val fragment: Fragment)
9 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/dsl/ViewExtension.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.dsl
2 |
3 | import android.support.annotation.IdRes
4 | import android.view.View
5 |
6 | @Suppress("UNCHECKED_CAST")
7 | fun View.findView(@IdRes id: Int): T = this.findViewById(id) as T
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/firebase/SmokSmogFirebaseMessagingService.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.firebase
2 |
3 | import com.google.firebase.messaging.FirebaseMessagingService
4 |
5 | class SmokSmogFirebaseMessagingService : FirebaseMessagingService() {
6 |
7 |
8 | }
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/screen/order/ItemTouchHelperAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.screen.order
2 |
3 | interface ItemTouchHelperAdapter {
4 |
5 | fun onItemMove(fromPosition: Int, toPosition: Int)
6 |
7 | fun onItemDismiss(position: Int)
8 | }
9 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/screen/order/OnStartDragListener.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.screen.order
2 |
3 | import android.support.v7.widget.RecyclerView
4 |
5 | interface OnStartDragListener {
6 |
7 | fun onStartDrag(viewHolder: RecyclerView.ViewHolder)
8 | }
9 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Sep 14 09:14:04 CEST 2016
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
7 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/dsl/RemoteViewsExtension.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.dsl
2 |
3 | import android.support.annotation.IdRes
4 | import android.widget.RemoteViews
5 |
6 | fun RemoteViews.setBackgroundColor(@IdRes viewId: Int, color: Int) = this.setInt(viewId, "setBackgroundColor", color)
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_recyclerview.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/changelog/model/Version.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.changelog.model
2 |
3 | import org.joda.time.LocalDate
4 |
5 |
6 | data class Version(
7 | val name: String,
8 | val code: Int,
9 | val date: LocalDate,
10 | val changes: List = listOf())
--------------------------------------------------------------------------------
/app/src/main/res/values-v19/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/dsl/PreferenceExtension.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.dsl
2 |
3 | import android.preference.Preference
4 | import android.preference.PreferenceFragment
5 |
6 |
7 | @Suppress("UNCHECKED_CAST")
8 | fun PreferenceFragment.findPreference(key: String): T? = this.findPreference(key) as T
--------------------------------------------------------------------------------
/app/src/main/res/drawable/triangle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/shape_oval.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/shape_oval_iron.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_chart.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/rx/plugins/RxJavaTestPlugins.java:
--------------------------------------------------------------------------------
1 | package rx.plugins;
2 |
3 | /**
4 | * Allows to access reser
5 | */
6 | public class RxJavaTestPlugins extends RxJavaPlugins {
7 |
8 | RxJavaTestPlugins() {
9 | super();
10 | }
11 |
12 | public static void resetPlugins() {
13 | getInstance().reset();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/firebase/jobdispatcher/TriggerConfigurator.kt:
--------------------------------------------------------------------------------
1 | package com.firebase.jobdispatcher
2 |
3 |
4 | class TriggerConfigurator {
5 | companion object {
6 | fun executionWindow(builder: Job.Builder, windowStart: Int, windowEnd: Int) {
7 | builder.trigger = Trigger.executionWindow(windowStart, windowEnd)
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/eventbus/EventBusModule.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.eventbus
2 |
3 | import dagger.Module
4 | import dagger.Provides
5 | import javax.inject.Singleton
6 |
7 | @Module
8 | class EventBusModule {
9 |
10 | @Provides
11 | @Singleton
12 | internal fun provideRxBus(): RxBus {
13 | return RxBus()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/screen/about/AboutActivityComponent.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.screen.about
2 |
3 | import com.antyzero.smoksmog.ui.screen.ActivityModule
4 |
5 | import dagger.Subcomponent
6 |
7 | @Subcomponent(modules = arrayOf(ActivityModule::class))
8 | interface AboutActivityComponent {
9 | fun inject(activity: AboutActivity)
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_signal_cellular_4_bar_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/toolbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/domain/src/main/kotlin/com/antyzero/smoksmog/location/LocationProvider.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.location
2 |
3 | import rx.Observable
4 |
5 | interface LocationProvider {
6 |
7 | fun location(): Observable
8 | }
9 |
10 | sealed class Location {
11 |
12 | class Position(val coordinates: Pair) : Location()
13 |
14 | class Unknown : Location()
15 | }
--------------------------------------------------------------------------------
/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/user/UserModule.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.user
2 |
3 |
4 | import android.content.Context
5 | import dagger.Module
6 | import dagger.Provides
7 | import javax.inject.Singleton
8 |
9 | @Module
10 | @Singleton
11 | class UserModule {
12 |
13 | @Provides
14 | @Singleton
15 | internal fun provideUser(context: Context): User = User(context)
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/typeface/TypefaceProvider.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.typeface
2 |
3 |
4 | import android.content.Context
5 | import android.graphics.Typeface
6 |
7 | class TypefaceProvider(context: Context) {
8 |
9 | val default: Typeface
10 |
11 | init {
12 | default = Typeface.createFromAsset(context.assets, "fonts/Lato-Light.ttf")
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/settings/SettingsModule.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.settings
2 |
3 | import android.content.Context
4 | import dagger.Module
5 | import dagger.Provides
6 | import javax.inject.Singleton
7 |
8 | @Singleton
9 | @Module
10 | class SettingsModule {
11 |
12 | @Provides
13 | @Singleton
14 | fun provideSettingsHelper(context: Context) = SettingsHelper(context)
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/screen/start/item/ViewDelegate.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.screen.start.item
2 |
3 | import android.view.ViewGroup
4 |
5 | abstract class ViewDelegate, R>(val viewType: Int) {
6 |
7 | abstract fun onCreateViewHolder(parent: ViewGroup): T
8 |
9 | fun onBindViewHolder(holder: T, data: R) {
10 | holder.bind(data)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/android/src/main/res/drawable/gradient_pollution.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/error/ErrorReporter.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.error
2 |
3 | import android.support.annotation.StringRes
4 |
5 | /**
6 | * Common interface for ui elements to report errors
7 | */
8 | interface ErrorReporter {
9 |
10 | fun report(message: String)
11 |
12 | fun report(@StringRes stringId: Int)
13 |
14 | fun report(@StringRes stringId: Int, vararg objects: Any)
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v21/ic_info_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/shape_oval_iron_border.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
10 |
11 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/fabric/StationShowEvent.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.fabric
2 |
3 | import com.crashlytics.android.answers.ContentViewEvent
4 |
5 | import pl.malopolska.smoksmog.model.Station
6 |
7 | class StationShowEvent(station: Station) : ContentViewEvent() {
8 |
9 | init {
10 | putContentId(station.id.toString())
11 | putContentName(station.name)
12 | putContentType("Station")
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/screen/start/item/ListViewHolder.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.screen.start.item
2 |
3 | import android.content.Context
4 | import android.support.v7.widget.RecyclerView
5 | import android.view.View
6 |
7 | abstract class ListViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
8 |
9 | val context: Context = itemView.context
10 |
11 | open fun bind(data: T) {
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/dsl/ViewHolderExtension.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.dsl
2 |
3 | import android.support.annotation.IdRes
4 | import android.support.v7.widget.RecyclerView
5 | import android.view.View
6 |
7 | fun RecyclerView.ViewHolder.findViewById(@IdRes id: Int): View = this.itemView.findViewById(id)
8 |
9 | @Suppress("UNCHECKED_CAST")
10 | fun RecyclerView.ViewHolder.findView(@IdRes id: Int): T = this.itemView.findViewById(id) as T
--------------------------------------------------------------------------------
/domain/src/integrationTest/kotlin/com/antyzero/smoksmog/StringRandom.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog
2 |
3 | import java.util.*
4 |
5 |
6 | class StringRandom(seed: Long = System.nanoTime()) {
7 |
8 | private val LOWER_CASE_A = 97
9 | private val random = Random(seed)
10 |
11 | fun random(length: Int = 8) = (1..length).fold("") {
12 | previous, position ->
13 | previous + (LOWER_CASE_A + random.nextInt(22)).toChar()
14 | }
15 | }
--------------------------------------------------------------------------------
/domain/src/main/kotlin/com/antyzero/smoksmog/location/SimpleLocationProvider.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.location
2 |
3 | import rx.Observable
4 | import rx.subjects.BehaviorSubject
5 |
6 | class SimpleLocationProvider(initialLocation: Location = Location.Unknown()) : LocationProvider {
7 |
8 | val locationSubject: BehaviorSubject = BehaviorSubject.create(initialLocation)
9 |
10 | override fun location(): Observable = locationSubject
11 | }
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/eventbus/RxBus.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.eventbus
2 |
3 | import rx.Observable
4 | import rx.subjects.PublishSubject
5 | import rx.subjects.SerializedSubject
6 |
7 | class RxBus {
8 |
9 | private val _bus = SerializedSubject(PublishSubject.create())
10 |
11 | fun send(o: Any) {
12 | _bus.onNext(o)
13 | }
14 |
15 | fun toObserverable(): Observable {
16 | return _bus
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/widget/WidgetModule.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.widget
2 |
3 |
4 | import android.content.Context
5 | import dagger.Module
6 | import dagger.Provides
7 | import javax.inject.Singleton
8 |
9 | @Module
10 | @Singleton
11 | class WidgetModule {
12 |
13 | @Provides
14 | @Singleton
15 | fun provideStationWidgetData(context: Context): StationWidgetData {
16 | return StationWidgetData(context)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/pick_station.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/android/src/androidTest/java/com/antyzero/smoksmog/android/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.android;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/BaseFragment.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui
2 |
3 | import android.os.Bundle
4 | import android.support.annotation.CallSuper
5 | import android.view.View
6 | import com.trello.rxlifecycle.components.RxFragment
7 |
8 | abstract class BaseFragment : RxFragment() {
9 |
10 | @CallSuper
11 | override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
12 | super.onViewCreated(view, savedInstanceState)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/screen/start/fragment/LocationStationFragmentComponent.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.screen.start.fragment
2 |
3 |
4 | import com.antyzero.smoksmog.google.GoogleModule
5 | import com.antyzero.smoksmog.ui.screen.FragmentModule
6 |
7 | import dagger.Subcomponent
8 |
9 | @Subcomponent(modules = arrayOf(FragmentModule::class, GoogleModule::class))
10 | interface LocationStationFragmentComponent {
11 |
12 | fun inject(fragment: LocationStationFragment)
13 | }
14 |
--------------------------------------------------------------------------------
/network/src/main/kotlin/pl/malopolska/smoksmog/model/Station.kt:
--------------------------------------------------------------------------------
1 | package pl.malopolska.smoksmog.model
2 |
3 | import com.google.gson.annotations.SerializedName
4 | import java.util.*
5 |
6 | data class Station(
7 |
8 | val id: Long,
9 | val name: String,
10 | @SerializedName("lon") val longitude: Float = 0f,
11 | @SerializedName("lat") val latitude: Float = 0f) {
12 |
13 | val particulates: List = ArrayList()
14 |
15 | override fun toString() = name
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/androidTest/resources/particulates-1.json:
--------------------------------------------------------------------------------
1 | {
2 | "desc": "Dwutlenek siarki powstaje w wyniku spalania, zawieraj\u0105cych \u015bladowe ilo\u015bci siarki, paliw kopalnych. Wi\u0119kszo\u015b\u0107 SO\u2082 emitowana jest podczas produkcji energii elektrycznej oraz w niewielkim stopniu przez \u015brodki transportu. Wdychanie SO\u2082 mo\u017ce powodowa\u0107 choroby uk\u0142adu oddechowego. Kwas siarkowy powsta\u0142y w wyniku atmosferycznej reakcji SO\u2082 jest jednym ze sk\u0142adnik\u00f3w kwa\u015bnych deszcz\u00f3w."
3 | }
--------------------------------------------------------------------------------
/android/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | very good air quality
5 | good air quality
6 | moderate air quality
7 | sufficient air quality
8 | bad air quality
9 | very bad air quality
10 |
11 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/antyzero/smoksmog/rules/rx/SchedulersHook.java:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.rules.rx;
2 |
3 | import rx.Scheduler;
4 | import rx.plugins.RxJavaSchedulersHook;
5 |
6 |
7 | public class SchedulersHook extends RxJavaSchedulersHook {
8 |
9 | private final Scheduler scheduler;
10 |
11 | public SchedulersHook(Scheduler scheduler) {
12 | this.scheduler = scheduler;
13 | }
14 |
15 | @Override
16 | public Scheduler getNewThreadScheduler() {
17 | return scheduler;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/i18n/ContextLocaleProvider.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.i18n
2 |
3 | import android.content.Context
4 | import android.os.Build
5 | import java.util.*
6 |
7 | internal class ContextLocaleProvider(private val context: Context) : LocaleProvider {
8 |
9 | @Suppress("DEPRECATION")
10 | override fun get(): Locale = if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ){
11 | context.resources.configuration.locales[0]
12 | } else {
13 | context.resources.configuration.locale
14 | }
15 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v21/ic_notifications_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/screen/FragmentComponent.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.screen
2 |
3 | import com.antyzero.smoksmog.ui.screen.settings.GeneralSettingsFragment
4 | import com.antyzero.smoksmog.ui.screen.start.fragment.NetworkStationFragment
5 |
6 | import dagger.Subcomponent
7 |
8 | @Subcomponent(modules = arrayOf(FragmentModule::class))
9 | interface FragmentComponent {
10 |
11 | fun inject(generalSettingsFragment: GeneralSettingsFragment)
12 |
13 | fun inject(networkStationFragment: NetworkStationFragment)
14 | }
15 |
--------------------------------------------------------------------------------
/domain/src/main/kotlin/com/antyzero/smoksmog/storage/Storage.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.storage
2 |
3 | import com.antyzero.smoksmog.storage.model.Item
4 |
5 |
6 | interface Storage {
7 |
8 | fun addStation(id: Long): Boolean {
9 | return add(Item.Station(id))
10 | }
11 |
12 | fun add(item: Item): Boolean
13 |
14 | fun removeById(id: Long)
15 |
16 | fun removeAt(i: Int)
17 |
18 | fun update(id: Long, itemUpdate: Item)
19 |
20 | fun fetchAll(): List-
21 |
22 | fun set(itemCollection: Collection
- )
23 | }
--------------------------------------------------------------------------------
/android/src/main/res/values-pl/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | zła jakość powietrza
5 | dobra jakość powietrza
6 | umiarkowana jakość powietrza
7 | dostateczna jakość powietrza
8 | bardzo zła jakość powietrza
9 | bardzo dobra jakość powietrza
10 |
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 | # Files for the Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 |
15 | # Gradle files
16 | .gradle/
17 | build/
18 |
19 | # Local configuration file (sdk path, etc)
20 | local.properties
21 |
22 | # Proguard folder generated by Eclipse
23 | proguard/
24 |
25 | # Android Studio
26 | *.iml
27 | .idea/
28 |
29 | # Crashlytics
30 | crashlytics.properties
31 | crashlytics-build.properties
32 | app/src/main/res/values/com_crashlytics_export_strings.xml
33 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v21/ic_sync_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/i18n/time/CountdownProvider.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.i18n.time
2 |
3 |
4 | import com.antyzero.smoksmog.i18n.LocaleProvider
5 | import java.util.*
6 |
7 | class CountdownProvider(private val localeProvider: LocaleProvider) {
8 |
9 | operator fun get(seconds: Int): String = when (localeProvider.get()) {
10 | LOCALE_POLISH -> PolishCountdown()[seconds]
11 | else -> EnglishCountdown()[seconds]
12 | }
13 |
14 | companion object {
15 |
16 | private val LOCALE_POLISH = Locale("pl", "PL")
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/res/values/constants.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | SmokSmog
4 |
5 |
6 | Facebook
7 | Beta
8 |
9 | 24h
10 | 1h
11 |
12 | %.1f %s
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/job/JobModule.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.job
2 |
3 | import android.content.Context
4 | import com.firebase.jobdispatcher.FirebaseJobDispatcher
5 | import com.firebase.jobdispatcher.GooglePlayDriver
6 | import dagger.Module
7 | import dagger.Provides
8 | import javax.inject.Singleton
9 |
10 | @Module
11 | @Singleton
12 | class JobModule {
13 |
14 | @Provides
15 | @Singleton
16 | internal fun provideFirebaseJobDispatcher(context: Context): FirebaseJobDispatcher {
17 | return FirebaseJobDispatcher(GooglePlayDriver(context))
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/widget_station.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/network/src/test/java/pl/malopolska/smoksmog/model/ParticulateEnumTest.java:
--------------------------------------------------------------------------------
1 | package pl.malopolska.smoksmog.model;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.assertj.core.api.Assertions.assertThat;
6 |
7 | public class ParticulateEnumTest {
8 |
9 | @Test
10 | public void testNonSupportedId() throws Exception {
11 |
12 | // given
13 | long id = -34;
14 |
15 | // when
16 | ParticulateEnum result = ParticulateEnum.Companion.findById(-34);
17 |
18 | // then
19 | assertThat(result).isNotNull();
20 | assertThat(result).isEqualTo(ParticulateEnum.UNKNOWN);
21 | }
22 | }
--------------------------------------------------------------------------------
/network/src/main/kotlin/pl/malopolska/smoksmog/model/ParticulateEnum.kt:
--------------------------------------------------------------------------------
1 | package pl.malopolska.smoksmog.model
2 |
3 | enum class ParticulateEnum(val id: Long) {
4 |
5 | SO2(1), NO(2), NO2(3), CO(4), O3(5), NOx(6), PM10(7), PM25(8), C6H6(11), UNKNOWN(-1);
6 |
7 | override fun toString() = "$name {id=$id}"
8 |
9 | companion object {
10 |
11 | fun findById(id: Long): ParticulateEnum {
12 |
13 | for (particulateEnum in values()) {
14 | if (particulateEnum.id == id) {
15 | return particulateEnum
16 | }
17 | }
18 | return UNKNOWN
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/fabric/FabricModule.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.fabric
2 |
3 |
4 | import com.crashlytics.android.answers.Answers
5 | import com.crashlytics.android.core.CrashlyticsCore
6 | import dagger.Module
7 | import dagger.Provides
8 | import javax.inject.Singleton
9 |
10 | @Singleton
11 | @Module
12 | class FabricModule {
13 |
14 | @Provides
15 | @Singleton
16 | internal fun provideAnswers(): Answers {
17 | return Answers.getInstance()
18 | }
19 |
20 | @Provides
21 | @Singleton
22 | internal fun provideCrashlyticsCore(): CrashlyticsCore {
23 | return CrashlyticsCore.getInstance()
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/screen/start/item/AirQualityViewDelegate.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.screen.start.item
2 |
3 | import android.view.LayoutInflater
4 | import android.view.ViewGroup
5 |
6 | import com.antyzero.smoksmog.R
7 |
8 | import pl.malopolska.smoksmog.model.Station
9 |
10 | class AirQualityViewDelegate(viewType: Int) : ViewDelegate(viewType) {
11 |
12 | override fun onCreateViewHolder(parent: ViewGroup): AirQualityViewHolder {
13 | val layoutInflater = LayoutInflater.from(parent.context)
14 | return AirQualityViewHolder(layoutInflater.inflate(R.layout.item_air_quility, parent, false))
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/screen/start/item/ParticulateViewDelegate.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.screen.start.item
2 |
3 | import android.view.LayoutInflater
4 | import android.view.ViewGroup
5 |
6 | import com.antyzero.smoksmog.R
7 |
8 | import pl.malopolska.smoksmog.model.Particulate
9 |
10 | class ParticulateViewDelegate(viewType: Int) : ViewDelegate(viewType) {
11 |
12 | override fun onCreateViewHolder(parent: ViewGroup): ParticulateViewHolder {
13 | val inflater = LayoutInflater.from(parent.context)
14 | return ParticulateViewHolder(inflater.inflate(R.layout.item_particulate, parent, false))
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/network/src/main/kotlin/pl/malopolska/smoksmog/ApiUtils.kt:
--------------------------------------------------------------------------------
1 | package pl.malopolska.smoksmog
2 |
3 |
4 | import pl.malopolska.smoksmog.model.Particulate
5 | import rx.Observable
6 |
7 | class ApiUtils private constructor() {
8 |
9 | init {
10 | throw IllegalAccessError()
11 | }
12 |
13 | companion object {
14 |
15 | fun sortParticulates(particulates: Collection): Observable {
16 |
17 | return Observable.from(particulates)
18 | .toSortedList { first, second -> first.position - second.position }
19 | .flatMap { particulates -> Observable.from(particulates) }
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/dsl/DependencyInjectionExtension.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.dsl
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import com.antyzero.smoksmog.SmokSmogApplication
6 | import com.antyzero.smoksmog.ui.screen.ActivityModule
7 |
8 | /**
9 | * ApplicationComponent access
10 | */
11 | fun Any.appComponent(context: Context) = SmokSmogApplication[context].appComponent
12 |
13 | fun Context.appComponent() = appComponent(this)
14 |
15 | /**
16 | * ActivityComponent access
17 | */
18 | fun Any.activityComponent(activity: Activity) = appComponent(activity).plus(ActivityModule(activity))
19 |
20 | fun Activity.activityComponent() = activityComponent(this)
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
12 |
13 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/dsl/ContextExtension.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.dsl
2 |
3 | import android.appwidget.AppWidgetManager
4 | import android.content.Context
5 | import android.support.annotation.StringRes
6 | import android.widget.Toast
7 |
8 | fun Context.toast(chars: CharSequence, duration: Int = Toast.LENGTH_SHORT) = Toast.makeText(this, chars, duration).show()
9 |
10 | fun Context.toast(chars: String, duration: Int = Toast.LENGTH_SHORT) = Toast.makeText(this, chars, duration).show()
11 |
12 | fun Context.toast(@StringRes stringRes: Int, duration: Int = Toast.LENGTH_SHORT) = Toast.makeText(this, stringRes, duration).show()
13 |
14 | fun Context.appWidgetManager() = AppWidgetManager.getInstance(this)
--------------------------------------------------------------------------------
/app/src/test/resources/changelog_simple.json:
--------------------------------------------------------------------------------
1 | {
2 | "versions": [
3 | {
4 | "name": "0.9.0",
5 | "code": 90,
6 | "date": 123123123,
7 | "changes": [
8 | {
9 | "type": "fix",
10 | "text": "We fixed something",
11 | "translations": {
12 | "pl": "Coś tam naprawiliśmy"
13 | }
14 | }
15 | ]
16 | },
17 | {
18 | "name": "0.8.0",
19 | "code": 60,
20 | "date": 14654465464,
21 | "changes": [
22 | {
23 | "type": "new",
24 | "text": "We added something",
25 | "": {
26 | "pl": "Coś tam dodalismy"
27 | }
28 | }
29 | ]
30 | }
31 | ]
32 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /opt/android-sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/i18n/I18nModule.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.i18n
2 |
3 | import android.content.Context
4 | import com.antyzero.smoksmog.i18n.time.CountdownProvider
5 | import dagger.Module
6 | import dagger.Provides
7 | import javax.inject.Singleton
8 |
9 | @Module
10 | @Singleton
11 | class I18nModule {
12 |
13 | @Provides
14 | @Singleton
15 | internal fun provideLocalProvider(context: Context): LocaleProvider {
16 | return ContextLocaleProvider(context)
17 | }
18 |
19 | @Provides
20 | @Singleton
21 | internal fun provideCountdownProvider(localeProvider: LocaleProvider): CountdownProvider {
22 | return CountdownProvider(localeProvider)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/network/src/test/resources/responseParticulateDescription.json:
--------------------------------------------------------------------------------
1 | {
2 | "desc": "NO\u2093 jest terminem u\u017cywanym do opisania mieszaniny tlenku azotu (NO) i dwutlenek azotu (NO\u2082). S\u0105 nieorganicznymi gazami utworzonymi przez po\u0142\u0105czenie tlenu z azotem z powietrza. NO jest wytwarzany w znacznie wi\u0119kszych ilo\u015bciach ni\u017c NO\u2082, ale utlenia si\u0119 do NO\u2082 w atmosferze. NO\u2082 powoduje szkodliwe skutki dla dr\u00f3g oddechowych. St\u0119\u017cenia dwutlenku azotu cz\u0119sto zbli\u017caj\u0105 si\u0119, a czasemi przekraczaj\u0105 normy jako\u015bci powietrza w wielu miastach europejskich. NO\u2093 emitowane gdy spalane jest paliwo np. w transporcie, procesach przemys\u0142owych i energetycznych."
3 | }
4 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/google/GoogleModule.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.google
2 |
3 | import android.content.Context
4 |
5 | import com.google.android.gms.common.api.GoogleApiClient
6 | import com.google.android.gms.location.LocationServices
7 |
8 | import dagger.Module
9 | import dagger.Provides
10 |
11 | @Module
12 | class GoogleModule(private val connectionCallbacks: GoogleApiClient.ConnectionCallbacks) {
13 |
14 | @Provides
15 | internal fun provideGoogleApiClient(context: Context): GoogleApiClient {
16 | return GoogleApiClient.Builder(context)
17 | .addConnectionCallbacks(connectionCallbacks)
18 | .addApi(LocationServices.API)
19 | .build()
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/android/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/iwopolanski/Workspace/android-sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/job/SmokSmogJobService.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.job
2 |
3 | import com.antyzero.smoksmog.dsl.appComponent
4 | import com.antyzero.smoksmog.ui.widget.StationWidgetService
5 | import com.firebase.jobdispatcher.JobParameters
6 | import com.firebase.jobdispatcher.JobService
7 |
8 | class SmokSmogJobService : JobService() {
9 |
10 | override fun onCreate() {
11 | super.onCreate()
12 | appComponent().inject(this)
13 | }
14 |
15 | override fun onStartJob(job: JobParameters?): Boolean {
16 | StationWidgetService.updateAll(this)
17 | return false
18 | }
19 |
20 | override fun onStopJob(job: JobParameters?): Boolean {
21 | return false
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/settings_old_general.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
15 |
16 |
--------------------------------------------------------------------------------
/network/src/main/kotlin/pl/malopolska/smoksmog/model/Particulate.kt:
--------------------------------------------------------------------------------
1 | package pl.malopolska.smoksmog.model
2 |
3 | import com.google.gson.annotations.SerializedName
4 | import org.joda.time.DateTime
5 | import java.util.*
6 |
7 | data class Particulate(
8 | val id: Long,
9 | val name: String,
10 | @SerializedName("short_name") val shortName: String,
11 | val value: Float = 0f,
12 | val unit: String,
13 | val norm: Float = 0f,
14 | val date: DateTime,
15 | @SerializedName("avg") val average: Float = 0f,
16 | val position: Int = 0,
17 | val values: List = ArrayList()) {
18 |
19 | val enum: ParticulateEnum
20 | get() = ParticulateEnum.findById(id)
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/dialog/AirQualityDialog.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.dialog
2 |
3 |
4 | import android.text.method.LinkMovementMethod
5 | import android.view.View
6 | import android.widget.TextView
7 | import com.antyzero.smoksmog.R
8 | import com.antyzero.smoksmog.dsl.compatFromHtml
9 |
10 | class AirQualityDialog : InfoDialog() {
11 |
12 | override fun getLayoutId(): Int = R.layout.dialog_info_air_quality
13 |
14 | override fun initView(view: View) {
15 | super.initView(view)
16 |
17 | val textView = view.findViewById(R.id.textView) as TextView
18 |
19 | textView.compatFromHtml(R.string.desc_air_quality)
20 | textView.movementMethod = LinkMovementMethod.getInstance()
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 | 60dp
7 |
8 | 30dp
9 |
10 | 8dp
11 | 7dp
12 |
13 | 10dp
14 |
15 | 8dp
16 | 16dp
17 |
18 | 80dp
19 |
20 | 2dp
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/screen/order/OrderItemViewHolder.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.screen.order
2 |
3 | import android.support.v7.widget.RecyclerView
4 | import android.view.View
5 | import android.widget.TextView
6 | import com.antyzero.smoksmog.R
7 | import com.antyzero.smoksmog.dsl.findView
8 | import com.antyzero.smoksmog.dsl.findViewById
9 | import com.antyzero.smoksmog.storage.model.Item
10 | import pl.malopolska.smoksmog.model.Station
11 |
12 | class OrderItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
13 |
14 | val textView: TextView = findView(R.id.textView)
15 | val viewHandle: View = findViewById(R.id.viewHandle)
16 |
17 | fun bind(stationName: String?) {
18 | textView.text = stationName
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_share_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/antyzero/smoksmog/rules/SpoonRule.java:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.rules;
2 |
3 | import android.support.test.rule.ActivityTestRule;
4 |
5 | import com.squareup.spoon.Spoon;
6 |
7 | import org.junit.rules.ExternalResource;
8 |
9 | /**
10 | *
11 | */
12 | public class SpoonRule extends ExternalResource {
13 |
14 | private final ActivityTestRule activityTestRule;
15 |
16 | public SpoonRule(ActivityTestRule activityTestRule) {
17 | this.activityTestRule = activityTestRule;
18 | }
19 |
20 | public void screenshot(String tag) {
21 | try {
22 | Spoon.screenshot(activityTestRule.getActivity(), tag);
23 | } catch (Exception e) {
24 | System.err.println("Missing Spoon");
25 | }
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/screen/start/PageSave.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.screen.start
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 |
6 | class PageSave(context: Context) {
7 |
8 | private val preferences: SharedPreferences
9 |
10 | init {
11 | preferences = context.getSharedPreferences(TAG, Context.MODE_PRIVATE)
12 | }
13 |
14 | fun savePage(pageOrder: Int) {
15 | preferences.edit().putInt(KEY_ORDER, pageOrder).apply()
16 | }
17 |
18 | fun restorePage(): Int {
19 | return preferences.getInt(KEY_ORDER, 0)
20 | }
21 |
22 | companion object {
23 |
24 | private val TAG = PageSave::class.java.simpleName
25 |
26 | private val KEY_ORDER = "keyOrder"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_pick_station.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
19 |
20 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/smoksmog/logger/Logger.kt:
--------------------------------------------------------------------------------
1 | package smoksmog.logger
2 |
3 |
4 | interface Logger {
5 |
6 | // Verbose
7 |
8 | fun v(tag: String, message: String)
9 |
10 | fun v(tag: String, message: String, throwable: Throwable)
11 |
12 | // Debug
13 |
14 | fun d(tag: String, message: String)
15 |
16 | fun d(tag: String, message: String, throwable: Throwable)
17 |
18 | // Info
19 |
20 | fun i(tag: String, message: String)
21 |
22 | fun i(tag: String, message: String, throwable: Throwable)
23 |
24 | // Warning
25 |
26 | fun w(tag: String, message: String)
27 |
28 | fun w(tag: String, message: String, throwable: Throwable)
29 |
30 | // Error
31 |
32 | fun e(tag: String, message: String)
33 |
34 | fun e(tag: String, message: String, throwable: Throwable)
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/settings/Percent.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.settings
2 |
3 | import android.content.Context
4 | import android.support.annotation.StringRes
5 |
6 | import com.antyzero.smoksmog.R
7 |
8 | /**
9 | * Indicates from which period measurement should be taken
10 | */
11 | enum class Percent constructor(@StringRes private val value: Int) {
12 |
13 | HOUR(R.string.pref_percent_value_hour), DAY(R.string.pref_percent_value_day);
14 |
15 | companion object {
16 |
17 | fun find(context: Context, value: String): Percent {
18 |
19 | values()
20 | .filter { context.getString(it.value) == value }
21 | .forEach { return it }
22 |
23 | throw IllegalArgumentException("Unable to find proper enum value for \"" + value + "\"")
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/domain/src/main/kotlin/com/antyzero/smoksmog/storage/model/Module.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.storage.model
2 |
3 | sealed class Module {
4 |
5 | private val _class: String = javaClass.canonicalName
6 |
7 | /**
8 | * Show AQI and it's type
9 | */
10 | class AirQualityIndex(val type: Type = AirQualityIndex.Type.POLISH) : Module() {
11 |
12 | enum class Type {
13 | POLISH
14 | }
15 | }
16 |
17 | /**
18 | * List of measurements for particulates
19 | */
20 | class Measurements : Module()
21 |
22 | override fun equals(other: Any?): Boolean {
23 | if (this === other) return true
24 | if (other !is Module) return false
25 |
26 | if (_class != other._class) return false
27 |
28 | return true
29 | }
30 |
31 | override fun hashCode(): Int {
32 | return _class.hashCode()
33 | }
34 | }
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/widget/StationWidgetData.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.widget
2 |
3 | import android.content.Context
4 | import android.content.Context.MODE_PRIVATE
5 | import android.content.SharedPreferences
6 | import com.antyzero.smoksmog.dsl.tag
7 |
8 |
9 | class StationWidgetData(context: Context) {
10 |
11 | val sharedPreferences: SharedPreferences
12 |
13 | init {
14 | sharedPreferences = context.getSharedPreferences(tag(), MODE_PRIVATE)
15 | }
16 |
17 | fun addWidget(widgetId: Int, stationId: Long) {
18 | sharedPreferences.edit().putLong(widgetId.toString(), stationId).apply()
19 | }
20 |
21 | fun removeWidget(widgetId: Int) {
22 | sharedPreferences.edit().remove(widgetId.toString()).apply()
23 | }
24 |
25 | fun widgetStationId(widgetId: Int): Long {
26 | return sharedPreferences.getLong(widgetId.toString(), -1)
27 | }
28 | }
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/dsl/ActivityExtension.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.dsl
2 |
3 | import android.app.Activity
4 | import android.content.res.Configuration
5 | import android.support.annotation.IdRes
6 | import android.view.View
7 |
8 |
9 | fun Activity.fullscreen() {
10 | window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
11 | if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
12 | window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
13 | }
14 | }
15 |
16 | fun Activity.statusBarHeight(): Int {
17 | val resource = resources.getIdentifier("status_bar_height", "dimen", "android")
18 | if (resource > 0) {
19 | return resources.getDimensionPixelSize(resource)
20 | }
21 | return 0
22 | }
23 |
24 | @Suppress("UNCHECKED_CAST")
25 | fun Activity.findView(@IdRes id: Int): T = this.findViewById(id) as T
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/tracking/Tracking.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.tracking
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 | import org.joda.time.DateTime
6 |
7 | class Tracking(context: Context) {
8 |
9 | private var preferences: SharedPreferences
10 |
11 | init {
12 | preferences = context.getSharedPreferences("TrackerSharedPreferences", Context.MODE_PRIVATE)
13 |
14 | // Tracking
15 | trackFirstRun()
16 | }
17 |
18 | private fun trackFirstRun() {
19 | if (preferences.contains(KEY_FIRST_RUN_TIME).not()) {
20 | preferences.edit().putLong(KEY_FIRST_RUN_TIME, System.currentTimeMillis()).apply()
21 | }
22 | }
23 |
24 | fun getFirstRunDateTime(): DateTime = DateTime(preferences.getLong(KEY_FIRST_RUN_TIME, 0))
25 |
26 | private companion object {
27 |
28 | private val KEY_FIRST_RUN_TIME = "firstRun"
29 | }
30 | }
--------------------------------------------------------------------------------
/android/src/main/kotlin/smoksmog/logger/SilentLogger.kt:
--------------------------------------------------------------------------------
1 | package smoksmog.logger
2 |
3 | class SilentLogger : Logger {
4 |
5 | override fun v(tag: String, message: String) {
6 |
7 | }
8 |
9 | override fun v(tag: String, message: String, throwable: Throwable) {
10 |
11 | }
12 |
13 | override fun d(tag: String, message: String) {
14 |
15 | }
16 |
17 | override fun d(tag: String, message: String, throwable: Throwable) {
18 |
19 | }
20 |
21 | override fun i(tag: String, message: String) {
22 |
23 | }
24 |
25 | override fun i(tag: String, message: String, throwable: Throwable) {
26 |
27 | }
28 |
29 | override fun w(tag: String, message: String) {
30 |
31 | }
32 |
33 | override fun w(tag: String, message: String, throwable: Throwable) {
34 |
35 | }
36 |
37 | override fun e(tag: String, message: String) {
38 |
39 | }
40 |
41 | override fun e(tag: String, message: String, throwable: Throwable) {
42 |
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/screen/history/HistoryAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.screen.history
2 |
3 | import android.support.v7.widget.RecyclerView
4 | import android.view.LayoutInflater
5 | import android.view.ViewGroup
6 |
7 | import com.antyzero.smoksmog.R
8 |
9 | import pl.malopolska.smoksmog.model.Particulate
10 |
11 |
12 | class HistoryAdapter(private val particulates: List) : RecyclerView.Adapter() {
13 |
14 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ParticulateHistoryViewHolder {
15 | val inflater = LayoutInflater.from(parent.context)
16 | return ParticulateHistoryViewHolder(inflater.inflate(R.layout.item_chart, parent, false))
17 | }
18 |
19 | override fun getItemCount(): Int {
20 | return particulates.size
21 | }
22 |
23 | override fun onBindViewHolder(holder: ParticulateHistoryViewHolder, position: Int) {
24 | holder.bind(particulates[position])
25 | }
26 |
27 | }
--------------------------------------------------------------------------------
/domain/src/integrationTest/kotlin/com/antyzero/smoksmog/api/ApiIntegrationTest.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.api
2 |
3 | import org.assertj.core.api.Assertions.assertThat
4 | import org.junit.Before
5 | import org.junit.Test
6 | import pl.malopolska.smoksmog.RestClient
7 | import pl.malopolska.smoksmog.model.Station
8 | import rx.Observable
9 | import rx.observers.TestSubscriber
10 |
11 | class ApiIntegrationTest {
12 |
13 | lateinit private var api: RestClient
14 |
15 | @Before
16 | fun setUp() {
17 | api = RestClient.Builder().build()
18 | }
19 |
20 | @Test
21 | fun stations() {
22 | val testSubscriber = TestSubscriber()
23 |
24 | api.stations()
25 | .flatMap { Observable.from(it) }
26 | .subscribe(testSubscriber)
27 |
28 | testSubscriber.assertNoErrors()
29 | testSubscriber.assertCompleted()
30 | assertThat(testSubscriber.onNextEvents.size).isGreaterThanOrEqualTo(50) // we should have that much
31 | }
32 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
10 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
11 | # When configured, Gradle will run in incubating parallel mode.
12 | # This option should only be used with decoupled projects. More details, visit
13 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
14 | # org.gradle.parallel=true
15 | org.gradle.daemon=true
16 | org.gradle.parallel=true
17 | org.gradle.jvmargs=-Xmx2048M
18 | appVersionName=1.9.8
19 | appVersionCode=198000
20 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/permission/PermissionHelper.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.permission
2 |
3 | import android.Manifest
4 | import android.content.Context
5 | import android.content.pm.PackageManager
6 | import android.os.Build
7 | import android.support.v4.content.ContextCompat
8 |
9 |
10 | /**
11 | * Makes permission checks easier
12 | *
13 | * Provides scenarios for post and pre Marshmallow OS versions
14 | */
15 | class PermissionHelper(private val context: Context) {
16 |
17 | val isGrantedAccessCoarseLocation: Boolean
18 | get() = isGranted(Manifest.permission.ACCESS_COARSE_LOCATION)
19 |
20 | fun get(permissionKey: String) = isGranted(permissionKey)
21 |
22 | private fun isGranted(permission: String): Boolean {
23 |
24 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
25 | return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED
26 | } else {
27 | return true // TODO check manifest
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/changelog/ChangelogReader.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.changelog
2 |
3 | import com.antyzero.smoksmog.changelog.model.ChangeType
4 | import com.antyzero.smoksmog.changelog.model.Changelog
5 | import com.google.gson.Gson
6 | import com.google.gson.GsonBuilder
7 | import com.google.gson.JsonDeserializer
8 | import org.joda.time.LocalDate
9 | import java.io.File
10 | import java.util.*
11 |
12 | class ChangelogReader(locale: Locale, changelogFile: File) {
13 |
14 | val changelog: Changelog
15 |
16 | private var gson: Gson
17 |
18 | init {
19 | gson = GsonBuilder().apply {
20 | registerTypeAdapter(LocalDate::class.java, JsonDeserializer { json, typeOfT, context -> LocalDate(json.asLong) })
21 | registerTypeAdapter(ChangeType::class.java, JsonDeserializer { json, typeOfT, context -> ChangeType.valueOf(json.asString.toUpperCase()) })
22 | }.create()
23 |
24 | changelog = gson.fromJson(changelogFile.readText(), Changelog::class.java)
25 | }
26 | }
--------------------------------------------------------------------------------
/app/src/androidTest/resources/station-4.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "4",
3 | "name": "Krak\u00f3w - Aleja Krasi\u0144skiego",
4 | "particulates": [
5 | {
6 | "id": "7",
7 | "name": "Py\u0142 zawieszony",
8 | "short_name": "PM\u2081\u2080",
9 | "value": "112.675",
10 | "unit": "\u00b5g\/m\u00b3",
11 | "norm": "50",
12 | "date": "2015-12-17 10:11:02",
13 | "avg": "146.08",
14 | "position": "1"
15 | },
16 | {
17 | "id": "3",
18 | "name": "Dwutlenek azotu",
19 | "short_name": "NO\u2082",
20 | "value": "75.8005",
21 | "unit": "\u00b5g\/m\u00b3",
22 | "norm": "200",
23 | "date": "2015-12-17 10:11:02",
24 | "avg": "54.08",
25 | "position": "5"
26 | },
27 | {
28 | "id": "4",
29 | "name": "Tlenek w\u0119gla",
30 | "short_name": "CO",
31 | "value": "1832.72",
32 | "unit": "\u00b5g\/m\u00b3",
33 | "norm": "10000",
34 | "date": "2015-12-17 10:11:02",
35 | "avg": "1655.32",
36 | "position": "6"
37 | }
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/firebase/SmokSmogFirebaseInstanceIdService.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.firebase
2 |
3 | import android.util.Log
4 | import com.antyzero.smoksmog.BuildConfig
5 | import com.antyzero.smoksmog.dsl.appComponent
6 | import com.antyzero.smoksmog.dsl.tag
7 | import com.crashlytics.android.core.CrashlyticsCore
8 | import com.google.firebase.iid.FirebaseInstanceId
9 | import com.google.firebase.iid.FirebaseInstanceIdService
10 | import javax.inject.Inject
11 |
12 | class SmokSmogFirebaseInstanceIdService : FirebaseInstanceIdService() {
13 |
14 | @Inject lateinit var crashlyticsCore: CrashlyticsCore
15 |
16 | override fun onCreate() {
17 | super.onCreate()
18 | appComponent().inject(this)
19 | }
20 |
21 | override fun onTokenRefresh() {
22 | super.onTokenRefresh()
23 | val token = FirebaseInstanceId.getInstance().token
24 | if (BuildConfig.DEBUG) {
25 | Log.i(tag(), "FCM token: $token")
26 | }
27 | crashlyticsCore.setString("FCM_TOKEN", token)
28 | }
29 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_base.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
- @string/pref_station_selection_mode_entries_last
5 | - @string/pref_station_selection_mode_entries_closest
6 | - @string/pref_station_selection_mode_entries_defined
7 |
8 |
9 | - @string/pref_station_default_value_last
10 | - @string/pref_station_default_value_closest
11 | - @string/pref_station_default_value_defined
12 |
13 |
14 |
15 | - @string/pref_percent_entry_day
16 | - @string/pref_percent_entry_hour
17 |
18 |
19 | - @string/pref_percent_value_day
20 | - @string/pref_percent_value_hour
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/settings_general.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
17 |
18 |
23 |
24 |
--------------------------------------------------------------------------------
/network/src/main/kotlin/pl/malopolska/smoksmog/Api.kt:
--------------------------------------------------------------------------------
1 | package pl.malopolska.smoksmog
2 |
3 | import pl.malopolska.smoksmog.model.Description
4 | import pl.malopolska.smoksmog.model.Station
5 | import retrofit2.http.GET
6 | import retrofit2.http.Path
7 | import rx.Observable
8 |
9 | interface Api {
10 |
11 | @GET("stations")
12 | fun stations(): Observable>
13 |
14 | @GET("stations/{stationId}")
15 | fun station(@Path("stationId") stationId: Long): Observable
16 |
17 | @GET("stations/{lat}/{lon}")
18 | fun stationByLocation(@Path("lat") latitude: Double, @Path("lon") longitude: Double): Observable
19 |
20 | @GET("stations/{stationId}/history")
21 | fun stationHistory(@Path("stationId") stationId: Long): Observable
22 |
23 | @GET("stations/{lat}/{lon}/history")
24 | fun stationHistoryByLocation(@Path("lat") latitude: Double, @Path("lon") longitude: Double): Observable
25 |
26 | @GET("particulates/{id}/desc")
27 | fun particulateDescription(@Path("id") particulateId: Long): Observable
28 | }
29 |
--------------------------------------------------------------------------------
/android/src/test/java/smoksmog/air/AirQualityIndexTest.kt:
--------------------------------------------------------------------------------
1 | package smoksmog.air
2 |
3 | import org.assertj.core.api.Assertions.assertThat
4 | import org.junit.Test
5 |
6 |
7 | class AirQualityIndexTest {
8 |
9 | @Test
10 | fun firstRangeCorrectResult() {
11 | var value = 0f
12 | do {
13 | val index = AirQualityIndex.calculateBenzene(value)
14 | assertThat(index).isLessThan(1f)
15 | value += 0.001f
16 | } while (value < 5f)
17 | }
18 |
19 | @Test
20 | fun secondRangeCorrectResult() {
21 | var value = 5f
22 | do {
23 | val index = AirQualityIndex.calculateBenzene(value)
24 | assertThat(index).isLessThan(7f)
25 | value += 0.001f
26 | } while (value < 20f)
27 | }
28 |
29 | @Test
30 | fun thirdRangeCorrectResult() {
31 | var value = 20f
32 | do {
33 | val index = AirQualityIndex.calculateBenzene(value)
34 | assertThat(index).isLessThan(10f)
35 | value += 0.001f
36 | } while (value < 50f)
37 | }
38 |
39 | }
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/antyzero/smoksmog/CustomTestRunner.java:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog;
2 |
3 | import android.os.AsyncTask;
4 | import android.support.test.runner.AndroidJUnitRunner;
5 |
6 | import rx.Scheduler;
7 | import rx.functions.Func1;
8 | import rx.plugins.RxJavaHooks;
9 | import rx.schedulers.Schedulers;
10 |
11 | /**
12 | * http://collectiveidea.com/blog/archives/2016/10/13/retrofitting-espresso
13 | */
14 | public class CustomTestRunner extends AndroidJUnitRunner {
15 |
16 | private static final Func1 SCHEDULER = new Func1() {
17 | @Override
18 | public Scheduler call(Scheduler scheduler) {
19 | return Schedulers.from(AsyncTask.THREAD_POOL_EXECUTOR);
20 | }
21 | };
22 |
23 | @Override
24 | public void onStart() {
25 | RxJavaHooks.setOnIOScheduler(SCHEDULER);
26 | RxJavaHooks.setOnNewThreadScheduler(SCHEDULER);
27 | super.onStart();
28 | }
29 |
30 | @Override
31 | public void onDestroy() {
32 | super.onDestroy();
33 | RxJavaHooks.reset();
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/test/kotlin/com/antyzero/smoksmog/changelog/ChangelogReaderTest.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.changelog
2 |
3 | import com.antyzero.smoksmog.changelog.model.ChangeType.FIX
4 | import org.assertj.core.api.Assertions.assertThat
5 | import org.junit.Test
6 | import java.io.File
7 | import java.util.*
8 |
9 |
10 | class ChangelogReaderTest {
11 |
12 | val localePolish = Locale("pl", "PL")
13 |
14 | @Test
15 | fun readChangelog() {
16 | val changelogReader = createChangelog(localePolish, "/changelog_simple.json")
17 | val changelog = changelogReader.changelog
18 |
19 | assertThat(changelog).isNotNull()
20 |
21 | println(changelog)
22 |
23 | with(changelog.versions) {
24 | assertThat(this).hasSize(2)
25 | assertThat(this[0].changes[0].type).isEqualTo(FIX)
26 | assertThat(this[0].changes[0].translations["pl"]).isNotNull()
27 | }
28 | }
29 |
30 | private fun createChangelog(locale: Locale, resourcePath: String) = ChangelogReader(
31 | locale, File(ChangelogReaderTest::class.java.getResource(resourcePath).toURI())
32 | )
33 | }
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/logger/LoggerModule.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.logger
2 |
3 | import com.antyzero.smoksmog.BuildConfig
4 | import com.antyzero.smoksmog.logger.CrashlyticsLogger.ExceptionLevel.ERROR
5 | import com.antyzero.smoksmog.user.User
6 | import com.crashlytics.android.core.CrashlyticsCore
7 | import dagger.Module
8 | import dagger.Provides
9 | import smoksmog.logger.AndroidLogger
10 | import smoksmog.logger.Logger
11 | import javax.inject.Singleton
12 |
13 | @Singleton
14 | @Module
15 | class LoggerModule {
16 |
17 | @Provides
18 | @Singleton
19 | internal fun provideLogger(callback: CrashlyticsLogger.ConfigurationCallback): Logger {
20 | return if (BuildConfig.DEBUG) AndroidLogger() else CrashlyticsLogger(ERROR, callback)
21 | }
22 |
23 | @Provides
24 | @Singleton
25 | internal fun provideConfigurationCallback(user: User): CrashlyticsLogger.ConfigurationCallback {
26 | return object : CrashlyticsLogger.ConfigurationCallback {
27 | override fun onConfiguration(instance: CrashlyticsCore) {
28 | instance.setUserIdentifier(user.identifier)
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/error/SnackBarErrorReporter.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.error
2 |
3 | import android.app.Activity
4 | import android.support.annotation.StringRes
5 | import android.support.design.widget.Snackbar
6 |
7 | /**
8 | * Error reporting via SnackBar
9 | */
10 | class SnackBarErrorReporter(private val activity: Activity) : ErrorReporter {
11 |
12 | override fun report(message: String) {
13 | processSnackBar(Snackbar.make(activity.findViewById(android.R.id.content), message, DURATION))
14 | }
15 |
16 | override fun report(@StringRes stringId: Int) {
17 | processSnackBar(Snackbar.make(activity.findViewById(android.R.id.content), stringId, DURATION))
18 | }
19 |
20 | override fun report(@StringRes stringId: Int, vararg objects: Any) {
21 | val message = activity.resources.getString(stringId, *objects)
22 | processSnackBar(Snackbar.make(activity.findViewById(android.R.id.content), message, DURATION))
23 | }
24 |
25 | private fun processSnackBar(snackBar: Snackbar) {
26 | snackBar.show()
27 | }
28 |
29 | companion object {
30 | private val DURATION = Snackbar.LENGTH_LONG
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/i18n/time/EnglishCountdown.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.i18n.time
2 |
3 | class EnglishCountdown : Countdown {
4 |
5 | override fun get(givenSeconds: Int): String {
6 |
7 | val seconds = givenSeconds and 60
8 | val minutes = givenSeconds / 60 % 60
9 | val hours = givenSeconds / 60 / 60
10 |
11 | if (minutes == 0) {
12 | return secondsAgo(seconds)
13 | } else if (hours == 0) {
14 | return minutesAgo(minutes)
15 | }
16 |
17 | return hoursAgo(hours)
18 | }
19 |
20 | private fun hoursAgo(hours: Int): String {
21 | return ago(hours, "hour", "hours")
22 | }
23 |
24 | private fun minutesAgo(minutes: Int): String {
25 | return ago(minutes, "minute", "minutes")
26 | }
27 |
28 | private fun secondsAgo(seconds: Int): String {
29 | return ago(seconds, "seconds", "seconds")
30 | }
31 |
32 | private fun ago(amount: Int, single: String, many: String): String {
33 | if (amount == 1) {
34 | return amount.toString() + " " + single
35 | } else {
36 | return amount.toString() + " " + many
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/antyzero/smoksmog/screen/HistoryActivityTestRule.java:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.screen;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.support.test.InstrumentationRegistry;
6 |
7 | import com.antyzero.smoksmog.ui.screen.history.HistoryActivity;
8 |
9 | public class HistoryActivityTestRule extends MockedNetworkActivityTestRule {
10 |
11 | public HistoryActivityTestRule() {
12 | super(HistoryActivity.class);
13 | }
14 |
15 | public HistoryActivityTestRule(boolean initialTouchMode) {
16 | super(HistoryActivity.class, initialTouchMode);
17 | }
18 |
19 | public HistoryActivityTestRule(boolean initialTouchMode, boolean launchActivity) {
20 | super(HistoryActivity.class, initialTouchMode, launchActivity);
21 | }
22 |
23 | @Override
24 | protected Intent getActivityIntent() {
25 | Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
26 | try {
27 | return HistoryActivity.Companion.intent(context, 13);
28 | } catch (Exception e) {
29 | throw new IllegalStateException("Unable to create Intent");
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/dsl/CompatExtension.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.dsl
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.content.res.Configuration
6 | import android.os.Build
7 | import android.support.annotation.ColorRes
8 | import android.support.annotation.StringRes
9 | import android.support.v4.content.ContextCompat
10 | import android.text.Html
11 | import android.widget.TextView
12 | import java.util.*
13 |
14 | /**
15 | * Extensions used with
16 | * - compat utilities
17 | * - for backward compatibility
18 | * - in case od deprecated methods
19 | */
20 |
21 | @SuppressLint("NewApi")
22 | fun Context.getCompatColor(@ColorRes colorId: Int): Int = when (Build.VERSION.SDK_INT) {
23 | in 1..Build.VERSION_CODES.LOLLIPOP_MR1 -> ContextCompat.getColor(this, colorId)
24 | else -> getColor(colorId) // API23
25 | }
26 |
27 | @Suppress("DEPRECATION")
28 | @SuppressLint("NewApi")
29 | fun TextView.compatFromHtml(@StringRes id: Int) {
30 | text = when (Build.VERSION.SDK_INT) {
31 | in 1..Build.VERSION_CODES.M -> Html.fromHtml(context.getString(id))
32 | else -> Html.fromHtml(context.getString(id), Html.FROM_HTML_MODE_LEGACY) // API24
33 | }
34 | }
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/screen/ActivityComponent.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.screen
2 |
3 | import com.antyzero.smoksmog.google.GoogleModule
4 | import com.antyzero.smoksmog.ui.screen.about.AboutActivity
5 | import com.antyzero.smoksmog.ui.screen.history.HistoryActivity
6 | import com.antyzero.smoksmog.ui.screen.order.OrderActivity
7 | import com.antyzero.smoksmog.ui.screen.start.StartActivity
8 | import com.antyzero.smoksmog.ui.screen.start.fragment.LocationStationFragmentComponent
9 | import com.antyzero.smoksmog.ui.widget.StationWidgetConfigureActivity
10 | import dagger.Subcomponent
11 |
12 | @Subcomponent(modules = arrayOf(ActivityModule::class))
13 | interface ActivityComponent {
14 |
15 | operator fun plus(fragmentModule: FragmentModule): FragmentComponent
16 |
17 | fun plus(fragmentModule: FragmentModule, googleModule: GoogleModule): LocationStationFragmentComponent
18 |
19 | fun inject(activity: HistoryActivity)
20 |
21 | fun inject(activity: AboutActivity)
22 |
23 | fun inject(startActivity: StartActivity)
24 |
25 | fun inject(orderActivity: OrderActivity)
26 |
27 | fun inject(pickStationActivity: PickStationActivity)
28 |
29 | fun inject(stationWidgetConfigureActivity: StationWidgetConfigureActivity)
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/res/values/preferences.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | stationDefault
5 | stationSelected
6 | stationClosest
7 | dragonShow
8 |
9 |
10 | last
11 | closest
12 | defined
13 |
14 |
15 |
16 |
17 | pref_key_percent
18 |
19 |
20 | 1
21 | 24
22 | @string/pref_percent_value_day
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/BaseActivity.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.os.Build
6 | import com.antyzero.smoksmog.R
7 | import com.antyzero.smoksmog.dsl.getCompatColor
8 | import com.trello.rxlifecycle.components.support.RxAppCompatActivity
9 | import uk.co.chrisjenx.calligraphy.CalligraphyContextWrapper
10 |
11 | abstract class BaseActivity : RxAppCompatActivity() {
12 |
13 | override fun attachBaseContext(newBase: Context) {
14 | super.attachBaseContext(if (addCalligraphy()) {
15 | CalligraphyContextWrapper.wrap(newBase)
16 | } else {
17 | newBase
18 | })
19 | }
20 |
21 | open protected fun addCalligraphy() = true
22 |
23 | companion object {
24 |
25 | /**
26 | * Shared initialization among activities, use it you cannot extend BaseActivity
27 |
28 | * @param activity for access to various data
29 | */
30 | fun initOnCreate(activity: Activity) {
31 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
32 | activity.window.navigationBarColor = activity.getCompatColor(R.color.primary)
33 | }
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/screen/ActivityModule.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.screen
2 |
3 | import android.app.Activity
4 |
5 | import com.antyzero.smoksmog.error.ErrorReporter
6 | import com.antyzero.smoksmog.error.SnackBarErrorReporter
7 | import com.antyzero.smoksmog.firebase.FirebaseEvents
8 | import com.google.firebase.analytics.FirebaseAnalytics
9 |
10 | import dagger.Module
11 | import dagger.Provides
12 |
13 | @Module
14 | class ActivityModule(private val activity: Activity) {
15 | private val firebaseAnalytics: FirebaseAnalytics
16 |
17 | init {
18 | this.firebaseAnalytics = FirebaseAnalytics.getInstance(activity)
19 | }
20 |
21 | @Provides
22 | internal fun provideFirebaseAnalytics(): FirebaseAnalytics {
23 | return firebaseAnalytics
24 | }
25 |
26 | @Provides
27 | internal fun provideFirebaseEvents(firebaseAnalytics: FirebaseAnalytics): FirebaseEvents {
28 | return FirebaseEvents(firebaseAnalytics)
29 | }
30 |
31 | @Provides
32 | internal fun provideActivity(): Activity {
33 | return activity
34 | }
35 |
36 | @Provides
37 | internal fun provideErrorReporter(activity: Activity): ErrorReporter {
38 | return SnackBarErrorReporter(activity)
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/user/User.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.user
2 |
3 | import android.content.Context
4 | import java.util.*
5 |
6 | /**
7 | * User management
8 | */
9 | class User(context: Context) {
10 |
11 | val identifier: String
12 |
13 | init {
14 |
15 | val preferences = context.getSharedPreferences(PREFERENCES_USER, Context.MODE_PRIVATE)
16 |
17 | if (!preferences.contains(KEY_USER_ID)) {
18 | identifier = createIdentifier()
19 | preferences.edit().putString(KEY_USER_ID, identifier).apply()
20 | } else {
21 | identifier = preferences.getString(KEY_USER_ID, DEF_VALUE)
22 |
23 | if (identifier == DEF_VALUE) {
24 | throw IllegalStateException("Missing identifier for user")
25 | }
26 | }
27 | }
28 |
29 | private fun createIdentifier(): String {
30 | val value = Random().nextInt(Integer.MAX_VALUE)
31 | val hashCode = value.toString().hashCode()
32 | return "ID-" + Math.abs(hashCode).toString()
33 | }
34 |
35 | companion object {
36 |
37 | private val PREFERENCES_USER = "USER"
38 | private val KEY_USER_ID = "KEY_USER_ID_V3"
39 | private val DEF_VALUE = "DEF_VALUE"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ApplicationModule.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import com.antyzero.smoksmog.permission.PermissionHelper
6 | import com.antyzero.smoksmog.tracking.Tracking
7 | import com.antyzero.smoksmog.ui.typeface.TypefaceProvider
8 | import dagger.Module
9 | import dagger.Provides
10 | import javax.inject.Singleton
11 |
12 | @Singleton
13 | @Module
14 | class ApplicationModule(private val application: Application) {
15 |
16 | @Provides
17 | @Singleton
18 | internal fun provideContext(): Context {
19 | return application
20 | }
21 |
22 | @Provides
23 | @Singleton
24 | internal fun provideApplication(): Application {
25 | return application
26 | }
27 |
28 | @Provides
29 | @Singleton
30 | internal fun provideTypefaceProvider(context: Context): TypefaceProvider {
31 | return TypefaceProvider(context)
32 | }
33 |
34 | @Provides
35 | @Singleton
36 | internal fun provideTracker(context: Context): Tracking {
37 | return Tracking(context)
38 | }
39 |
40 | @Provides
41 | @Singleton
42 | internal fun providePermissionHelper(context: Context): PermissionHelper {
43 | return PermissionHelper(context)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/dialog/BaseDialog.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.dialog
2 |
3 | import android.app.Dialog
4 | import android.content.DialogInterface
5 | import android.os.Bundle
6 | import android.support.v4.app.DialogFragment
7 | import android.support.v7.app.AlertDialog
8 | import android.support.v7.widget.Toolbar
9 | import android.view.View
10 | import com.antyzero.smoksmog.dsl.setNegativeButton
11 | import com.antyzero.smoksmog.dsl.setPositiveButton
12 | import com.antyzero.smoksmog.dsl.setToolbar
13 |
14 | abstract class BaseDialog : DialogFragment() {
15 |
16 | lateinit protected var toolbar: Toolbar
17 |
18 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
19 | return AlertDialog.Builder(activity).apply {
20 | toolbar = setToolbar(dataToolbarTitle())
21 | setView(dataContent())
22 | setPositiveButton(dataPositiveButton())
23 | setNegativeButton(dataNegativeButton())
24 | }.create()
25 | }
26 |
27 | abstract protected fun dataToolbarTitle(): String
28 | abstract protected fun dataPositiveButton(): Pair
29 | abstract protected fun dataNegativeButton(): Pair
30 | abstract protected fun dataContent(): View
31 | }
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/screen/order/SimpleItemTouchHelperCallback.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.screen.order
2 |
3 | import android.support.v7.widget.RecyclerView
4 | import android.support.v7.widget.helper.ItemTouchHelper
5 |
6 | class SimpleItemTouchHelperCallback(private val adapter: ItemTouchHelperAdapter) : ItemTouchHelper.Callback() {
7 |
8 | override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
9 | val drawFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
10 | val swipeFlags = ItemTouchHelper.START or ItemTouchHelper.END
11 | return ItemTouchHelper.Callback.makeMovementFlags(drawFlags, swipeFlags)
12 | }
13 |
14 | override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
15 | adapter.onItemMove(viewHolder.adapterPosition, target.adapterPosition)
16 | return true
17 | }
18 |
19 | override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
20 | adapter.onItemDismiss(viewHolder.adapterPosition)
21 | }
22 |
23 | override fun isLongPressDragEnabled(): Boolean {
24 | return true
25 | }
26 |
27 | override fun isItemViewSwipeEnabled(): Boolean {
28 | return true
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/dsl/DimenUtils.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.dsl
2 |
3 | import android.content.Context
4 | import android.support.annotation.DimenRes
5 |
6 | private const val idNavBar = "navigation_bar_height"
7 | private const val idStatusBar = "status_bar_height"
8 |
9 | fun Context.navBarHeight(): Int {
10 | val resources = this.resources
11 | val resourceId = resources.getIdentifier(idNavBar, "dimen", "android")
12 | return if (resourceId > 0) resources.getDimensionPixelSize(resourceId) else 0
13 | }
14 |
15 | fun Context.navBarHeight(@DimenRes defaultRes: Int): Int {
16 | val resources = this.resources
17 | val resourceId = resources.getIdentifier(idNavBar, "dimen", "android")
18 | return resources.getDimensionPixelSize(if (resourceId > 0) resourceId else defaultRes)
19 | }
20 |
21 | fun Context.getStatusBarHeight(@DimenRes defaultRes: Int): Int {
22 | val resources = this.resources
23 | val resourceId = resources.getIdentifier(idStatusBar, "dimen", "android")
24 | return resources.getDimensionPixelSize(if (resourceId > 0) resourceId else defaultRes)
25 | }
26 |
27 | fun Context.getStatusBarHeight(): Int {
28 | val resources = this.resources
29 | val resourceId = resources.getIdentifier(idStatusBar, "dimen", "android")
30 | return resources.getDimensionPixelSize(resourceId)
31 | }
--------------------------------------------------------------------------------
/domain/src/main/kotlin/com/antyzero/smoksmog/SmokSmog.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog
2 |
3 | import com.antyzero.smoksmog.location.Location
4 | import com.antyzero.smoksmog.location.LocationProvider
5 | import com.antyzero.smoksmog.model.Page
6 | import com.antyzero.smoksmog.storage.PersistentStorage
7 | import com.antyzero.smoksmog.storage.model.Item
8 | import pl.malopolska.smoksmog.Api
9 | import pl.malopolska.smoksmog.model.Station
10 | import rx.Observable
11 |
12 | class SmokSmog(val api: Api, val storage: PersistentStorage, val locationProvider: LocationProvider) {
13 |
14 | fun collectData(): Observable = Observable.from(storage.fetchAll())
15 | .flatMap { collectDataForItem(it) }
16 |
17 | fun collectDataForItem(item: Item): Observable = when (item) {
18 | is Item.Station -> api.station(item.id)
19 | is Item.Nearest -> nearestStation()
20 | else -> throw IllegalStateException("Unsupported item type $item")
21 | }.zipWith(Observable.just(item)) { station, item -> Page(item, station) }.limit(1)
22 |
23 | fun nearestStation(): Observable = locationProvider.location()
24 | .filter { it is Location.Position }
25 | .cast(Location.Position::class.java)
26 | .flatMap { api.stationByLocation(it.coordinates.first, it.coordinates.second) }
27 | .limit(1)
28 | }
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/screen/settings/SettingsActivity.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.screen.settings
2 |
3 |
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.os.Bundle
7 | import com.antyzero.smoksmog.R
8 | import com.antyzero.smoksmog.ui.BaseActivity
9 | import kotlinx.android.synthetic.main.activity_settings.*
10 |
11 | class SettingsActivity : BaseActivity() {
12 |
13 | override fun onCreate(savedInstanceState: Bundle?) {
14 | super.onCreate(savedInstanceState)
15 | setContentView(R.layout.activity_settings)
16 | setSupportActionBar(toolbar)
17 |
18 | if (supportActionBar != null) {
19 | supportActionBar!!.setDisplayHomeAsUpEnabled(true)
20 | supportActionBar!!.setTitle(R.string.title_settings)
21 | }
22 |
23 | fragmentManager.beginTransaction()
24 | .replace(R.id.contentFragment, GeneralSettingsFragment())
25 | .commit()
26 | }
27 |
28 | override fun addCalligraphy() = false
29 |
30 | companion object {
31 |
32 | fun start(context: Context) {
33 | context.startActivity(intent(context))
34 | }
35 |
36 | private fun intent(context: Context): Intent {
37 | return Intent(context, SettingsActivity::class.java)
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
18 |
19 |
23 |
24 |
25 |
26 |
27 |
30 |
31 |
34 |
35 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/network/src/test/resources/responseStation.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "4",
3 | "name": "Krak\u00f3w - Aleja Krasi\u0144skiego",
4 | "particulates": [
5 | {
6 | "id": "7",
7 | "name": "Py\u0142 zawieszony",
8 | "short_name": "PM\u2081\u2080",
9 | "value": "83.4571",
10 | "unit": "\u00b5g\/m\u00b3",
11 | "norm": "50",
12 | "date": "2015-08-25 12:11:03",
13 | "avg": "66.46",
14 | "position": "1"
15 | },
16 | {
17 | "id": "8",
18 | "name": "Py\u0142 zawieszony 2,5",
19 | "short_name": "PM\u2081\u2080",
20 | "value": "33.4571",
21 | "unit": "\u00b5g\/m\u00b3",
22 | "norm": "50",
23 | "date": "2015-08-25 12:11:03",
24 | "avg": "26.46",
25 | "position": "1"
26 | },
27 | {
28 | "id": "3",
29 | "name": "Dwutlenek azotu",
30 | "short_name": "NO\u2082",
31 | "value": "109.895",
32 | "unit": "\u00b5g\/m\u00b3",
33 | "norm": "200",
34 | "date": "2015-08-25 12:11:03",
35 | "avg": "84.94",
36 | "position": "5"
37 | },
38 | {
39 | "id": "4",
40 | "name": "Tlenek w\u0119gla",
41 | "short_name": "CO",
42 | "value": "1180.42",
43 | "unit": "\u00b5g\/m\u00b3",
44 | "norm": "10000",
45 | "date": "2015-08-25 12:11:03",
46 | "avg": "1083.54",
47 | "position": "6"
48 | }
49 | ]
50 | }
51 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/smoksmog/logger/AndroidLogger.kt:
--------------------------------------------------------------------------------
1 | package smoksmog.logger
2 |
3 | import android.util.Log
4 |
5 | /**
6 | * Typical Android SDK logging
7 | */
8 | class AndroidLogger : Logger {
9 |
10 | override fun v(tag: String, message: String) {
11 | Log.v(tag, message)
12 | }
13 |
14 | override fun v(tag: String, message: String, throwable: Throwable) {
15 | Log.v(tag, message, throwable)
16 | }
17 |
18 | override fun d(tag: String, message: String) {
19 | Log.d(tag, message)
20 | }
21 |
22 | override fun d(tag: String, message: String, throwable: Throwable) {
23 | Log.d(tag, message, throwable)
24 | }
25 |
26 | override fun i(tag: String, message: String) {
27 | Log.i(tag, message)
28 | }
29 |
30 | override fun i(tag: String, message: String, throwable: Throwable) {
31 | Log.i(tag, message, throwable)
32 | }
33 |
34 | override fun w(tag: String, message: String) {
35 | Log.w(tag, message)
36 | }
37 |
38 | override fun w(tag: String, message: String, throwable: Throwable) {
39 | Log.w(tag, message, throwable)
40 | }
41 |
42 | override fun e(tag: String, message: String) {
43 | Log.v(tag, message)
44 | }
45 |
46 | override fun e(tag: String, message: String, throwable: Throwable) {
47 | Log.v(tag, message, throwable)
48 | }
49 |
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/main.xml:
--------------------------------------------------------------------------------
1 |
46 |
--------------------------------------------------------------------------------
/.travis.yml-disabled:
--------------------------------------------------------------------------------
1 | language: android
2 | sudo: false
3 |
4 | env:
5 | global:
6 | - JAVA7_HOME=/usr/lib/jvm/java-7-oracle
7 | - JAVA8_HOME=/usr/lib/jvm/java-8-oracle
8 | - JAVA_HOME=$JAVA7_HOME
9 | - ANDROID_HOME=/usr/local/android-sdk
10 | matrix:
11 | - ANDROID_TARGET=android-18 ANDROID_ABI=armeabi-v7a
12 |
13 | jdk:
14 | - oraclejdk8
15 |
16 | cache:
17 | directories:
18 | - $HOME/.gradle/caches
19 | - $HOME/.gradle/daemon
20 |
21 | android:
22 | coponents:
23 | - platform-tools
24 | - tools
25 | - extra-google-google_play_services
26 | - extra-google-m2repository
27 | - extra-android-m2repository
28 | licenses:
29 | - 'android-sdk-preview-license-52d11cd2'
30 | - 'android-sdk-license-.+'
31 | - 'google-gdk-license-.+'
32 |
33 | script:
34 | - ./gradlew check assemble --stacktrace
35 |
36 | before_script:
37 | - mkdir "$ANDROID_HOME/licenses" || true
38 | - echo -e "\n8933bad161af4178b1185d1a37fbf41ea5269c55" > "$ANDROID_HOME/licenses/android-sdk-license"
39 | - echo -e "\n84831b9409646a918e30573bab4c9c91346d8abd" > "$ANDROID_HOME/licenses/android-sdk-preview-license"
40 | - rm -rf ./build ./android/build
41 |
42 | after_success:
43 | - ./gradlew jacocoFullReport
44 | - pip install --user codecov
45 | - codecov
46 |
47 | after_failure:
48 | - cat /home/travis/build/SmokSmog/smoksmog-android/android/build/outputs/lint-results-debug.xml
--------------------------------------------------------------------------------
/network/src/test/java/pl/malopolska/smoksmog/RestClientTest.java:
--------------------------------------------------------------------------------
1 | package pl.malopolska.smoksmog;
2 |
3 | import org.junit.Test;
4 |
5 | import java.text.Collator;
6 | import java.util.ArrayList;
7 | import java.util.Collections;
8 | import java.util.List;
9 | import java.util.Locale;
10 |
11 | import pl.malopolska.smoksmog.model.Station;
12 |
13 | import static org.assertj.core.api.Assertions.assertThat;
14 |
15 | public class RestClientTest {
16 |
17 | @Test
18 | public void testCreateServerUrlWithLocale() throws Exception {
19 |
20 | // given
21 | Locale locale = Locale.ENGLISH;
22 | RestClient restClient = new RestClient.Builder(locale).build();
23 |
24 | // when
25 | String result = restClient.getEndpoint();
26 |
27 | // then
28 | assertThat(result).isEqualTo("http://api.smoksmog.jkostrz.name/" + locale.getLanguage() + "/");
29 | }
30 |
31 | @Test
32 | public void stations() throws Exception {
33 |
34 | List stations = new RestClient.Builder().build().stations().toBlocking().first();
35 | List names = new ArrayList<>();
36 |
37 | for (Station station : stations) {
38 | names.add(station.getName());
39 | }
40 |
41 | Collections.sort(names, Collator.getInstance());
42 |
43 | for(String name : names){
44 | System.out.print(name + ", ");
45 | }
46 | }
47 | }
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/antyzero/smoksmog/screen/SettingsActivityTest.java:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.screen;
2 |
3 |
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.rule.ActivityTestRule;
6 | import android.support.test.runner.AndroidJUnit4;
7 |
8 | import com.antyzero.smoksmog.rules.RxSchedulerTestRule;
9 | import com.antyzero.smoksmog.rules.SpoonRule;
10 | import com.antyzero.smoksmog.ui.screen.settings.SettingsActivity;
11 |
12 | import org.junit.Rule;
13 | import org.junit.Test;
14 | import org.junit.rules.RuleChain;
15 | import org.junit.rules.TestRule;
16 | import org.junit.runner.RunWith;
17 |
18 | @RunWith(AndroidJUnit4.class)
19 | public class SettingsActivityTest {
20 |
21 | @Rule
22 | public final RxSchedulerTestRule rxSchedulerTestRule = new RxSchedulerTestRule();
23 | private final ActivityTestRule activityTestRule = new MockedNetworkActivityTestRule<>(SettingsActivity.class);
24 | private final SpoonRule spoonRule = new SpoonRule(activityTestRule);
25 | @Rule
26 | public final TestRule testRule = RuleChain.outerRule(activityTestRule).around(spoonRule);
27 |
28 | @Test
29 | public void checkCreation() {
30 |
31 | // Given
32 | activityTestRule.getActivity();
33 |
34 | // When
35 | InstrumentationRegistry.getInstrumentation().waitForIdleSync();
36 |
37 | // Then
38 | spoonRule.screenshot("Created");
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/antyzero/smoksmog/screen/HistoryActivityTest.java:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.screen;
2 |
3 |
4 | import android.content.Intent;
5 | import android.support.test.InstrumentationRegistry;
6 | import android.support.test.rule.ActivityTestRule;
7 | import android.support.test.runner.AndroidJUnit4;
8 |
9 | import com.antyzero.smoksmog.rules.RxSchedulerTestRule;
10 | import com.antyzero.smoksmog.rules.SpoonRule;
11 | import com.antyzero.smoksmog.ui.screen.history.HistoryActivity;
12 |
13 | import org.junit.Rule;
14 | import org.junit.Test;
15 | import org.junit.rules.RuleChain;
16 | import org.junit.rules.TestRule;
17 | import org.junit.runner.RunWith;
18 |
19 | @RunWith(AndroidJUnit4.class)
20 | public class HistoryActivityTest {
21 |
22 | @Rule
23 | public final RxSchedulerTestRule rxSchedulerTestRule = new RxSchedulerTestRule();
24 | private final ActivityTestRule activityTestRule = new HistoryActivityTestRule(true, false);
25 | private final SpoonRule spoonRule = new SpoonRule(activityTestRule);
26 | @Rule
27 | public final TestRule testRule = RuleChain.outerRule(activityTestRule).around(spoonRule);
28 |
29 | @Test
30 | public void checkCreation() {
31 |
32 | // given
33 | activityTestRule.launchActivity(HistoryActivity.Companion.fillIntent(new Intent(), 13));
34 |
35 | // when
36 | InstrumentationRegistry.getInstrumentation().waitForIdleSync();
37 |
38 | // then
39 | spoonRule.screenshot("Created");
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_history.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
16 |
17 |
21 |
22 |
28 |
29 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_order.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
25 |
26 |
37 |
38 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/utils/TextUtils.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.utils
2 |
3 |
4 | import android.text.Spannable
5 | import android.text.SpannableStringBuilder
6 | import android.text.style.RelativeSizeSpan
7 | import android.text.style.SubscriptSpan
8 |
9 | class TextUtils private constructor() {
10 |
11 | init {
12 | throw IllegalAccessError("Utils class")
13 | }
14 |
15 | companion object {
16 |
17 | fun spannableSubscript(originalText: String): CharSequence {
18 |
19 | val builder = SpannableStringBuilder()
20 |
21 | for (i in 0..originalText.length - 1) {
22 | val code = originalText.codePointAt(i)
23 | when (code) {
24 | in 8320..8329 -> {
25 | builder.append(String(Character.toChars(code - 8272)))
26 | makeCharSmaller(builder, i)
27 | }
28 | 46 -> {
29 | builder.append(originalText[i])
30 | makeCharSmaller(builder, i)
31 | }
32 | else -> builder.append(originalText[i])
33 | }
34 | }
35 |
36 | return builder
37 | }
38 |
39 | private fun makeCharSmaller(builder: SpannableStringBuilder, i: Int) {
40 | builder.setSpan(SubscriptSpan(), i, i + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
41 | builder.setSpan(RelativeSizeSpan(0.55f), i, i + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/dialog/FacebookDialog.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.dialog
2 |
3 | import android.app.AlertDialog
4 | import android.content.Intent
5 | import android.net.Uri
6 | import android.view.View
7 | import com.antyzero.smoksmog.R
8 | import com.antyzero.smoksmog.SmokSmogApplication
9 | import com.crashlytics.android.answers.Answers
10 | import com.crashlytics.android.answers.CustomEvent
11 | import kotlinx.android.synthetic.main.dialog_info_facebook.*
12 | import javax.inject.Inject
13 |
14 | class FacebookDialog : InfoDialog() {
15 |
16 | @Inject lateinit var answers: Answers
17 |
18 | override fun getLayoutId(): Int = R.layout.dialog_info_facebook
19 |
20 | override fun updateBuilder(builder: AlertDialog.Builder): AlertDialog.Builder {
21 | builder.setPositiveButton("OK, pokaż") { dialog, which -> takeMeToFacebook() }.setNegativeButton("Nie, podziękuję") { dialog, which -> dismiss() }
22 | return builder
23 | }
24 |
25 | override fun initView(view: View) {
26 | super.initView(view)
27 | SmokSmogApplication[view.context].appComponent.inject(this)
28 | imageView.setOnClickListener { takeMeToFacebook() }
29 | }
30 |
31 | private fun takeMeToFacebook() {
32 | val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://www.facebook.com/SmokSmog"))
33 | startActivity(browserIntent)
34 | dismiss()
35 | answers.logCustom(FacebookClickedEvent())
36 | }
37 |
38 | private class FacebookClickedEvent : CustomEvent(FacebookClickedEvent::class.java.simpleName)
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/screen/SimpleStationAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.screen
2 |
3 | import android.support.v7.widget.RecyclerView
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.View.INVISIBLE
7 | import android.view.ViewGroup
8 | import android.widget.TextView
9 | import com.antyzero.smoksmog.R
10 | import pl.malopolska.smoksmog.model.Station
11 |
12 |
13 | class SimpleStationAdapter(val listStation: List, val onStationClick: OnStationClick) : RecyclerView.Adapter() {
14 |
15 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
16 | val itemView = LayoutInflater.from(parent.context).inflate(R.layout.item_order, parent, false)
17 | return ViewHolder(itemView)
18 | }
19 |
20 | override fun onBindViewHolder(holder: ViewHolder, position: Int) {
21 | holder.itemView.setOnClickListener { onStationClick.click(listStation[position]) }
22 | holder.textView.text = listStation[position].name
23 | }
24 |
25 | override fun getItemCount(): Int {
26 | return listStation.size
27 | }
28 | }
29 |
30 | interface OnStationClick {
31 | fun click(station: Station)
32 | }
33 |
34 | class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
35 |
36 | var textView: TextView
37 | var viewHandle: View
38 |
39 | init {
40 | textView = itemView.findViewById(R.id.textView) as TextView
41 | viewHandle = itemView.findViewById(R.id.viewHandle)
42 | viewHandle.visibility = INVISIBLE
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/res/values-pl/about.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Jeżeli chcesz nam pomóc podziel się uwagami bądź sugestiami dotyczącymi SmokSmoga dla Androida, na tej stronie
6 |
7 | https://groups.google.com/forum/#!forum/smoksmog-android
8 |
9 | lub zwyczajnie wysłać nam maila na adres
10 |
11 | smoksmog-android@googlegroups.com
12 |
13 | Będziemy wdzięczni za kontakt z nami przed napisaniem opinii na Google Play, nie dlatego, że nie lubimy tych opinii :-) ale dlatego, że często nie możemy, w wypadku problemów, nawiązać kontaktu z jej autorem.
14 |
15 | Oficjalną stronę projektu SmokSmog znajdziesz pod poniższym linkiem
16 |
17 | http://smoksmog.malopolska.pl/
18 |
19 | SmokSmog na Androida jest projektem Open Source, czyli kod źródłowy jest jawny i dostępny dla wszystkich, którzy chcieliby zweryfikować naszą pracę lub współtworzyć nasz projekt
20 |
21 | https://github.com/SmokSmog/smoksmog-android ]]>
22 |
--------------------------------------------------------------------------------
/network/src/main/kotlin/pl/malopolska/smoksmog/DateTimeDeserializer.kt:
--------------------------------------------------------------------------------
1 | package pl.malopolska.smoksmog
2 |
3 | import com.google.gson.JsonDeserializationContext
4 | import com.google.gson.JsonDeserializer
5 | import com.google.gson.JsonElement
6 | import com.google.gson.JsonParseException
7 | import org.joda.time.DateTime
8 | import org.joda.time.DateTimeZone
9 | import java.lang.reflect.Type
10 | import java.util.*
11 |
12 | class DateTimeDeserializer : JsonDeserializer {
13 |
14 | override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): DateTime {
15 |
16 | val input = json.asString
17 |
18 | val matcher = "(\\d+)-(\\d+)-(\\d+)\\s+?(\\d+):(\\d+):(\\d+)".toPattern().matcher(input)
19 |
20 | if (!matcher.matches()) {
21 | throw JsonParseException("Invalid date format")
22 | }
23 |
24 | val year = Integer.parseInt(matcher.group(1))
25 | val month = Integer.parseInt(matcher.group(2))
26 | val day = Integer.parseInt(matcher.group(3))
27 | val hour = Integer.parseInt(matcher.group(4))
28 | val minute = Integer.parseInt(matcher.group(5))
29 | val second = Integer.parseInt(matcher.group(6))
30 |
31 | val dateTime = DateTime.now(DateTimeZone.forTimeZone(TimeZone.getTimeZone("Europe/Warsaw")))
32 | .withYear(year)
33 | .withMonthOfYear(month)
34 | .withDayOfMonth(day)
35 | .withHourOfDay(hour)
36 | .withMinuteOfHour(minute)
37 | .withSecondOfMinute(second)
38 |
39 | return dateTime
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/dsl/DialogFramgmentExtension.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.dsl
2 |
3 | import android.app.FragmentManager
4 | import android.content.DialogInterface
5 | import android.support.v4.app.DialogFragment
6 | import android.support.v4.app.Fragment
7 | import android.support.v7.app.AlertDialog
8 | import android.support.v7.app.AppCompatActivity
9 | import android.support.v7.widget.Toolbar
10 | import android.view.LayoutInflater
11 | import com.antyzero.smoksmog.R
12 |
13 | fun AlertDialog.Builder.setToolbar(toolbarText: String): Toolbar {
14 | val toolbar = LayoutInflater.from(context).inflate(R.layout.dialog_toolbar, null) as Toolbar
15 | toolbar.setTitleTextColor(context.getCompatColor(smoksmog.R.color.text_light))
16 | toolbar.setBackgroundColor(context.getCompatColor(smoksmog.R.color.primaryDark))
17 | toolbar.title = toolbarText
18 | this.setCustomTitle(toolbar)
19 | return toolbar
20 | }
21 |
22 | fun AlertDialog.Builder.setPositiveButton(pair: Pair): AlertDialog.Builder {
23 | setPositiveButton(pair.first, pair.second)
24 | return this
25 | }
26 |
27 | fun AlertDialog.Builder.setNegativeButton(pair: Pair): AlertDialog.Builder {
28 | setNegativeButton(pair.first, pair.second)
29 | return this
30 | }
31 |
32 | fun Fragment.layoutInflater() = activity.layoutInflater
33 |
34 | fun DialogFragment.show(appCompatActivity: AppCompatActivity, tag: String) = this.show(appCompatActivity.supportFragmentManager, tag)
35 |
36 | fun android.app.DialogFragment.show(fragmentManager: FragmentManager) = this.show(fragmentManager, this.tag())
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/firebase/FirebaseEvents.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.firebase
2 |
3 | import android.os.Bundle
4 | import com.google.firebase.analytics.FirebaseAnalytics
5 |
6 | class FirebaseEvents(private val firebaseAnalytics: FirebaseAnalytics) {
7 |
8 | fun logStationCardInView(stationId: Long) {
9 | firebaseAnalytics.logEvent(FirebaseAnalytics.Event.VIEW_ITEM, Bundle().apply {
10 | setItemId(stationId)
11 | setContentType(Content.STATION)
12 | // TODO station name ?
13 | })
14 | }
15 |
16 | fun logWidgetCreationStarted() {
17 | firebaseAnalytics.logEvent("widget-station-creation-started", Bundle())
18 | }
19 |
20 | fun logWidgetCreationStation(stationId: Long, stationName: String) {
21 | firebaseAnalytics.logEvent("widget-station-creation-station-id", Bundle().apply {
22 | setItemId(stationId)
23 | setItemName(stationName)
24 | })
25 | }
26 |
27 | fun logWidgetCreationSuccessful() {
28 | firebaseAnalytics.logEvent("widget-station-creation-successful", Bundle())
29 | }
30 | }
31 |
32 | private fun Bundle.setContentType(content: Content) = this.putString(FirebaseAnalytics.Param.CONTENT_TYPE, content.toString())
33 | private fun Bundle.setItemId(id: Long) = putLong(FirebaseAnalytics.Param.ITEM_ID, id)
34 | private fun Bundle.setItemName(stationName: String) = this.putString(FirebaseAnalytics.Param.ITEM_NAME, stationName)
35 |
36 | private enum class Content(private val contentName: String) {
37 |
38 | STATION("station");
39 |
40 | override fun toString(): String {
41 | return contentName
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/i18n/time/PolishCountdown.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.i18n.time
2 |
3 | class PolishCountdown : Countdown {
4 |
5 | override fun get(givenSeconds: Int): String {
6 |
7 | val seconds = givenSeconds and 60
8 | val minutes = givenSeconds / 60 % 60
9 | val hours = givenSeconds / 60 / 60
10 |
11 | if (minutes == 0) {
12 | return secondsAgo(seconds)
13 | } else if (hours == 0) {
14 | return minutesAgo(minutes)
15 | }
16 |
17 | return hoursAgo(hours)
18 | }
19 |
20 | private fun hoursAgo(hours: Int): String {
21 | return ago(hours, "godzinę", "godziny", "godzin")
22 | }
23 |
24 | private fun minutesAgo(minutes: Int): String {
25 | return ago(minutes, "minutę", "minuty", "minut")
26 | }
27 |
28 | private fun secondsAgo(seconds: Int): String {
29 | return ago(seconds, "sekundę", "sekundy", "sekund")
30 | }
31 |
32 | private fun ago(amount: Int, single: String, some: String, many: String): String {
33 | if (amount == 1) {
34 | return amount.toString() + " " + single
35 | } else if (endsWithTwoToFour(amount)) {
36 | return amount.toString() + " " + some
37 | } else {
38 | return amount.toString() + " " + many
39 | }
40 | }
41 |
42 | private fun endsWithTwoToFour(seconds: Int): Boolean {
43 | val modulo = seconds % 10
44 | if (seconds >= 10 && seconds < 20) {
45 | return false
46 | } else if (modulo >= 2 && modulo <= 4) {
47 | return true
48 | }
49 | return false
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/network/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java'
2 | apply plugin: 'kotlin'
3 |
4 | sourceCompatibility = JavaVersion.VERSION_1_7
5 | targetCompatibility = JavaVersion.VERSION_1_7
6 |
7 | //noinspection GroovyAssignabilityCheck
8 | sourceSets {
9 | main.java.srcDirs += 'src/main/kotlin'
10 | }
11 |
12 | dependencies {
13 | compile("com.squareup.retrofit2:retrofit:${libVersions.square.retrofit}") {
14 | exclude module: 'okhttp'
15 | }
16 | compile "com.squareup.retrofit2:converter-gson:${libVersions.square.retrofit}"
17 | compile "com.squareup.retrofit2:adapter-rxjava:${libVersions.square.retrofit}"
18 |
19 | compile "com.squareup.okhttp3:okhttp:${libVersions.square.okhttp}"
20 | compile "io.reactivex:rxjava:${libVersions.rx.java}"
21 | compile 'com.fatboyindustrial.gson-jodatime-serialisers:gson-jodatime-serialisers:1.2.0'
22 | compile "joda-time:joda-time:${libVersions.jodaTime}"
23 | compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
24 |
25 | testCompile "junit:junit:${libVersions.junit}"
26 | testCompile "org.assertj:assertj-core:${libVersions.assertj}"
27 | testCompile "com.squareup.okhttp3:mockwebserver:${libVersions.square.okhttp}"
28 | }
29 |
30 | /**
31 | * Optionally disable test failures
32 | */
33 | test {
34 | ignoreFailures = rootProject.ext.ignoreFailures
35 | }
36 |
37 | /**
38 | * This will copy resources files so they will be accessible thought Java getResource() method
39 | * in Android Studio, without it test won't find *.json files
40 | */
41 | task copyTestResources(type: Copy) {
42 | from "${project.projectDir}/src/test/resources"
43 | into "${project.buildDir}/classes/test"
44 | }
45 |
46 | processTestResources.dependsOn copyTestResources
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_info_air_quality.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
18 |
19 |
23 |
24 |
28 |
29 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/dialog/InfoDialog.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.dialog
2 |
3 |
4 | import android.app.AlertDialog
5 | import android.app.Dialog
6 | import android.app.DialogFragment
7 | import android.app.FragmentManager
8 | import android.os.Bundle
9 | import android.view.View
10 | import com.antyzero.smoksmog.dsl.show
11 |
12 | /**
13 | * For info dialog
14 | */
15 | abstract class InfoDialog : DialogFragment() {
16 |
17 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
18 | val builder = AlertDialog.Builder(activity)
19 | val view = activity.layoutInflater.inflate(getLayoutId(), null, false)
20 | initView(view)
21 | builder.setView(view)
22 | builder.setPositiveButton(android.R.string.ok) { dialog, which -> dialog.dismiss() }
23 | return updateBuilder(builder).create()
24 | }
25 |
26 | protected open fun updateBuilder(builder: AlertDialog.Builder): AlertDialog.Builder {
27 | return builder
28 | }
29 |
30 | protected open fun initView(view: View) {
31 | // override if needed
32 | }
33 |
34 | protected abstract fun getLayoutId(): Int
35 |
36 | class Event(internal val dialogFragment: Class)
37 |
38 | companion object {
39 |
40 | fun show(fragmentManager: FragmentManager, event: Event<*>) {
41 | val infoDialog: InfoDialog
42 |
43 | try {
44 | infoDialog = event.dialogFragment.newInstance() as InfoDialog
45 | } catch (e: Exception) {
46 | throw IllegalStateException(
47 | "Problem with creating fragment dialog " + event.dialogFragment.simpleName, e)
48 | }
49 |
50 | infoDialog.show(fragmentManager)
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/screen/start/StationSlideAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.screen.start
2 |
3 | import android.app.FragmentManager
4 | import android.support.v13.app.FragmentStatePagerAdapter
5 | import android.support.v4.view.PagerAdapter
6 | import android.util.SparseArray
7 | import android.view.ViewGroup
8 | import com.antyzero.smoksmog.storage.model.Item
9 |
10 | import com.antyzero.smoksmog.ui.screen.start.fragment.StationFragment
11 |
12 | import java.lang.ref.WeakReference
13 |
14 | /**
15 | * Adapter for sliding pages left-right
16 | */
17 | class StationSlideAdapter(fragmentManager: FragmentManager, private val stationIds: List- ) : FragmentStatePagerAdapter(fragmentManager) {
18 |
19 | private val fragmentRegister = SparseArray?>()
20 |
21 | override fun getItemPosition(`object`: Any?): Int {
22 | return PagerAdapter.POSITION_NONE
23 | }
24 |
25 | override fun instantiateItem(container: ViewGroup, position: Int): Any {
26 | val fragment = super.instantiateItem(container, position) as StationFragment
27 | fragmentRegister.put(position, WeakReference(fragment))
28 | return fragment
29 | }
30 |
31 | override fun destroyItem(container: ViewGroup?, position: Int, `object`: Any) {
32 | fragmentRegister.remove(position)
33 | super.destroyItem(container, position, `object`)
34 | }
35 |
36 | override fun getItem(position: Int): StationFragment {
37 | return StationFragment.newInstance(stationIds[position].id)
38 | }
39 |
40 | fun getFragmentReference(position: Int): WeakReference? {
41 | return fragmentRegister.get(position)
42 | }
43 |
44 | override fun getCount(): Int {
45 | return stationIds.size
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_order.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
14 |
15 |
21 |
22 |
27 |
28 |
29 |
30 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/network/src/test/java/pl/malopolska/smoksmog/TestUtils.java:
--------------------------------------------------------------------------------
1 | package pl.malopolska.smoksmog;
2 |
3 | import java.io.IOException;
4 | import java.net.URL;
5 | import java.nio.charset.Charset;
6 | import java.nio.file.Files;
7 | import java.nio.file.Paths;
8 | import java.util.Collection;
9 | import java.util.Iterator;
10 | import java.util.List;
11 |
12 |
13 | public class TestUtils {
14 |
15 | private TestUtils() {
16 | throw new IllegalAccessError("Utils class");
17 | }
18 |
19 | public static String readFromResources(String pathFile) {
20 |
21 | String path = null;
22 |
23 | try {
24 | final URL resource = TestUtils.class.getResource(pathFile);
25 |
26 | if (resource == null) {
27 | throw new IllegalArgumentException("Unable to find resource for path \"" + pathFile + "\"");
28 | }
29 |
30 | path = resource.getPath();
31 |
32 | return readFileToString(path, Charset.defaultCharset());
33 | } catch (Exception e) {
34 | throw new RuntimeException("Unable to read resource file at " + path, e);
35 | }
36 | }
37 |
38 | public static String readFileToString(String path, Charset charset) throws IOException {
39 | List lines = Files.readAllLines(Paths.get(path), charset);
40 | return join("", lines);
41 | }
42 |
43 | private static String join(String delimiter, Collection> col) {
44 | StringBuilder sb = new StringBuilder();
45 | Iterator> iterator = col.iterator();
46 | if (iterator.hasNext())
47 | sb.append(iterator.next().toString());
48 | while (iterator.hasNext()) {
49 | sb.append(delimiter);
50 | sb.append(iterator.next().toString());
51 | }
52 | return sb.toString();
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/android/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #000
7 | #414152
8 | #515965
9 | #5A6273
10 | #CDCDCD
11 | #d6d6d6
12 | #FFFFFF
13 |
14 | #FFA439
15 |
16 | #5ADED5
17 | #2983AC
18 |
19 | #00cbff
20 | #00e600
21 | #ffff00
22 | #ff7e00
23 | #ff0000
24 | #800021
25 |
26 |
27 |
28 | #19d6d6d6
29 | #33d6d6d6
30 |
31 |
32 |
33 | @color/comet
34 | @color/brightGray
35 | @color/viking
36 |
37 | @color/celeste
38 |
39 | @color/white
40 | @color/scarpaFlow
41 |
42 | @color/neonCarrot
43 | @color/viking
44 |
45 | @color/capri
46 | @color/green
47 | @color/yellow
48 | @color/amber
49 | @color/red
50 | @color/burgundy
51 |
52 | @color/iron_10
53 |
54 |
55 |
--------------------------------------------------------------------------------
/domain/src/main/kotlin/com/antyzero/smoksmog/storage/model/Item.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.storage.model
2 |
3 | import com.google.gson.JsonDeserializer
4 | import com.google.gson.JsonObject
5 | import com.google.gson.JsonParseException
6 |
7 | sealed class Item(val id: Long, val modules: MutableSet) {
8 |
9 | @Suppress("unused")
10 | private val _class: String = javaClass.canonicalName
11 |
12 | /**
13 | * Single, if present this represent nearest station
14 | */
15 | class Nearest(modules: MutableSet = mutableSetOf()) : Item(ID_NEAREST, modules) {
16 |
17 | fun copy(modules: MutableSet = this.modules): Nearest = Nearest(modules)
18 | }
19 |
20 | /**
21 | * Multiple, this represent station item
22 | */
23 | class Station(id: Long = Long.MIN_VALUE, modules: MutableSet = mutableSetOf()) : Item(id, modules) {
24 |
25 | fun copy(id: Long = this.id, modules: MutableSet = this.modules): Station = Station(id, modules)
26 | }
27 |
28 | override fun equals(other: Any?): Boolean {
29 | if (this === other) return true
30 | if (other !is Item) return false
31 |
32 | if (id != other.id) return false
33 |
34 | return true
35 | }
36 |
37 | override fun hashCode(): Int = id.hashCode()
38 |
39 | companion object {
40 |
41 | private val ID_NEAREST = 0L
42 |
43 | fun deserializer(): JsonDeserializer
- = JsonDeserializer { json, typeOfT, context ->
44 |
45 | if (json is JsonObject) {
46 | val id = json.get("id").asLong
47 | return@JsonDeserializer when {
48 | id > 0 -> Station(id)
49 | id == 0L -> Nearest()
50 | else -> throw JsonParseException("Unsupported id value: $id")
51 | }
52 | }
53 | throw JsonParseException("Unable to parse")
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/widget_station.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
22 |
23 |
29 |
30 |
38 |
39 |
40 |
41 |
50 |
51 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/screen/about/AboutActivity.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.screen.about
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.os.Bundle
6 | import android.text.method.LinkMovementMethod
7 | import com.antyzero.smoksmog.R
8 | import com.antyzero.smoksmog.SmokSmogApplication
9 | import com.antyzero.smoksmog.dsl.compatFromHtml
10 | import com.antyzero.smoksmog.ui.BaseActivity
11 | import com.antyzero.smoksmog.ui.screen.ActivityModule
12 | import kotlinx.android.synthetic.main.dialog_info_about.*
13 | import smoksmog.logger.Logger
14 | import javax.inject.Inject
15 |
16 | class AboutActivity : BaseActivity() {
17 |
18 | @Inject lateinit var logger: Logger
19 |
20 | override fun onCreate(savedInstanceState: Bundle?) {
21 | super.onCreate(savedInstanceState)
22 | SmokSmogApplication[this].appComponent.plus(ActivityModule(this)).inject(this)
23 | setContentView(R.layout.dialog_info_about)
24 |
25 | textView.compatFromHtml(R.string.about)
26 | textView.movementMethod = LinkMovementMethod.getInstance()
27 |
28 | try {
29 | val packageInfo = packageManager.getPackageInfo(packageName, 0)
30 | textViewVersionName.text = getString(R.string.version_name_and_code,
31 | packageInfo.versionName,
32 | packageInfo.versionCode)
33 | } catch (e: Exception) {
34 | logger.i(TAG, "Problem with obtaining version", e)
35 | }
36 |
37 | setSupportActionBar(toolbar)
38 |
39 | supportActionBar?.setDisplayHomeAsUpEnabled(true)
40 | }
41 |
42 | companion object {
43 |
44 | private val TAG = "AboutActivity"
45 |
46 | fun start(context: Context) {
47 | context.startActivity(intent(context))
48 | }
49 |
50 | fun intent(context: Context): Intent {
51 | return Intent(context, AboutActivity::class.java)
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_info_facebook.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
18 |
19 |
24 |
25 |
29 |
30 |
38 |
39 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/Jenkinsfile:
--------------------------------------------------------------------------------
1 | #!groovy
2 | node {
3 |
4 | def ignoreFailures = (env.BRANCH_NAME != 'master')
5 |
6 | // Build start
7 | slackSend channel: 'quality', color: '#0080FF', message: "Started Android _${env.JOB_NAME}_ #${env.BUILD_NUMBER} (<${env.BUILD_URL}|Open>)", teamDomain: 'smoksmog', tokenCredentialId: 'smoksmok-slack'
8 |
9 | stage('Prepare'){
10 | dir('./build/'){deleteDir()}
11 | dir('./android/build/'){deleteDir()}
12 | dir('./app/build/'){deleteDir()}
13 | dir('./domain/build/'){deleteDir()}
14 | dir('./network/build/'){deleteDir()}
15 | checkout scm
16 | }
17 |
18 | try {
19 | stage('Build'){
20 |
21 | sh "./gradlew uninstallAll || true"
22 | sh "./gradlew assemble -PignoreFailures=" + ignoreFailures
23 | sh "wake-devices"
24 | sh "./gradlew check connectedCheck -PignoreFailures=" + ignoreFailures
25 |
26 | // Build successful
27 | slackSend channel: 'quality', color: '#80FF00', message: "Success Android _${env.JOB_NAME}_ #${env.BUILD_NUMBER} (<${env.BUILD_URL}|Open>)", teamDomain: 'smoksmog', tokenCredentialId: 'smoksmok-slack'
28 | }
29 | } catch (error) {
30 | // Build failed
31 | slackSend channel: 'quality', color: '#FF0000', message: "Failed Android _${env.JOB_NAME}_ #${env.BUILD_NUMBER} (<${env.BUILD_URL}|Open>)\n\n$error", teamDomain: 'smoksmog', tokenCredentialId: 'smoksmok-slack'
32 | throw new IllegalStateException(error, "Build failed")
33 | }
34 |
35 | stage('Artifacts'){
36 |
37 | androidLint canComputeNew: false, canRunOnFailed: true, defaultEncoding: '', healthy: '', pattern: '**/build/outputs/lint-results-*.xml', unHealthy: ''
38 |
39 | publishHTML([allowMissing: false, alwaysLinkToLastBuild: false, keepAll: false, reportDir: 'app/build/reports/androidTests/connected/', reportFiles: 'index.html', reportName: 'Android tests'])
40 |
41 | junit '**/build/**/TEST-*.xml'
42 | }
43 | }
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/dialog/AboutDialog.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.dialog
2 |
3 | import android.os.Bundle
4 | import android.text.method.LinkMovementMethod
5 | import android.view.View
6 | import android.widget.TextView
7 | import com.antyzero.smoksmog.R
8 | import com.antyzero.smoksmog.SmokSmogApplication
9 | import com.antyzero.smoksmog.dsl.compatFromHtml
10 | import com.antyzero.smoksmog.dsl.tag
11 | import com.antyzero.smoksmog.user.User
12 | import smoksmog.logger.Logger
13 | import javax.inject.Inject
14 |
15 | class AboutDialog : InfoDialog() {
16 |
17 | @Inject lateinit var logger: Logger
18 | @Inject lateinit var user: User
19 |
20 | lateinit var textView: TextView
21 | lateinit var textViewVersionName: TextView
22 | lateinit var textViewUserId: TextView
23 |
24 | override fun onCreate(savedInstanceState: Bundle?) {
25 | super.onCreate(savedInstanceState)
26 | SmokSmogApplication[activity]
27 | .appComponent
28 | .inject(this)
29 | }
30 |
31 | override fun getLayoutId(): Int = R.layout.dialog_info_about
32 |
33 | override fun initView(view: View) {
34 | super.initView(view)
35 |
36 | with(view) {
37 | textView = findViewById(R.id.textView) as TextView
38 | textViewUserId = findViewById(R.id.textViewUserId) as TextView
39 | textViewVersionName = findViewById(R.id.textViewVersionName) as TextView
40 | }
41 |
42 | textView.compatFromHtml(R.string.about)
43 | textView.movementMethod = LinkMovementMethod.getInstance()
44 |
45 | try {
46 | val packageInfo = activity.packageManager.getPackageInfo(activity.packageName, 0)
47 | textViewVersionName.text = getString(R.string.version_name_and_code,
48 | packageInfo.versionName,
49 | packageInfo.versionCode)
50 | } catch (e: Exception) {
51 | logger.i(tag(), "Problem with obtaining version", e)
52 | }
53 |
54 | textViewUserId.text = user.identifier
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/screen/start/fragment/NetworkStationFragment.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.screen.start.fragment
2 |
3 | import android.os.Bundle
4 | import com.antyzero.smoksmog.R
5 | import com.antyzero.smoksmog.SmokSmogApplication
6 | import com.antyzero.smoksmog.ui.screen.ActivityModule
7 | import com.antyzero.smoksmog.ui.screen.FragmentModule
8 | import com.trello.rxlifecycle.android.FragmentEvent
9 | import pl.malopolska.smoksmog.model.Station
10 | import rx.android.schedulers.AndroidSchedulers
11 | import rx.schedulers.Schedulers
12 |
13 | class NetworkStationFragment : StationFragment() {
14 |
15 | override fun onActivityCreated(savedInstanceState: Bundle?) {
16 | super.onActivityCreated(savedInstanceState)
17 |
18 | val activity = activity
19 |
20 | SmokSmogApplication[activity].appComponent
21 | .plus(ActivityModule(activity))
22 | .plus(FragmentModule(this))
23 | .inject(this)
24 |
25 | loadData()
26 | }
27 |
28 | override fun loadData() {
29 | api.station(stationId)
30 | .doOnSubscribe { showLoading() }
31 | .subscribeOn(Schedulers.newThread())
32 | .observeOn(AndroidSchedulers.mainThread())
33 | .compose(bindUntilEvent(FragmentEvent.DESTROY_VIEW))
34 | .cast(Station::class.java)
35 | .subscribe(
36 | { station -> updateUI(station) }
37 | ) { throwable ->
38 | try {
39 | showTryAgain(R.string.error_unable_to_load_station_data)
40 | } catch (e: Exception) {
41 | logger.e(TAG, "Problem with error handling code", e)
42 | } finally {
43 | logger.i(TAG, "Unable to load station data (stationID:$stationId)", throwable)
44 | }
45 | }
46 | }
47 |
48 | companion object {
49 |
50 | private val TAG = NetworkStationFragment::class.java.simpleName
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/domain/src/integrationTest/kotlin/com/antyzero/smoksmog/DataCollectionTest.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog
2 |
3 | import com.antyzero.smoksmog.location.LocationProvider
4 | import com.antyzero.smoksmog.model.Page
5 | import com.antyzero.smoksmog.storage.JsonFileStorage
6 | import com.antyzero.smoksmog.storage.PersistentStorage
7 | import com.antyzero.smoksmog.storage.model.Item
8 | import org.junit.After
9 | import org.junit.Before
10 | import org.junit.Test
11 | import org.mockito.Mock
12 | import org.mockito.MockitoAnnotations
13 | import pl.malopolska.smoksmog.Api
14 | import pl.malopolska.smoksmog.RestClient
15 | import rx.observers.TestSubscriber
16 | import java.io.File
17 |
18 | class DataCollectionTest {
19 |
20 | @Mock
21 | lateinit private var locationProvider: LocationProvider
22 | lateinit private var storage: PersistentStorage
23 | lateinit private var smokSmog: SmokSmog
24 |
25 | private var api: Api = RestClient.Builder().build()
26 |
27 | @Before
28 | fun setUp() {
29 | MockitoAnnotations.initMocks(this)
30 | val file = File.createTempFile(StringRandom().random(8), "json").apply { delete() }
31 | storage = JsonFileStorage(file)
32 | smokSmog = SmokSmog(api, storage, locationProvider)
33 | }
34 |
35 | @After
36 | fun tearDown() {
37 | storage.clear()
38 | }
39 |
40 | @Test
41 | fun collectForSingle() {
42 | val testSubscriber = TestSubscriber()
43 |
44 | smokSmog.collectDataForItem(Item.Station(13)).subscribe(testSubscriber)
45 |
46 | testSubscriber.assertNoErrors()
47 | testSubscriber.assertCompleted()
48 | testSubscriber.assertValueCount(1)
49 | }
50 |
51 | @Test
52 | fun fetchAll() {
53 | val testSubscriber = TestSubscriber()
54 | smokSmog.storage.addStation(13)
55 | smokSmog.storage.addStation(17)
56 |
57 | smokSmog.collectData().subscribe(testSubscriber)
58 |
59 | testSubscriber.assertNoErrors()
60 | testSubscriber.assertCompleted()
61 | testSubscriber.assertValueCount(2)
62 | }
63 | }
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/antyzero/smoksmog/screen/StartActivityTest.java:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.screen;
2 |
3 | import android.support.test.InstrumentationRegistry;
4 | import android.support.test.rule.ActivityTestRule;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import com.antyzero.smoksmog.R;
8 | import com.antyzero.smoksmog.rules.RxSchedulerTestRule;
9 | import com.antyzero.smoksmog.rules.SpoonRule;
10 | import com.antyzero.smoksmog.ui.screen.start.StartActivity;
11 |
12 | import org.junit.Rule;
13 | import org.junit.Test;
14 | import org.junit.rules.RuleChain;
15 | import org.junit.rules.TestRule;
16 | import org.junit.runner.RunWith;
17 |
18 | import static android.support.test.espresso.Espresso.onView;
19 | import static android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu;
20 | import static android.support.test.espresso.action.ViewActions.click;
21 | import static android.support.test.espresso.matcher.ViewMatchers.withId;
22 | import static android.support.test.espresso.matcher.ViewMatchers.withText;
23 |
24 |
25 | @RunWith(AndroidJUnit4.class)
26 | public class StartActivityTest {
27 |
28 | @Rule
29 | public final RxSchedulerTestRule rxSchedulerTestRule = new RxSchedulerTestRule();
30 | private final ActivityTestRule activityTestRule = new MockedNetworkActivityTestRule<>(StartActivity.class, false, true);
31 | private final SpoonRule spoonRule = new SpoonRule(activityTestRule);
32 | @Rule
33 | public final TestRule testRule = RuleChain.outerRule(activityTestRule).around(spoonRule);
34 |
35 | @Test
36 | public void checkCreation() {
37 | spoonRule.screenshot("Creation");
38 | }
39 |
40 | // @Test
41 | public void addStation() {
42 |
43 | // Given
44 | InstrumentationRegistry.getInstrumentation().waitForIdleSync();
45 |
46 | // When
47 | openActionBarOverflowOrOptionsMenu(InstrumentationRegistry.getTargetContext());
48 | onView(withText(R.string.action_manage_stations)).perform(click());
49 | onView(withId(R.id.fab)).perform(click());
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | compileSdkVersion androidVars.compileSdkVersion
6 | buildToolsVersion androidVars.buildToolsVersion
7 |
8 | compileOptions {
9 | sourceCompatibility commonVars.javaVersion
10 | targetCompatibility commonVars.javaVersion
11 | }
12 |
13 | defaultConfig {
14 | minSdkVersion androidVars.minSdkVersion
15 | targetSdkVersion androidVars.targetSdkVersion
16 | versionCode 1
17 | versionName "0.0.1"
18 | }
19 |
20 | buildTypes {
21 | release {
22 | minifyEnabled false
23 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
24 | }
25 | }
26 |
27 | packagingOptions {
28 | exclude 'META-INF/LICENSE'
29 | exclude 'META-INF/LICENSE.txt'
30 | exclude 'META-INF/NOTICE'
31 | exclude 'META-INF/NOTICE.txt'
32 | exclude 'META-INF/services/javax.annotation.processing.Processor'
33 | }
34 |
35 | lintOptions {
36 | disable 'InvalidPackage', 'GoogleAppIndexingWarning'
37 | }
38 |
39 | sourceSets {
40 | main.java.srcDirs += 'src/main/kotlin'
41 | }
42 | }
43 |
44 | dependencies {
45 | compile project(':network')
46 |
47 | // UI oriented
48 | compile "com.android.support:recyclerview-v7:${libVersions.android.appCompat}"
49 |
50 | // General
51 | compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
52 | compile "io.reactivex:rxjava:${libVersions.rx.java}"
53 | compile "io.reactivex:rxjava-math:${libVersions.rx.javaMath}"
54 | compile "io.reactivex:rxandroid:${libVersions.rx.android}"
55 | compile "io.reactivex:rxkotlin:${libVersions.rx.kotlin}"
56 | compile "com.trello:rxlifecycle:${libVersions.rx.lifecycle}"
57 | compile "com.trello:rxlifecycle-components:${libVersions.rx.lifecycle}"
58 |
59 | testCompile "junit:junit:${libVersions.junit}"
60 | testCompile 'org.assertj:assertj-core:2.6.0'
61 | }
62 |
63 | repositories {
64 | mavenCentral()
65 | }
66 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/smoksmog/logger/LevelBlockingLogger.kt:
--------------------------------------------------------------------------------
1 | package smoksmog.logger
2 |
3 | import android.util.Log
4 |
5 | class LevelBlockingLogger(private val logger: Logger, private val lowestLevelToLog: Int) : Logger {
6 |
7 | override fun v(tag: String, message: String) {
8 | if (lowestLevelToLog >= Log.VERBOSE) {
9 | logger.v(tag, message)
10 | }
11 | }
12 |
13 | override fun v(tag: String, message: String, throwable: Throwable) {
14 | if (lowestLevelToLog >= Log.VERBOSE) {
15 | logger.v(tag, message, throwable)
16 | }
17 | }
18 |
19 | override fun d(tag: String, message: String) {
20 | if (lowestLevelToLog >= Log.DEBUG) {
21 | logger.d(tag, message)
22 | }
23 | }
24 |
25 | override fun d(tag: String, message: String, throwable: Throwable) {
26 | if (lowestLevelToLog >= Log.DEBUG) {
27 | logger.d(tag, message, throwable)
28 | }
29 | }
30 |
31 | override fun i(tag: String, message: String) {
32 | if (lowestLevelToLog >= Log.INFO) {
33 | logger.i(tag, message)
34 | }
35 | }
36 |
37 | override fun i(tag: String, message: String, throwable: Throwable) {
38 | if (lowestLevelToLog >= Log.INFO) {
39 | logger.i(tag, message, throwable)
40 | }
41 | }
42 |
43 | override fun w(tag: String, message: String) {
44 | if (lowestLevelToLog >= Log.WARN) {
45 | logger.w(tag, message)
46 | }
47 | }
48 |
49 | override fun w(tag: String, message: String, throwable: Throwable) {
50 | if (lowestLevelToLog >= Log.WARN) {
51 | logger.w(tag, message, throwable)
52 | }
53 | }
54 |
55 | override fun e(tag: String, message: String) {
56 | if (lowestLevelToLog >= Log.ERROR) {
57 | logger.e(tag, message)
58 | }
59 | }
60 |
61 | override fun e(tag: String, message: String, throwable: Throwable) {
62 | if (lowestLevelToLog >= Log.ERROR) {
63 | logger.e(tag, message, throwable)
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/antyzero/smoksmog/migration/OldToNewStationListTest.java:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.migration;
2 |
3 |
4 | import org.junit.Test;
5 | import org.junit.runner.RunWith;
6 |
7 | import android.annotation.SuppressLint;
8 | import android.content.Context;
9 | import android.support.test.InstrumentationRegistry;
10 | import android.support.test.runner.AndroidJUnit4;
11 |
12 | import com.antyzero.smoksmog.permission.PermissionHelper;
13 | import com.antyzero.smoksmog.settings.SettingsHelper;
14 | import com.antyzero.smoksmog.storage.JsonFileStorage;
15 | import com.antyzero.smoksmog.storage.model.Item;
16 |
17 | import java.util.ArrayList;
18 | import java.util.Arrays;
19 | import java.util.List;
20 |
21 | import static org.assertj.core.api.Assertions.assertThat;
22 |
23 | @RunWith(AndroidJUnit4.class)
24 | public class OldToNewStationListTest {
25 |
26 | @SuppressLint("CommitPrefEdits")
27 | @Test
28 | public void simpleMigration() throws Exception {
29 |
30 | // Given
31 | List longList = new ArrayList<>(Arrays.asList(4L, 3L, 2L));
32 | Context context = InstrumentationRegistry.getTargetContext();
33 | SettingsHelper settingsHelper = new SettingsHelper(context);
34 | settingsHelper.getPreferences().edit().clear().commit();
35 | settingsHelper.setStationIdList(longList);
36 | JsonFileStorage jsonFileStorage = new JsonFileStorage();
37 | jsonFileStorage.clear();
38 |
39 | // When
40 | for (Long id : settingsHelper.getStationIdList()) {
41 | jsonFileStorage.addStation(id);
42 | }
43 | settingsHelper.getPreferences().edit().clear().commit();
44 | settingsHelper.getStationIdList().clear();
45 |
46 | // Then
47 | List
- items = jsonFileStorage.fetchAll();
48 | assertThat(items).hasSize(3);
49 | assertThat(items.get(0).getId()).isEqualTo(4);
50 | assertThat(items.get(1).getId()).isEqualTo(3);
51 | assertThat(items.get(2).getId()).isEqualTo(2);
52 |
53 | assertThat(settingsHelper.getStationIdList()).hasSize(0);
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/widget/StationWidget.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.widget
2 |
3 | import android.appwidget.AppWidgetManager
4 | import android.appwidget.AppWidgetProvider
5 | import android.content.Context
6 | import android.widget.RemoteViews
7 | import com.antyzero.smoksmog.BuildConfig
8 | import com.antyzero.smoksmog.R
9 | import com.antyzero.smoksmog.dsl.setBackgroundColor
10 | import org.joda.time.DateTime
11 | import pl.malopolska.smoksmog.model.Station
12 | import smoksmog.air.AirQuality
13 | import smoksmog.air.AirQualityIndex
14 |
15 | class StationWidget : AppWidgetProvider() {
16 |
17 | override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
18 | appWidgetIds.forEach {
19 | StationWidgetService.update(context, it)
20 | }
21 | }
22 |
23 | companion object {
24 |
25 | fun updateWidget(widgetId: Int, context: Context, appWidgetManager: AppWidgetManager, station: Station) {
26 |
27 | val airQualityIndex = AirQualityIndex.calculate(station)
28 | val airQuality = AirQuality.Companion.findByValue(airQualityIndex)
29 |
30 | val views = RemoteViews(context.packageName, R.layout.widget_station)
31 |
32 | var name = station.name
33 |
34 | if (BuildConfig.DEBUG) {
35 | name = "Stacja: ${station.name}\nPomiar: ${station.particulates[0].date}\nAktulizacja: ${DateTime.now()}"
36 | }
37 |
38 | views.setTextViewText(R.id.textViewStation, name)
39 | views.setTextViewText(R.id.textViewAirQuality, airQualityIndex.format(1))
40 |
41 | views.setTextColor(R.id.textViewAirQuality, airQuality.getColor(context))
42 | views.setBackgroundColor(R.id.airIndicator1, airQuality.getColor(context))
43 | views.setBackgroundColor(R.id.airIndicator2, airQuality.getColor(context))
44 |
45 | appWidgetManager.updateAppWidget(widgetId, views)
46 | }
47 | }
48 | }
49 |
50 | private fun Double.format(digits: Int): CharSequence {
51 | return java.lang.String.format("%.${digits}f", this)
52 | }
53 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/smoksmog/logger/AggregatingLogger.kt:
--------------------------------------------------------------------------------
1 | package smoksmog.logger
2 |
3 |
4 | import java.util.Arrays
5 |
6 | class AggregatingLogger(private val loggerCollection: Collection) : Logger {
7 |
8 | constructor(vararg logger: Logger) : this(Arrays.asList(*logger)) {
9 | }
10 |
11 | override fun v(tag: String, message: String) {
12 | for (logger in loggerCollection) {
13 | logger.v(tag, message)
14 | }
15 | }
16 |
17 | override fun v(tag: String, message: String, throwable: Throwable) {
18 | for (logger in loggerCollection) {
19 | logger.v(tag, message, throwable)
20 | }
21 | }
22 |
23 | override fun d(tag: String, message: String) {
24 | for (logger in loggerCollection) {
25 | logger.d(tag, message)
26 | }
27 | }
28 |
29 | override fun d(tag: String, message: String, throwable: Throwable) {
30 | for (logger in loggerCollection) {
31 | logger.d(tag, message, throwable)
32 | }
33 | }
34 |
35 | override fun i(tag: String, message: String) {
36 | for (logger in loggerCollection) {
37 | logger.i(tag, message)
38 | }
39 | }
40 |
41 | override fun i(tag: String, message: String, throwable: Throwable) {
42 | for (logger in loggerCollection) {
43 | logger.i(tag, message, throwable)
44 | }
45 | }
46 |
47 | override fun w(tag: String, message: String) {
48 | for (logger in loggerCollection) {
49 | logger.w(tag, message)
50 | }
51 | }
52 |
53 | override fun w(tag: String, message: String, throwable: Throwable) {
54 | for (logger in loggerCollection) {
55 | logger.w(tag, message, throwable)
56 | }
57 | }
58 |
59 | override fun e(tag: String, message: String) {
60 | for (logger in loggerCollection) {
61 | logger.e(tag, message)
62 | }
63 | }
64 |
65 | override fun e(tag: String, message: String, throwable: Throwable) {
66 | for (logger in loggerCollection) {
67 | logger.e(tag, message, throwable)
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/network/src/main/kotlin/pl/malopolska/smoksmog/utils/StationUtils.kt:
--------------------------------------------------------------------------------
1 | package pl.malopolska.smoksmog.utils
2 |
3 |
4 | import pl.malopolska.smoksmog.model.Station
5 | import rx.Observable
6 |
7 | class StationUtils private constructor() {
8 |
9 | init {
10 | throw IllegalAccessError("Utils class")
11 | }
12 |
13 | companion object {
14 |
15 | fun convertStationsToIdsList(stationList: List): List {
16 | return Observable.from(stationList).map { station -> station.id }.toList().toBlocking().first()
17 | }
18 |
19 | fun convertStationsToIdsArray(stations: Collection): LongArray {
20 | val result = LongArray(stations.size)
21 | var i = 0
22 | for ((id) in stations) {
23 | result[i] = id
24 | i++
25 | }
26 | return result
27 | }
28 |
29 | fun findClosest(stations: Collection, latitude: Double, longitude: Double): Station {
30 |
31 | var closestStation: Station = stations.first()
32 | var distance = java.lang.Double.MAX_VALUE
33 |
34 | for (station in stations) {
35 |
36 | val calculatedDistance = distanceInRadians(
37 | station.latitude.toDouble(), station.longitude.toDouble(),
38 | latitude, longitude)
39 |
40 | if (calculatedDistance < distance) {
41 | closestStation = station
42 | distance = calculatedDistance
43 | }
44 | }
45 |
46 | return closestStation
47 | }
48 |
49 | private fun distanceInRadians(latitude1: Double, longitude1: Double, latitude2: Double, longitude2: Double): Double {
50 |
51 | val x1 = Math.toRadians(latitude1)
52 | val y1 = Math.toRadians(longitude1)
53 | val x2 = Math.toRadians(latitude2)
54 | val y2 = Math.toRadians(longitude2)
55 |
56 | // great circle distance in radians
57 | return Math.acos(Math.sin(x1) * Math.sin(x2) + Math.cos(x1) * Math.cos(x2) * Math.cos(y1 - y2))
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/smoksmog/air/AirQualityIndex.kt:
--------------------------------------------------------------------------------
1 | package smoksmog.air
2 |
3 | import pl.malopolska.smoksmog.model.Particulate
4 | import pl.malopolska.smoksmog.model.ParticulateEnum.*
5 | import pl.malopolska.smoksmog.model.Station
6 |
7 | object AirQualityIndex {
8 |
9 | fun calculate(station: Station): Double {
10 | return calculate(station.particulates)
11 | }
12 |
13 | fun calculate(particulate: Particulate): Double = calculate(listOf(particulate))
14 |
15 | fun calculate(particulates: Collection): Double {
16 |
17 | var index = 0.0f
18 |
19 | for (particulate in particulates) {
20 |
21 | var particulateValue = 0.0f
22 |
23 | when (particulate.enum) {
24 | PM10 -> particulateValue = particulate.value / 20
25 | PM25 -> particulateValue = particulate.value / 12
26 | SO2 -> particulateValue = particulate.value / 70
27 | NO2 -> particulateValue = particulate.value / 40
28 | CO -> particulateValue = particulate.value / 2000
29 | O3 -> particulateValue = particulate.value / 24
30 | C6H6 -> particulateValue = calculateBenzene(particulate.value)
31 | else -> {
32 | // do nothing
33 | }
34 | }
35 |
36 | index = Math.max(index, particulateValue)
37 | }
38 |
39 | return index.toDouble()
40 | }
41 |
42 | fun calculateBenzene(particulate: Float): Float {
43 | val value = Math.max(particulate, 0f)
44 | return when (value) {
45 | in 0f until 5f -> value * 0.2f// Index 0-1
46 | in 5f until 20f -> value * 0.4f - 1 // Index 1-7
47 | else -> value * 0.1f + 5 // Index 7 and above
48 | }
49 | }
50 | }
51 |
52 | private class FloatRange(override val start: Float, override val endInclusive: Float) : ClosedRange {
53 | override fun contains(value: Float): Boolean = value >= start && value < endInclusive
54 | }
55 |
56 | private infix fun Float.until(to: Float): FloatRange {
57 | if (this > to) throw IllegalArgumentException("The to argument value '$to' was too small.")
58 | return FloatRange(this, to)
59 | }
--------------------------------------------------------------------------------
/android/src/main/kotlin/smoksmog/air/AirQuality.kt:
--------------------------------------------------------------------------------
1 | package smoksmog.air
2 |
3 | import android.content.Context
4 | import android.support.annotation.ColorRes
5 | import android.support.annotation.StringRes
6 |
7 | import smoksmog.R
8 |
9 | /**
10 |
11 | */
12 | enum class AirQuality constructor(@ColorRes val colorResId: Int, @StringRes val titleResId: Int) : ValueCheck {
13 |
14 | VERY_GOOD(R.color.airQualityVeryGood, R.string.airQualityVeryGood) {
15 | override fun isValueInRange(value: Double): Boolean {
16 | return value <= 1
17 | }
18 | },
19 |
20 | GOOD(R.color.airQualityGood, R.string.airQualityGood) {
21 | override fun isValueInRange(value: Double): Boolean {
22 | return value > 1 && value <= 3
23 | }
24 | },
25 |
26 | MODERATE(R.color.airQualityModerate, R.string.airQualityModerate) {
27 | override fun isValueInRange(value: Double): Boolean {
28 | return value > 3 && value <= 5
29 | }
30 | },
31 |
32 | SUFFICIENT(R.color.airQualitySufficient, R.string.airQualitySufficient) {
33 | override fun isValueInRange(value: Double): Boolean {
34 | return value > 5 && value <= 7
35 | }
36 | },
37 |
38 | BAD(R.color.airQualityBad, R.string.airQualityBad) {
39 | override fun isValueInRange(value: Double): Boolean {
40 | return value > 7 && value <= 10
41 | }
42 | },
43 |
44 | VERY_BAD(R.color.airQualityVeryBad, R.string.airQualityVeryBad) {
45 | override fun isValueInRange(value: Double): Boolean {
46 | return value > 10
47 | }
48 | };
49 |
50 | fun getTitle(context: Context): CharSequence {
51 | return context.getString(titleResId)
52 | }
53 |
54 | fun getColor(context: Context): Int {
55 | // Deprecated since 23 API, quite fresh
56 | @Suppress("DEPRECATION")
57 | return context.resources.getColor(colorResId)
58 | }
59 |
60 | companion object {
61 |
62 | fun findByValue(value: Double): AirQuality {
63 | values().filter { it.isValueInRange(value) }.forEach { return it }
64 | throw IllegalStateException("Unable to find AirQuality for given value ($value)")
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ui/widget/StationWidgetService.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.ui.widget
2 |
3 | import android.app.IntentService
4 | import android.appwidget.AppWidgetManager
5 | import android.content.ComponentName
6 | import android.content.Context
7 | import android.content.Intent
8 | import com.antyzero.smoksmog.dsl.appComponent
9 | import com.antyzero.smoksmog.dsl.appWidgetManager
10 | import com.antyzero.smoksmog.dsl.tag
11 | import pl.malopolska.smoksmog.Api
12 | import smoksmog.logger.Logger
13 | import javax.inject.Inject
14 |
15 |
16 | class StationWidgetService : IntentService(StationWidgetService::class.java.simpleName) {
17 |
18 | @Inject lateinit var api: Api
19 | @Inject lateinit var widgetData: StationWidgetData
20 | @Inject lateinit var logger: Logger
21 |
22 | lateinit var appWidgetManager: AppWidgetManager
23 |
24 | override fun onCreate() {
25 | super.onCreate()
26 | appComponent().inject(this)
27 | this.appWidgetManager = appWidgetManager()
28 | }
29 |
30 | override fun onHandleIntent(intent: Intent) {
31 | if (intent.hasExtra(EXTRA_WIDGET_ID)) {
32 | val widgetId = intent.getIntExtra(EXTRA_WIDGET_ID, Int.MIN_VALUE)
33 | try {
34 | val stationId = widgetData.widgetStationId(widgetId)
35 | val station = api.station(stationId).toBlocking().first()
36 | StationWidget.updateWidget(widgetId, this, appWidgetManager, station)
37 | } catch (e: Exception) {
38 | logger.w(tag(), "Unable to update widget $widgetId", e)
39 | }
40 | }
41 | }
42 |
43 | companion object {
44 |
45 | internal val EXTRA_WIDGET_ID = "widgetId"
46 |
47 | fun update(context: Context, widgetId: Int) {
48 | val intent = Intent(context, StationWidgetService::class.java)
49 | intent.putExtra(EXTRA_WIDGET_ID, widgetId)
50 | context.startService(intent)
51 | }
52 |
53 | fun updateAll(context: Context) {
54 | context.appWidgetManager().getAppWidgetIds(ComponentName(context, StationWidget::class.java)).forEach {
55 | StationWidgetService.update(context, it)
56 | }
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/about.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Jeżeli chcesz nam pomóc podziel się uwagami bądź sugestiami dotyczącymi SmokSmoga dla Androida, na tej stronie
6 |
7 | https://groups.google.com/forum/#!forum/smoksmog-android
8 |
9 | lub zwyczajnie wysłać nam maila na adres
10 |
11 | smoksmog-android@googlegroups.com
12 |
13 | Będziemy wdzięczni za kontakt z nami przed napisaniem opinii na Google Play, nie dlatego, że nie lubimy tych opinii :-) ale dlatego, że często nie możemy, w wypadku problemów, nawiązać kontaktu z jej autorem.
14 |
15 | Oficjalną stronę projektu SmokSmog znajdziesz pod poniższym linkiem
16 |
17 | http://smoksmog.malopolska.pl/
18 |
19 | SmokSmog na Androida jest projektem Open Source, czyli kod źródłowy jest jawny i dostępny dla wszystkich, którzy chcieliby zweryfikować naszą pracę lub współtworzyć nasz projekt
20 |
21 | https://github.com/SmokSmog/smoksmog-android ]]>
22 |
23 |
24 |
25 | Obliczany jest na podstawie najnowszych pomiarów, pochodzących z ostatniej godziny.
26 |
27 | Więcej na temat indeksu jakości powietrza]]>
28 |
29 |
--------------------------------------------------------------------------------
/domain/src/test/kotlin/com/antyzero/smoksmog/SmokSmogTest.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog
2 |
3 | import com.antyzero.smoksmog.location.Location
4 | import com.antyzero.smoksmog.location.Location.Position
5 | import com.antyzero.smoksmog.location.LocationProvider
6 | import com.antyzero.smoksmog.storage.PersistentStorage
7 | import com.nhaarman.mockito_kotlin.doReturn
8 | import com.nhaarman.mockito_kotlin.mock
9 | import org.assertj.core.api.Assertions.assertThat
10 | import org.junit.Test
11 | import pl.malopolska.smoksmog.Api
12 | import pl.malopolska.smoksmog.model.Station
13 | import rx.Observable
14 | import rx.observers.TestSubscriber
15 |
16 | class SmokSmogTest {
17 |
18 | @Test
19 | fun nearestStation() {
20 | val latitude = 49.617454
21 | val longitude = 20.715333
22 | val testSubscriber = TestSubscriber()
23 | val locationProvider = mock {
24 | on { location() } doReturn Position(latitude to longitude).observable()
25 | }
26 | val api = mock {
27 | on { stationByLocation(latitude, longitude) } doReturn Station(
28 | 6, "Nowy Sącz", latitude = latitude.toFloat(), longitude = longitude.toFloat()).observable()
29 | }
30 | val smokSmog = SmokSmog(api, mock {}, locationProvider)
31 |
32 | smokSmog.nearestStation().subscribe(testSubscriber)
33 |
34 | testSubscriber.assertNoErrors()
35 | testSubscriber.assertCompleted()
36 | testSubscriber.assertValueCount(1)
37 | assertThat(testSubscriber.onNextEvents[0].id).isEqualTo(6)
38 | }
39 |
40 | @Test
41 | fun locationUnknown() {
42 | val testSubscriber = TestSubscriber()
43 | val locationProvider = mock {
44 | on { location() } doReturn Location.Unknown().observable()
45 | }
46 | val smokSmog = SmokSmog(mock { }, mock { }, locationProvider)
47 |
48 | smokSmog.nearestStation().subscribe(testSubscriber)
49 |
50 | testSubscriber.assertNoErrors()
51 | testSubscriber.assertCompleted()
52 | testSubscriber.assertValueCount(0)
53 | }
54 | }
55 |
56 | private fun T.observable() = Observable.just(this)
--------------------------------------------------------------------------------
/domain/src/test/kotlin/com/antyzero/smoksmog/storage/PersistentStorageTest.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.storage
2 |
3 | import com.antyzero.smoksmog.storage.model.Item
4 | import com.antyzero.smoksmog.storage.model.Module
5 | import org.assertj.core.api.Assertions
6 | import org.junit.Test
7 | import java.io.File
8 |
9 |
10 | class PersistentStorageTest {
11 |
12 | private val storageFile = File.createTempFile("pref", "json")
13 |
14 | fun createStorageInstance() = JsonFileStorage(storageFile)
15 |
16 | @Test
17 | fun persistenceNotCleaned() {
18 | val storageOne: PersistentStorage = createStorageInstance()
19 | storageOne.add(Item.Station(1, mutableSetOf(
20 | Module.AirQualityIndex(Module.AirQualityIndex.Type.POLISH),
21 | Module.Measurements()
22 | )))
23 | storageOne.addStation(2)
24 | storageOne.update(2, Item.Station(modules = mutableSetOf(
25 | Module.Measurements()
26 | )))
27 |
28 | val storageTwo = createStorageInstance()
29 |
30 | Assertions.assertThat(storageTwo.fetchAll()).hasSize(2)
31 | Assertions.assertThat(storageTwo.fetchAll()[0].id).isEqualTo(1)
32 | Assertions.assertThat(storageTwo.fetchAll()[0].modules).hasSize(2)
33 | Assertions.assertThat(storageTwo.fetchAll()[0].modules.toList()[0]).isInstanceOf(Module.AirQualityIndex::class.java)
34 | Assertions.assertThat(storageTwo.fetchAll()[0].modules.toList()[1]).isInstanceOf(Module.Measurements::class.java)
35 | Assertions.assertThat(storageTwo.fetchAll()[1].id).isEqualTo(2)
36 | Assertions.assertThat(storageTwo.fetchAll()[1].modules).hasSize(1)
37 | Assertions.assertThat(storageTwo.fetchAll()[1].modules.toList()[0]).isInstanceOf(Module.Measurements::class.java)
38 | }
39 |
40 | @Test
41 | fun persistenceCleaned() {
42 | val storageOne: PersistentStorage = createStorageInstance()
43 | storageOne.add(Item.Station(1, mutableSetOf(
44 | Module.AirQualityIndex(Module.AirQualityIndex.Type.POLISH),
45 | Module.Measurements()
46 | )))
47 | storageOne.clear()
48 |
49 | val storageTwo = createStorageInstance()
50 |
51 | Assertions.assertThat(storageTwo.fetchAll()).hasSize(0)
52 | }
53 | }
--------------------------------------------------------------------------------
/domain/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java'
2 | apply plugin: 'kotlin'
3 |
4 | sourceCompatibility = JavaVersion.VERSION_1_7
5 | targetCompatibility = JavaVersion.VERSION_1_7
6 |
7 | //noinspection GroovyAssignabilityCheck
8 | sourceSets {
9 | main.java.srcDirs += 'src/main/kotlin'
10 |
11 | integrationTest {
12 | java {
13 | compileClasspath += main.output + test.output
14 | runtimeClasspath += main.output + test.output
15 | srcDir file('src/integrationTest/kotlin')
16 | }
17 | resources.srcDir file('src/integrationTest/resources')
18 | }
19 | }
20 |
21 | configurations {
22 | integrationTestCompile.extendsFrom testCompile
23 | integrationTestRuntime.extendsFrom testRuntime
24 | }
25 |
26 | test {
27 | testLogging {
28 | events "passed", "skipped", "failed", "standardOut"
29 | showExceptions true
30 | exceptionFormat "full"
31 | showCauses true
32 | showStackTraces true
33 | }
34 | }
35 |
36 | task integrationTest(type: Test) {
37 | testClassesDir = sourceSets.integrationTest.output.classesDir
38 | classpath = sourceSets.integrationTest.runtimeClasspath
39 | outputs.upToDateWhen { false }
40 |
41 | testLogging {
42 | events "passed", "skipped", "failed", "standardOut"
43 | showExceptions true
44 | exceptionFormat "full"
45 | showCauses true
46 | showStackTraces true
47 | }
48 | }
49 |
50 | check.dependsOn integrationTest
51 | integrationTest.mustRunAfter test
52 |
53 | tasks.withType(Test) {
54 | reports.html.destination = file("${reporting.baseDir}/${name}")
55 | }
56 |
57 | dependencies {
58 |
59 | compile project(':network')
60 |
61 | compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
62 | compile "io.reactivex:rxjava:${libVersions.rx.java}"
63 | compile "io.reactivex:rxkotlin:${libVersions.rx.kotlin}"
64 |
65 | testCompile "junit:junit:${libVersions.junit}"
66 | testCompile "org.assertj:assertj-core:${libVersions.assertj}"
67 | testCompile "com.nhaarman:mockito-kotlin:1.1.0"
68 | testCompile "com.squareup.okhttp3:mockwebserver:${libVersions.square.okhttp}"
69 |
70 | integrationTestCompile "junit:junit:${libVersions.junit}"
71 | integrationTestCompile "org.assertj:assertj-core:${libVersions.assertj}"
72 | }
--------------------------------------------------------------------------------
/app/src/main/kotlin/com/antyzero/smoksmog/ApplicationComponent.kt:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog
2 |
3 | import com.antyzero.smoksmog.domain.DomainModule
4 | import com.antyzero.smoksmog.eventbus.EventBusModule
5 | import com.antyzero.smoksmog.fabric.FabricModule
6 | import com.antyzero.smoksmog.firebase.SmokSmogFirebaseInstanceIdService
7 | import com.antyzero.smoksmog.i18n.I18nModule
8 | import com.antyzero.smoksmog.job.JobModule
9 | import com.antyzero.smoksmog.job.SmokSmogJobService
10 | import com.antyzero.smoksmog.logger.LoggerModule
11 | import com.antyzero.smoksmog.settings.SettingsModule
12 | import com.antyzero.smoksmog.ui.dialog.AboutDialog
13 | import com.antyzero.smoksmog.ui.dialog.FacebookDialog
14 | import com.antyzero.smoksmog.ui.screen.ActivityComponent
15 | import com.antyzero.smoksmog.ui.screen.ActivityModule
16 | import com.antyzero.smoksmog.ui.screen.start.item.AirQualityViewHolder
17 | import com.antyzero.smoksmog.ui.screen.start.item.ParticulateViewHolder
18 | import com.antyzero.smoksmog.ui.widget.StationWidget
19 | import com.antyzero.smoksmog.ui.widget.StationWidgetService
20 | import com.antyzero.smoksmog.ui.widget.WidgetModule
21 | import com.antyzero.smoksmog.user.UserModule
22 | import dagger.Component
23 | import javax.inject.Singleton
24 |
25 | @Singleton
26 | @Component(modules = arrayOf(
27 | ApplicationModule::class,
28 | DomainModule::class,
29 | LoggerModule::class,
30 | FabricModule::class,
31 | SettingsModule::class,
32 | EventBusModule::class,
33 | I18nModule::class,
34 | UserModule::class,
35 | WidgetModule::class,
36 | JobModule::class))
37 | interface ApplicationComponent {
38 |
39 | operator fun plus(activityModule: ActivityModule): ActivityComponent
40 |
41 | fun inject(smokSmogApplication: SmokSmogApplication)
42 |
43 | fun inject(airQualityViewHolder: AirQualityViewHolder)
44 |
45 | fun inject(aboutDialog: AboutDialog)
46 |
47 | fun inject(particulateViewHolder: ParticulateViewHolder)
48 |
49 | fun inject(facebookDialog: FacebookDialog)
50 |
51 | fun inject(stationWidget: StationWidget)
52 |
53 | fun inject(stationWidgetService: StationWidgetService)
54 |
55 | fun inject(smokSmogFirebaseInstanceIdService: SmokSmogFirebaseInstanceIdService)
56 |
57 | fun inject(smokSmogJobService: SmokSmogJobService)
58 | }
59 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/antyzero/smoksmog/rules/RxSchedulerTestRule.java:
--------------------------------------------------------------------------------
1 | package com.antyzero.smoksmog.rules;
2 |
3 | import android.support.test.espresso.Espresso;
4 |
5 | import com.antyzero.smoksmog.rules.rx.CustomExecutorScheduler;
6 | import com.antyzero.smoksmog.rules.rx.SchedulersHook;
7 | import com.antyzero.smoksmog.rules.rx.ThreadPoolIdlingResource;
8 |
9 | import org.junit.rules.TestRule;
10 | import org.junit.runner.Description;
11 | import org.junit.runners.model.Statement;
12 |
13 | import java.util.concurrent.Executors;
14 | import java.util.concurrent.ThreadPoolExecutor;
15 |
16 | import rx.plugins.RxJavaTestPlugins;
17 |
18 | public class RxSchedulerTestRule implements TestRule {
19 |
20 | private final SchedulersHook schedulersHook;
21 | private final ThreadPoolIdlingResource idlingResource;
22 |
23 | public RxSchedulerTestRule() {
24 | ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) Executors.newScheduledThreadPool(16);
25 | CustomExecutorScheduler scheduler = new CustomExecutorScheduler(threadPoolExecutor);
26 | schedulersHook = new SchedulersHook(scheduler);
27 | idlingResource = new ThreadPoolIdlingResource(threadPoolExecutor) {
28 | @Override
29 | public String getName() {
30 | return getClass().getSimpleName();
31 | }
32 | };
33 | }
34 |
35 | @Override
36 | public Statement apply(Statement base, Description description) {
37 | return new RxStatement(base, this);
38 | }
39 |
40 | private static class RxStatement extends Statement {
41 | private final Statement base;
42 | private final RxSchedulerTestRule testRule;
43 |
44 | public RxStatement(Statement base, RxSchedulerTestRule schedulersHook) {
45 | this.base = base;
46 | this.testRule = schedulersHook;
47 | }
48 |
49 | @Override
50 | public void evaluate() throws Throwable {
51 |
52 | RxJavaTestPlugins.resetPlugins();
53 | RxJavaTestPlugins.getInstance().registerSchedulersHook(testRule.schedulersHook);
54 | Espresso.registerIdlingResources(testRule.idlingResource);
55 |
56 | base.evaluate();
57 |
58 | Espresso.unregisterIdlingResources(testRule.idlingResource);
59 | RxJavaTestPlugins.resetPlugins();
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------