├── .idea
├── .name
├── codeStyles
│ ├── codeStyleConfig.xml
│ └── Project.xml
├── vcs.xml
├── dictionaries
│ └── Ahmed_S.xml
├── misc.xml
├── runConfigurations.xml
└── gradle.xml
├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── ic_launcher-playstore.png
│ │ ├── res
│ │ │ ├── drawable-hdpi
│ │ │ │ ├── ic_dead.png
│ │ │ │ ├── ic_globe.png
│ │ │ │ ├── ic_heart.png
│ │ │ │ ├── ic_home.png
│ │ │ │ ├── ic_virus.png
│ │ │ │ ├── ic_comment.png
│ │ │ │ ├── ic_dead_light.png
│ │ │ │ ├── ic_new_deaths.png
│ │ │ │ ├── ic_up_arrow.png
│ │ │ │ ├── ic_new_affected.png
│ │ │ │ └── ic_new_recovered.png
│ │ │ ├── drawable-mdpi
│ │ │ │ ├── ic_dead.png
│ │ │ │ ├── ic_globe.png
│ │ │ │ ├── ic_heart.png
│ │ │ │ ├── ic_home.png
│ │ │ │ ├── ic_virus.png
│ │ │ │ ├── ic_comment.png
│ │ │ │ ├── ic_dead_light.png
│ │ │ │ ├── ic_new_deaths.png
│ │ │ │ ├── ic_up_arrow.png
│ │ │ │ ├── ic_new_affected.png
│ │ │ │ └── ic_new_recovered.png
│ │ │ ├── drawable-xhdpi
│ │ │ │ ├── ic_dead.png
│ │ │ │ ├── ic_home.png
│ │ │ │ ├── ic_globe.png
│ │ │ │ ├── ic_heart.png
│ │ │ │ ├── ic_virus.png
│ │ │ │ ├── ic_comment.png
│ │ │ │ ├── ic_up_arrow.png
│ │ │ │ ├── ic_dead_light.png
│ │ │ │ ├── ic_new_deaths.png
│ │ │ │ ├── ic_new_affected.png
│ │ │ │ └── ic_new_recovered.png
│ │ │ ├── values
│ │ │ │ ├── dimen.xml
│ │ │ │ ├── ic_launcher_background.xml
│ │ │ │ ├── preloaded_fonts.xml
│ │ │ │ ├── arrays.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── styles.xml
│ │ │ │ ├── strings.xml
│ │ │ │ └── font_certs.xml
│ │ │ ├── drawable-xxhdpi
│ │ │ │ ├── ic_dead.png
│ │ │ │ ├── ic_globe.png
│ │ │ │ ├── ic_heart.png
│ │ │ │ ├── ic_home.png
│ │ │ │ ├── ic_virus.png
│ │ │ │ ├── ic_comment.png
│ │ │ │ ├── ic_up_arrow.png
│ │ │ │ ├── ic_dead_light.png
│ │ │ │ ├── ic_new_deaths.png
│ │ │ │ ├── ic_new_affected.png
│ │ │ │ └── ic_new_recovered.png
│ │ │ ├── drawable-xxxhdpi
│ │ │ │ ├── ic_dead.png
│ │ │ │ ├── ic_comment.png
│ │ │ │ ├── ic_globe.png
│ │ │ │ ├── ic_heart.png
│ │ │ │ ├── ic_virus.png
│ │ │ │ ├── ic_up_arrow.png
│ │ │ │ ├── ic_dead_light.png
│ │ │ │ ├── ic_new_affected.png
│ │ │ │ ├── ic_new_deaths.png
│ │ │ │ └── ic_new_recovered.png
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── drawable
│ │ │ │ ├── tab_item_bg.xml
│ │ │ │ ├── unselected_tab_item_bg.xml
│ │ │ │ ├── stats_bg.xml
│ │ │ │ ├── blue_to_transparent_gradient.xml
│ │ │ │ ├── green_to_transparent_gradient.xml
│ │ │ │ ├── red_to_transparent_gradient.xml
│ │ │ │ ├── tab_item_selector.xml
│ │ │ │ ├── yellow_to_transparent_gradient.xml
│ │ │ │ ├── graph_affected_bg.xml
│ │ │ │ ├── graph_active_cases_bg.xml
│ │ │ │ ├── item_background.xml
│ │ │ │ └── ic_launcher_background.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── font
│ │ │ │ ├── viga.xml
│ │ │ │ └── ubuntu_bold.xml
│ │ │ ├── layout
│ │ │ │ ├── activity_main.xml
│ │ │ │ ├── overall_chart_fragment.xml
│ │ │ │ ├── marker.xml
│ │ │ │ ├── list_item.xml
│ │ │ │ ├── active_cases_stat_fragment.xml
│ │ │ │ └── fragment_overview.xml
│ │ │ ├── drawable-anydpi
│ │ │ │ └── ic_home.xml
│ │ │ ├── navigation
│ │ │ │ └── nav_graph.xml
│ │ │ └── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── example
│ │ │ │ └── coronavirus_stats
│ │ │ │ ├── util
│ │ │ │ ├── SharedVariables.kt
│ │ │ │ ├── SortEnum.kt
│ │ │ │ ├── Constants.kt
│ │ │ │ ├── Preferences.kt
│ │ │ │ ├── NetworkUtils.kt
│ │ │ │ ├── MarginItemDecoration.kt
│ │ │ │ ├── NotificationUtils.kt
│ │ │ │ ├── CustomMarkerView.kt
│ │ │ │ ├── Utils.kt
│ │ │ │ ├── ExtensionFunctions.kt
│ │ │ │ ├── BindingUtils.kt
│ │ │ │ └── Location.kt
│ │ │ │ ├── models
│ │ │ │ ├── WorldCurrentStat.kt
│ │ │ │ ├── CountryStatHistoryPerDay.kt
│ │ │ │ ├── CountryCurrentStat.kt
│ │ │ │ ├── CountryStatHistoryResponse.kt
│ │ │ │ └── WorldStatHistoryResponse.kt
│ │ │ │ ├── ui
│ │ │ │ ├── adapters
│ │ │ │ │ ├── ChartsPagerAdapter.kt
│ │ │ │ │ └── CountryStatAdapter.kt
│ │ │ │ ├── fragments
│ │ │ │ │ ├── ActiveCasesStatFragment.kt
│ │ │ │ │ ├── OverallChartFragment.kt
│ │ │ │ │ └── OverviewFragment.kt
│ │ │ │ ├── MainActivity.kt
│ │ │ │ └── OverviewViewModel.kt
│ │ │ │ ├── api
│ │ │ │ ├── CoronavirusHistoryApiService.kt
│ │ │ │ └── CoronavirusLatestApiService.kt
│ │ │ │ ├── network
│ │ │ │ └── Network.kt
│ │ │ │ └── receiver
│ │ │ │ └── AlarmReceiver.kt
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── example
│ │ │ └── coronavirus_stats
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── example
│ │ └── coronavirus_stats
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── pics
├── facebook.png
├── twitter.png
├── Screenshot_20200424-235937_One UI Home.jpg
├── Screenshot_20200425-112021_Coronavirus Stats.jpg
└── Screenshot_20200425-112047_Coronavirus Stats.jpg
├── apk
└── Coronavirus Stats.apk
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── LICENSE
├── gradle.properties
├── gradlew.bat
├── README.md
└── gradlew
/.idea/.name:
--------------------------------------------------------------------------------
1 | Coronavirus Stats
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name='Coronavirus Stats'
2 | include ':app'
3 |
--------------------------------------------------------------------------------
/pics/facebook.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/pics/facebook.png
--------------------------------------------------------------------------------
/pics/twitter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/pics/twitter.png
--------------------------------------------------------------------------------
/apk/Coronavirus Stats.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/apk/Coronavirus Stats.apk
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_dead.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-hdpi/ic_dead.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_globe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-hdpi/ic_globe.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_heart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-hdpi/ic_heart.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-hdpi/ic_home.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_virus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-hdpi/ic_virus.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_dead.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-mdpi/ic_dead.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_globe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-mdpi/ic_globe.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_heart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-mdpi/ic_heart.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-mdpi/ic_home.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_virus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-mdpi/ic_virus.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_dead.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-xhdpi/ic_dead.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-xhdpi/ic_home.png
--------------------------------------------------------------------------------
/app/src/main/res/values/dimen.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 8dp
4 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_comment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-hdpi/ic_comment.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_comment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-mdpi/ic_comment.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_globe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-xhdpi/ic_globe.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_heart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-xhdpi/ic_heart.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_virus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-xhdpi/ic_virus.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_dead.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-xxhdpi/ic_dead.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_globe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-xxhdpi/ic_globe.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_heart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-xxhdpi/ic_heart.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-xxhdpi/ic_home.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_virus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-xxhdpi/ic_virus.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_dead.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-xxxhdpi/ic_dead.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_dead_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-hdpi/ic_dead_light.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_new_deaths.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-hdpi/ic_new_deaths.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_up_arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-hdpi/ic_up_arrow.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_dead_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-mdpi/ic_dead_light.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_new_deaths.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-mdpi/ic_new_deaths.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_up_arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-mdpi/ic_up_arrow.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_comment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-xhdpi/ic_comment.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_up_arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-xhdpi/ic_up_arrow.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_comment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-xxhdpi/ic_comment.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_up_arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-xxhdpi/ic_up_arrow.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_comment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-xxxhdpi/ic_comment.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_globe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-xxxhdpi/ic_globe.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_heart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-xxxhdpi/ic_heart.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_virus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-xxxhdpi/ic_virus.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/pics/Screenshot_20200424-235937_One UI Home.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/pics/Screenshot_20200424-235937_One UI Home.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_new_affected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-hdpi/ic_new_affected.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_new_affected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-mdpi/ic_new_affected.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_dead_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-xhdpi/ic_dead_light.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_new_deaths.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-xhdpi/ic_new_deaths.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_dead_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-xxhdpi/ic_dead_light.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_new_deaths.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-xxhdpi/ic_new_deaths.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_up_arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-xxxhdpi/ic_up_arrow.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_new_recovered.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-hdpi/ic_new_recovered.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_new_recovered.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-mdpi/ic_new_recovered.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_new_affected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-xhdpi/ic_new_affected.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_new_recovered.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-xhdpi/ic_new_recovered.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_new_affected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-xxhdpi/ic_new_affected.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_new_recovered.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-xxhdpi/ic_new_recovered.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_dead_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-xxxhdpi/ic_dead_light.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_new_affected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-xxxhdpi/ic_new_affected.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_new_deaths.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-xxxhdpi/ic_new_deaths.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/pics/Screenshot_20200425-112021_Coronavirus Stats.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/pics/Screenshot_20200425-112021_Coronavirus Stats.jpg
--------------------------------------------------------------------------------
/pics/Screenshot_20200425-112047_Coronavirus Stats.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/pics/Screenshot_20200425-112047_Coronavirus Stats.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_new_recovered.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/drawable-xxxhdpi/ic_new_recovered.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ahmed-Sellami/Coronavirus-Stats/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #F9F9F9
4 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/coronavirus_stats/util/SharedVariables.kt:
--------------------------------------------------------------------------------
1 | package com.example.coronavirus_stats.util
2 |
3 | object SharedVariables {
4 | var country: String? = null
5 | }
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/coronavirus_stats/util/SortEnum.kt:
--------------------------------------------------------------------------------
1 | package com.example.coronavirus_stats.util
2 |
3 | enum class SortEnum {
4 | AFFECTED,
5 | DEATHS,
6 | RECOVERED
7 | }
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/tab_item_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | -
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/coronavirus_stats/models/WorldCurrentStat.kt:
--------------------------------------------------------------------------------
1 | package com.example.coronavirus_stats.models
2 |
3 | data class WorldCurrentStat (
4 | val confirmed: String,
5 | val deaths: String,
6 | val recovered: String
7 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/example/coronavirus_stats/models/CountryStatHistoryPerDay.kt:
--------------------------------------------------------------------------------
1 | package com.example.coronavirus_stats.models
2 |
3 | data class CountryStatHistoryPerDay(
4 | val confirmed: Double,
5 | val deaths: Double,
6 | val recovered: Double
7 | )
--------------------------------------------------------------------------------
/app/src/main/res/drawable/unselected_tab_item_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/preloaded_fonts.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - @font/ubuntu_bold
5 | - @font/viga
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/dictionaries/Ahmed_S.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | coronavirus
5 | coroutine
6 | emoji
7 | moshi
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/coronavirus_stats/models/CountryCurrentStat.kt:
--------------------------------------------------------------------------------
1 | package com.example.coronavirus_stats.models
2 |
3 | data class CountryCurrentStat (
4 | val country: String,
5 | val confirmed: Double,
6 | val deaths: Double,
7 | val recovered: Double
8 | )
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/stats_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Apr 07 20:41:29 WAT 2020
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-5.6.4-all.zip
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - @string/by_affected
5 | - @string/by_deaths
6 | - @string/by_recovered
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/coronavirus_stats/util/Constants.kt:
--------------------------------------------------------------------------------
1 | package com.example.coronavirus_stats.util
2 |
3 | object Constants {
4 | const val COUNTRY_NAME = "COUNTRY_NAME"
5 | const val REMINDER_HOUR = 23
6 | const val REMINDER_MINUTE = 59
7 | const val NOTIFICATION_INTENT_ACTION = "SUMMARY"
8 | }
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/coronavirus_stats/models/CountryStatHistoryResponse.kt:
--------------------------------------------------------------------------------
1 | package com.example.coronavirus_stats.models
2 |
3 | import com.example.coronavirus_stats.models.CountryStatHistoryPerDay
4 |
5 | data class CountryStatHistoryResponse(
6 | val count: Double,
7 | val result: Map
8 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/example/coronavirus_stats/models/WorldStatHistoryResponse.kt:
--------------------------------------------------------------------------------
1 | package com.example.coronavirus_stats.models
2 |
3 | import com.example.coronavirus_stats.models.CountryStatHistoryPerDay
4 |
5 | data class WorldStatHistoryResponse(
6 | val count: Double,
7 | val date: String,
8 | val result: CountryStatHistoryPerDay
9 | )
--------------------------------------------------------------------------------
/app/src/main/res/drawable/blue_to_transparent_gradient.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/green_to_transparent_gradient.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/red_to_transparent_gradient.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/tab_item_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/yellow_to_transparent_gradient.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/graph_affected_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/graph_active_cases_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/font/viga.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/coronavirus_stats/util/Preferences.kt:
--------------------------------------------------------------------------------
1 | package com.example.coronavirus_stats.util
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 | import androidx.preference.PreferenceManager
6 |
7 | object Preferences {
8 | fun getPrefs(context: Context) : SharedPreferences =
9 | PreferenceManager.getDefaultSharedPreferences(context)
10 | }
--------------------------------------------------------------------------------
/app/src/main/res/font/ubuntu_bold.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/test/java/com/example/coronavirus_stats/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.coronavirus_stats
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-anydpi/ic_home.xml:
--------------------------------------------------------------------------------
1 |
8 |
10 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/navigation/nav_graph.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #448FDA
4 | #3700B3
5 | #FFFFFF
6 |
7 | #FFCE00
8 | #C31F1F
9 | #2ECC71
10 | #1976D2
11 |
12 | #F44336
13 |
14 | #FFCDD2
15 | #C8E6C9
16 | #FFECB3
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/coronavirus_stats/util/NetworkUtils.kt:
--------------------------------------------------------------------------------
1 | package com.example.coronavirus_stats.util
2 |
3 | import android.content.Context
4 | import android.net.ConnectivityManager
5 | import android.os.Build
6 |
7 |
8 | fun isNetworkAvailable(context: Context) : Boolean {
9 | val connectivityManager =
10 | context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
11 |
12 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
13 | connectivityManager.activeNetwork != null
14 | } else {
15 | connectivityManager.activeNetworkInfo != null
16 | }
17 |
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/coronavirus_stats/util/MarginItemDecoration.kt:
--------------------------------------------------------------------------------
1 | package com.example.coronavirus_stats.util
2 |
3 | import android.graphics.Rect
4 | import android.view.View
5 | import androidx.recyclerview.widget.RecyclerView
6 |
7 | class MarginItemDecoration(private val spaceHeight: Int) : RecyclerView.ItemDecoration() {
8 |
9 | override fun getItemOffsets(outRect: Rect, view: View,
10 | parent: RecyclerView, state: RecyclerView.State) {
11 | with(outRect) {
12 | top = spaceHeight
13 | left = spaceHeight
14 | right = spaceHeight
15 | bottom = spaceHeight
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Coronavirus Stats
3 |
4 | Active Cases
5 | Overall
6 |
7 | No item
8 |
9 | Affected
10 | Deaths
11 | Recovered
12 |
13 | summary_channel
14 | Summary
15 | Today\'s summary in your country
16 | Affected
17 | Active
18 | 🇩🇪
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/overall_chart_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
8 |
9 |
13 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/marker.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
20 |
21 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/example/coronavirus_stats/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.coronavirus_stats
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.example.coronavirus_stats", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/coronavirus_stats/ui/adapters/ChartsPagerAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.example.coronavirus_stats.ui.adapters
2 |
3 | import androidx.fragment.app.Fragment
4 | import androidx.viewpager2.adapter.FragmentStateAdapter
5 | import com.example.coronavirus_stats.ui.fragments.ActiveCasesStatFragment
6 | import com.example.coronavirus_stats.ui.fragments.OverallChartFragment
7 |
8 | class ChartsPagerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
9 |
10 | override fun createFragment(position: Int): Fragment = when (position) {
11 | ACTIVE_CASES_POSITION -> ActiveCasesStatFragment()
12 | OVERALL_POSITION -> OverallChartFragment()
13 | else -> throw IllegalArgumentException("No item")
14 | }
15 |
16 | override fun getItemCount(): Int =
17 | VIEWS_NUMBER
18 |
19 | companion object{
20 | const val VIEWS_NUMBER = 2
21 |
22 | const val ACTIVE_CASES_POSITION = 0
23 | const val OVERALL_POSITION = 1
24 | }
25 |
26 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/item_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | -
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/coronavirus_stats/api/CoronavirusHistoryApiService.kt:
--------------------------------------------------------------------------------
1 | package com.example.coronavirus_stats.api
2 |
3 | import com.example.coronavirus_stats.models.CountryStatHistoryResponse
4 | import com.example.coronavirus_stats.models.WorldStatHistoryResponse
5 | import com.example.coronavirus_stats.network.createNetworkClient
6 | import kotlinx.coroutines.Deferred
7 | import retrofit2.http.GET
8 | import retrofit2.http.Path
9 |
10 | private const val BASE_URL = "https://covidapi.info/api/v1/"
11 |
12 | interface CoronavirusHistoryApiService{
13 | @GET("country/{country}/{date}")
14 | fun getCountryStatHistoryPerDay(@Path("country") country: String?, @Path("date") date: String):
15 | Deferred
16 |
17 | @GET("global/{date}")
18 | fun getWorldStatHistoryPerDay(@Path("date") date: String):
19 | Deferred
20 | }
21 |
22 | object CoronavirusHistoryApi {
23 | val retrofitService : CoronavirusHistoryApiService by lazy {
24 | createNetworkClient(BASE_URL)
25 | .create(CoronavirusHistoryApiService::class.java) }
26 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Ahmed Sellami
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/coronavirus_stats/network/Network.kt:
--------------------------------------------------------------------------------
1 | package com.example.coronavirus_stats.network
2 |
3 | import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory
4 | import com.squareup.moshi.Moshi
5 | import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
6 | import okhttp3.OkHttpClient
7 | import retrofit2.Retrofit
8 | import retrofit2.converter.moshi.MoshiConverterFactory
9 | import java.util.concurrent.TimeUnit
10 |
11 | fun createNetworkClient(baseUrl: String) =
12 | retrofitClient(baseUrl)
13 |
14 | private val moshi = Moshi.Builder()
15 | .add(KotlinJsonAdapterFactory())
16 | .build()
17 |
18 | val okHttpClient: OkHttpClient = OkHttpClient.Builder()
19 | .connectTimeout(1, TimeUnit.MINUTES)
20 | .readTimeout(30, TimeUnit.SECONDS)
21 | .writeTimeout(15, TimeUnit.SECONDS)
22 | .build()
23 |
24 | private fun retrofitClient(baseUrl: String): Retrofit = Retrofit.Builder()
25 | .addConverterFactory(MoshiConverterFactory.create(moshi))
26 | .addCallAdapterFactory(CoroutineCallAdapterFactory())
27 | .baseUrl(baseUrl)
28 | .client(okHttpClient)
29 | .build()
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/coronavirus_stats/api/CoronavirusLatestApiService.kt:
--------------------------------------------------------------------------------
1 | package com.example.coronavirus_stats.api
2 |
3 | import com.example.coronavirus_stats.models.CountryCurrentStat
4 | import com.example.coronavirus_stats.models.WorldCurrentStat
5 | import com.example.coronavirus_stats.network.createNetworkClient
6 | import kotlinx.coroutines.Deferred
7 | import retrofit2.http.GET
8 | import retrofit2.http.Headers
9 | import retrofit2.http.Query
10 |
11 | private const val BASE_URL = "https://covid-19-data.p.rapidapi.com/"
12 | private const val API_KEY = "c64032eeafmsh14b4af700711ddcp1a88afjsn5bcc4454a330"
13 |
14 | interface CoronavirusMonitorApiService {
15 |
16 | @Headers("x-rapidapi-key: $API_KEY")
17 | @GET("totals")
18 | fun getWorldStat(): Deferred>
19 |
20 | @Headers("x-rapidapi-key: $API_KEY")
21 | @GET("country")
22 | fun getCountryStat(@Query("name") countryName: String): Deferred>
23 |
24 | }
25 |
26 | object CoronavirusMonitorApi {
27 | val retrofitService : CoronavirusMonitorApiService by lazy {
28 | createNetworkClient(BASE_URL)
29 | .create(CoronavirusMonitorApiService::class.java) }
30 | }
31 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/coronavirus_stats/util/NotificationUtils.kt:
--------------------------------------------------------------------------------
1 | package com.example.coronavirus_stats.util
2 |
3 | import android.app.NotificationManager
4 | import android.app.PendingIntent
5 | import android.content.Context
6 | import android.content.Intent
7 | import androidx.core.app.NotificationCompat
8 | import com.example.coronavirus_stats.ui.MainActivity
9 | import com.example.coronavirus_stats.R
10 |
11 | private const val NOTIFICATION_ID = 0
12 |
13 | fun NotificationManager.sendNotification(messageBody: String, applicationContext: Context) {
14 | val contentIntent = Intent(applicationContext, MainActivity::class.java)
15 | val contentPendingIntent = PendingIntent.getActivity(
16 | applicationContext,
17 | NOTIFICATION_ID,
18 | contentIntent,
19 | PendingIntent.FLAG_UPDATE_CURRENT
20 | )
21 | val builder = NotificationCompat.Builder(
22 | applicationContext,
23 | applicationContext.getString(R.string.summary_channel_id)
24 | )
25 | .setContentTitle(applicationContext.getString(R.string.notification_title))
26 | .setSmallIcon(R.mipmap.ic_launcher)
27 | .setContentText(messageBody)
28 | .setContentIntent(contentPendingIntent)
29 | .setAutoCancel(true)
30 |
31 | notify(NOTIFICATION_ID, builder.build())
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
28 |
29 |
30 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/coronavirus_stats/util/CustomMarkerView.kt:
--------------------------------------------------------------------------------
1 | package com.example.coronavirus_stats.util
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.view.View
6 | import android.widget.TextView
7 | import com.example.coronavirus_stats.R
8 | import com.github.mikephil.charting.components.IMarker
9 | import com.github.mikephil.charting.components.MarkerView
10 | import com.github.mikephil.charting.data.Entry
11 | import com.github.mikephil.charting.highlight.Highlight
12 | import com.github.mikephil.charting.utils.MPPointF
13 |
14 |
15 | @SuppressLint("ViewConstructor")
16 | class CustomMarkerView(context: Context?, layoutResource: Int) :
17 | MarkerView(context, layoutResource), IMarker {
18 | private val tvContent: TextView = findViewById(R.id.tvContent) as TextView
19 |
20 | // callbacks everytime the MarkerView is redrawn, can be used to update the
21 | // content (user-interface)
22 | override fun refreshContent(
23 | e: Entry,
24 | highlight: Highlight
25 | ) {
26 | tvContent.text = e.y.toInt().toString()
27 |
28 | // this will perform necessary layouting
29 | super.refreshContent(e, highlight)
30 | }
31 |
32 | private var mOffset: MPPointF? = null
33 | override fun getOffset(): MPPointF {
34 | if (mOffset == null) {
35 | // center the marker horizontally and vertically
36 | mOffset = MPPointF((-(width / 2)).toFloat(), (-height).toFloat())
37 | }
38 | return mOffset!!
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/coronavirus_stats/util/Utils.kt:
--------------------------------------------------------------------------------
1 | package com.example.coronavirus_stats.util
2 |
3 | import android.util.Log
4 | import java.text.SimpleDateFormat
5 | import java.util.*
6 |
7 | fun getTheSixPreviousDays(dateFormat: String, addToday: Boolean): MutableList{
8 | val calendar = Calendar.getInstance()
9 | val currentDate: Date = calendar.time
10 | val formatter = SimpleDateFormat(dateFormat, Locale.getDefault())
11 | val formattedDate = formatter.format(currentDate)
12 | calendar.time = formatter.parse(formattedDate)!!
13 |
14 | val list: MutableList = ArrayList()
15 | if(addToday){
16 | calendar.add(Calendar.DATE, 0)
17 | list.add(formatter.format(calendar.time))
18 | Log.i("TopLevelFunc", formatter.format(calendar.time))
19 | }
20 | for (i in 1..6){
21 | calendar.add(Calendar.DATE, -1)
22 | list.add(formatter.format(calendar.time))
23 | Log.i("TopLevelFunc", formatter.format(calendar.time))
24 | }
25 | return list
26 | }
27 |
28 | fun setCountriesFlagEmoji() : MutableMap {
29 | val countries: MutableMap = HashMap()
30 |
31 | for (iso in Locale.getISOCountries()) {
32 | val l = Locale("", iso)
33 | val countryName = when(l.displayCountry){
34 | "United States" -> "USA"
35 | "United Kingdom" -> "UK"
36 | "United Arab Emirates" -> "UAE"
37 | else -> l.displayCountry
38 | }
39 | countries[countryName] = l.flagEmoji
40 | }
41 | return countries
42 | }
43 |
44 | private val Locale.flagEmoji: String
45 | get() {
46 | val firstLetter = Character.codePointAt(country, 0) - 0x41 + 0x1F1E6
47 | val secondLetter = Character.codePointAt(country, 1) - 0x41 + 0x1F1E6
48 | return String(Character.toChars(firstLetter)) + String(Character.toChars(secondLetter))
49 | }
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/coronavirus_stats/receiver/AlarmReceiver.kt:
--------------------------------------------------------------------------------
1 | package com.example.coronavirus_stats.receiver
2 |
3 |
4 | import android.app.NotificationManager
5 | import android.content.BroadcastReceiver
6 | import android.content.Context
7 | import android.content.Intent
8 | import androidx.core.content.ContextCompat
9 | import com.example.coronavirus_stats.util.Constants.COUNTRY_NAME
10 | import com.example.coronavirus_stats.util.Constants.NOTIFICATION_INTENT_ACTION
11 | import com.example.coronavirus_stats.api.CoronavirusMonitorApi
12 | import com.example.coronavirus_stats.util.Preferences
13 | import com.example.coronavirus_stats.util.isNetworkAvailable
14 | import com.example.coronavirus_stats.util.sendNotification
15 | import com.example.coronavirus_stats.util.toStringWithCommas
16 | import kotlinx.coroutines.CoroutineScope
17 | import kotlinx.coroutines.Dispatchers
18 | import kotlinx.coroutines.launch
19 |
20 |
21 | class AlarmReceiver : BroadcastReceiver() {
22 |
23 | override fun onReceive(context: Context, intent: Intent) {
24 |
25 | val prefs = Preferences.getPrefs(context)
26 |
27 | if(intent.action == NOTIFICATION_INTENT_ACTION && isNetworkAvailable(context)){
28 |
29 | val notificationManager = ContextCompat.getSystemService(
30 | context,
31 | NotificationManager::class.java
32 | ) as NotificationManager
33 |
34 | CoroutineScope(Dispatchers.IO).launch {
35 | val countryName = prefs.getString(COUNTRY_NAME, null)
36 |
37 | if (countryName != null) {
38 | try {
39 | val latestStat = CoronavirusMonitorApi.retrofitService.getCountryStat(
40 | countryName
41 | ).await()
42 |
43 | val stat = latestStat[0]
44 | val summary = "Confirmed: ${stat.confirmed.toInt().toStringWithCommas()} | " +
45 | "Deaths: ${stat.deaths.toInt().toStringWithCommas()} | " +
46 | "Recovered: ${stat.recovered.toInt().toStringWithCommas()}"
47 |
48 | notificationManager.sendNotification(
49 | summary,
50 | context
51 | )
52 | } catch (e: Exception) {
53 | e.printStackTrace()
54 | }
55 | }
56 |
57 | }
58 |
59 | }
60 |
61 | }
62 |
63 | }
64 |
65 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/coronavirus_stats/ui/fragments/ActiveCasesStatFragment.kt:
--------------------------------------------------------------------------------
1 | package com.example.coronavirus_stats.ui.fragments
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import androidx.fragment.app.Fragment
8 | import androidx.lifecycle.Observer
9 | import androidx.lifecycle.ViewModelProvider
10 | import com.example.coronavirus_stats.databinding.ActiveCasesStatFragmentBinding
11 | import com.example.coronavirus_stats.ui.OverviewViewModel
12 | import com.example.coronavirus_stats.util.minusToNullable
13 | import com.example.coronavirus_stats.util.plusToNullable
14 | import com.github.mikephil.charting.charts.LineChart
15 | import com.github.mikephil.charting.components.Description
16 |
17 |
18 | class ActiveCasesStatFragment : Fragment() {
19 |
20 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
21 |
22 | val binding = ActiveCasesStatFragmentBinding.inflate(inflater)
23 |
24 | val viewModel = activity?.run {
25 | ViewModelProvider(this)[OverviewViewModel::class.java]
26 | } ?: throw Exception("Invalid Activity")
27 |
28 | binding.lifecycleOwner = viewLifecycleOwner
29 |
30 | binding.viewModel = viewModel
31 |
32 | customizeChart(binding.chart1)
33 | customizeChart(binding.chart2)
34 |
35 | viewModel.countryHistoryActiveCases.observe(viewLifecycleOwner, Observer {
36 | binding.spinKit.visibility = View.GONE
37 | binding.spinKit1.visibility = View.GONE
38 |
39 | val countryCurrentStat = viewModel.countryCurrentStat.value
40 |
41 | val activeCases = countryCurrentStat?.confirmed.minusToNullable(countryCurrentStat?.deaths.plusToNullable(countryCurrentStat?.recovered))
42 | val totalCases = countryCurrentStat?.confirmed ?: 0.0
43 |
44 | binding.circularProgress.setProgress(activeCases / totalCases * 100.0, 100.0)
45 | })
46 |
47 | binding.circularProgress.setProgressTextAdapter { currentProgress -> String.format("%.1f", currentProgress) + "%" }
48 |
49 | return binding.root
50 | }
51 |
52 | private fun customizeChart(chart: LineChart) {
53 | val desc = Description()
54 | desc.text = ""
55 | chart.description = desc
56 | chart.xAxis.isEnabled = false
57 | chart.axisLeft.isEnabled = false
58 | chart.axisRight.isEnabled = false
59 | chart.legend.isEnabled = false
60 |
61 | chart.setTouchEnabled(false)
62 | }
63 |
64 | }
65 |
66 |
67 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/coronavirus_stats/ui/fragments/OverallChartFragment.kt:
--------------------------------------------------------------------------------
1 | package com.example.coronavirus_stats.ui.fragments
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import androidx.fragment.app.Fragment
8 | import androidx.lifecycle.ViewModelProvider
9 | import com.example.coronavirus_stats.R
10 | import com.example.coronavirus_stats.databinding.OverallChartFragmentBinding
11 | import com.example.coronavirus_stats.ui.OverviewViewModel
12 | import com.example.coronavirus_stats.util.CustomMarkerView
13 | import com.example.coronavirus_stats.util.getTheSixPreviousDays
14 | import com.github.mikephil.charting.charts.LineChart
15 | import com.github.mikephil.charting.components.AxisBase
16 | import com.github.mikephil.charting.components.Description
17 | import com.github.mikephil.charting.components.XAxis
18 | import com.github.mikephil.charting.formatter.LargeValueFormatter
19 | import com.github.mikephil.charting.formatter.ValueFormatter
20 |
21 | class OverallChartFragment : Fragment() {
22 |
23 | private var previousSixDays =
24 | getTheSixPreviousDays("MM-dd", true)
25 |
26 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
27 |
28 | val binding = OverallChartFragmentBinding.inflate(inflater)
29 |
30 | binding.lifecycleOwner = viewLifecycleOwner
31 |
32 | binding.viewModel = activity?.run {
33 | ViewModelProvider(this)[OverviewViewModel::class.java]
34 | } ?: throw Exception("Invalid Activity")
35 |
36 | customizeChart(binding.chart)
37 |
38 | return binding.root
39 | }
40 |
41 | private fun customizeChart(chart: LineChart) {
42 | val desc = Description()
43 | desc.text = ""
44 | chart.description = desc
45 | chart.axisRight.isEnabled = false
46 | chart.xAxis.position = XAxis.XAxisPosition.BOTTOM
47 | previousSixDays = previousSixDays.reversed() as MutableList
48 | previousSixDays.add(0, "0")
49 |
50 | chart.xAxis.valueFormatter = object : ValueFormatter() {
51 | override fun getAxisLabel(value: Float, axis: AxisBase?): String {
52 | return previousSixDays.getOrNull(value.toInt()) ?: value.toString()
53 | }
54 | }
55 | chart.axisLeft.valueFormatter = object : LargeValueFormatter(){}
56 | chart.axisLeft.axisMinimum = 0f
57 | chart.legend.setDrawInside(true)
58 | chart.legend.yOffset = 104f
59 |
60 | chart.isHighlightPerTapEnabled = true
61 | chart.setDrawMarkers(true)
62 | chart.setTouchEnabled(true)
63 |
64 | chart.marker = CustomMarkerView(
65 | context,
66 | R.layout.marker
67 | )
68 |
69 | }
70 | }
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-android-extensions'
4 | apply plugin: 'kotlin-kapt'
5 |
6 | android {
7 | compileSdkVersion 29
8 | buildToolsVersion "29.0.3"
9 |
10 | dataBinding {
11 | enabled = true
12 | }
13 | defaultConfig {
14 | applicationId "com.example.coronavirus_stats"
15 | minSdkVersion 21
16 | targetSdkVersion 29
17 | versionCode 1
18 | versionName "1.0"
19 |
20 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
21 | }
22 |
23 | buildTypes {
24 | release {
25 | minifyEnabled false
26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
27 | }
28 | }
29 |
30 | compileOptions {
31 | sourceCompatibility 1.8
32 | targetCompatibility 1.8
33 | }
34 | }
35 |
36 | dependencies {
37 | implementation fileTree(dir: 'libs', include: ['*.jar'])
38 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
39 | implementation 'androidx.appcompat:appcompat:1.1.0'
40 | implementation 'androidx.core:core-ktx:1.2.0'
41 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
42 | testImplementation 'junit:junit:4.13'
43 | androidTestImplementation 'androidx.test.ext:junit:1.1.1'
44 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
45 | implementation 'androidx.navigation:navigation-fragment-ktx:2.2.2'
46 | implementation 'androidx.navigation:navigation-ui-ktx:2.2.2'
47 | implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
48 |
49 | // Retrofit
50 | implementation "com.squareup.retrofit2:retrofit:2.7.2"
51 | implementation "com.squareup.retrofit2:converter-moshi:2.7.2"
52 |
53 | // Moshi
54 | implementation "com.squareup.moshi:moshi:1.9.2"
55 | implementation "com.squareup.moshi:moshi-kotlin:1.9.2"
56 |
57 | // Kotlin Coroutines
58 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.5"
59 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.5"
60 |
61 | implementation "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2"
62 |
63 | // Preference
64 | implementation "androidx.preference:preference-ktx:1.1.1"
65 |
66 | // Google Location API
67 | implementation 'com.google.android.gms:play-services-location:17.0.0'
68 |
69 | // ViewPager2
70 | implementation "com.google.android.material:material:1.2.0-alpha06"
71 | implementation "androidx.viewpager2:viewpager2:1.1.0-alpha01"
72 |
73 | /** Custom UI **/
74 | // Android SpinKit
75 | implementation 'com.github.ybq:Android-SpinKit:1.4.0'
76 |
77 | // MPAndroidChart
78 | implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
79 |
80 | // CircularProgressIndicator
81 | implementation 'com.github.antonKozyriatskyi:CircularProgressIndicator:1.3.0'
82 | }
83 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/coronavirus_stats/util/ExtensionFunctions.kt:
--------------------------------------------------------------------------------
1 | package com.example.coronavirus_stats.util
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.view.View
6 | import android.view.inputmethod.InputMethodManager
7 | import androidx.core.content.ContextCompat
8 | import com.example.coronavirus_stats.R
9 | import com.example.coronavirus_stats.models.CountryStatHistoryPerDay
10 | import com.github.mikephil.charting.charts.LineChart
11 | import com.github.mikephil.charting.data.Entry
12 | import com.github.mikephil.charting.data.LineDataSet
13 |
14 |
15 | fun String.formatToBeShowed(): String{
16 | var str = this
17 | if(this.indexOf(",") != -1) {
18 | str = str.replace(",", ".")
19 | str = if (str.indexOf(".", 4) == -1)
20 | str.substring(0, 5) + 'K'
21 | else
22 | str.substring(0, 5) + 'M'
23 | }
24 | return str
25 | }
26 |
27 |
28 | fun Int.toStringWithCommas() : String{
29 | var str = this.toString()
30 | val isNegative = str[0] == '-'
31 | if (isNegative) {str = str.replace("-", "")}
32 | var step = 3
33 | for(i in str.length-3 downTo 1 step step){
34 | str = str.substring(0, i) + ',' + str.substring(i, str.length)
35 | step++
36 | }
37 | if(isNegative) {str = "-$str"}
38 | return str
39 | }
40 |
41 | fun Context.hideKeyboard(view: View) {
42 | val inputMethodManager = getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
43 | inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0)
44 | }
45 |
46 | fun ArrayList.addEntry(dataHistory: List, stat: Double, index: Int){
47 | Entry((dataHistory.size - index).toFloat(), stat.toFloat()).let { this.add(it) }
48 | }
49 |
50 |
51 | fun LineDataSet.customizeDataSet(entries: ArrayList, chart: LineChart): LineDataSet {
52 | this.setDrawFilled(true)
53 | this.setDrawValues(false)
54 |
55 | val color = when(this.label){
56 | "Affected" -> ContextCompat.getColor(chart.context,
57 | R.color.yellow
58 | )
59 | "Deaths" -> ContextCompat.getColor(chart.context,
60 | R.color.red
61 | )
62 | else -> ContextCompat.getColor(chart.context,
63 | R.color.green
64 | )
65 | }
66 |
67 | this.color = color
68 |
69 | this.fillDrawable = when(this.label){
70 | "Affected" -> ContextCompat.getDrawable(
71 | chart.context,
72 | R.drawable.yellow_to_transparent_gradient
73 | )
74 | "Deaths" -> ContextCompat.getDrawable(chart.context,
75 | R.drawable.red_to_transparent_gradient
76 | )
77 | else -> ContextCompat.getDrawable(chart.context,
78 | R.drawable.green_to_transparent_gradient
79 | )
80 | }
81 |
82 | this.setColors(color)
83 |
84 | for (index in 0..entries.size - 2) {
85 | this.circleColors[0] = color
86 | }
87 | return this
88 | }
89 |
90 | fun Double?.plusToNullable(number: Double?): Double {
91 | val a = this ?: 0.0
92 | val b = number ?: 0.0
93 | return a + b
94 | }
95 |
96 | fun Double?.minusToNullable(double: Double): Double {
97 | val a = this ?: 0.0
98 | return a - double
99 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/font_certs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - @array/com_google_android_gms_fonts_certs_dev
5 | - @array/com_google_android_gms_fonts_certs_prod
6 |
7 |
8 | -
9 | MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAeFw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVyxW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8XW8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexAcKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkwHQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0cxb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrPzgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXclaXjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05aIskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+aayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUWEv9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs=
10 |
11 |
12 |
13 | -
14 | MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/coronavirus_stats/util/BindingUtils.kt:
--------------------------------------------------------------------------------
1 | package com.example.coronavirus_stats.util
2 |
3 | import android.widget.TextView
4 | import androidx.core.content.ContextCompat.getColor
5 | import androidx.core.content.ContextCompat.getDrawable
6 | import androidx.databinding.BindingAdapter
7 | import com.example.coronavirus_stats.R
8 | import com.example.coronavirus_stats.models.CountryStatHistoryPerDay
9 | import com.github.mikephil.charting.charts.LineChart
10 | import com.github.mikephil.charting.data.Entry
11 | import com.github.mikephil.charting.data.LineData
12 | import com.github.mikephil.charting.data.LineDataSet
13 | import com.github.mikephil.charting.interfaces.datasets.ILineDataSet
14 |
15 |
16 | @BindingAdapter("currentStat")
17 | fun setCurrentStat(textView: TextView, value: Double){
18 | textView.text = value.toInt().toStringWithCommas()
19 | }
20 |
21 |
22 | @BindingAdapter("simpleChartData")
23 | fun setSimpleChartData(chart: LineChart, dataHistory: List?) {
24 |
25 | val entries = ArrayList()
26 | if ((dataHistory != null) && (dataHistory.size >= 5)) {
27 | for (index in 0..4) {
28 | dataHistory[index]?.toFloat()?.let { Entry((4 - index).toFloat(), it) }?.let { entries.add(it) }
29 | }
30 | }
31 | val dataSet = LineDataSet(entries.reversed(), "Label")
32 | dataSet.setDrawFilled(true)
33 | dataSet.setDrawValues(false)
34 |
35 | val color = if(chart.id == R.id.chart1)
36 | getColor(chart.context, R.color.yellow)
37 | else
38 | getColor(chart.context, R.color.blue)
39 |
40 | dataSet.color = color
41 | dataSet.setColors(color)
42 | for (index in 0..4) {
43 | dataSet.circleColors[0] = color
44 | }
45 |
46 | dataSet.fillDrawable = if(chart.id == R.id.chart1)
47 | getDrawable(chart.context, R.drawable.yellow_to_transparent_gradient)
48 | else
49 | getDrawable(chart.context, R.drawable.blue_to_transparent_gradient)
50 |
51 | val lineData = LineData(dataSet)
52 | chart.data = lineData
53 | chart.invalidate()
54 | }
55 |
56 |
57 | @BindingAdapter("chartData")
58 | fun setChartData(chart: LineChart, dataHistory: List?) {
59 | val affectedEntries = ArrayList()
60 | val deathsEntries = ArrayList()
61 | val recoveredEntries = ArrayList()
62 |
63 | if (dataHistory != null) {
64 | for (index in dataHistory.indices) {
65 | dataHistory[index]?.confirmed?.let { affectedEntries.addEntry(dataHistory, it, index) }
66 | dataHistory[index]?.deaths?.let { deathsEntries.addEntry(dataHistory, it, index) }
67 | dataHistory[index]?.recovered?.let { recoveredEntries.addEntry(dataHistory, it, index) }
68 | }
69 | }
70 |
71 | val dataSets: MutableList = ArrayList()
72 |
73 | val affectedDataSet = LineDataSet(affectedEntries.reversed(), "Affected").customizeDataSet(affectedEntries, chart)
74 | val deathsDataSet = LineDataSet(deathsEntries.reversed(), "Deaths").customizeDataSet(deathsEntries, chart)
75 | val recoveredDataSet = LineDataSet(recoveredEntries.reversed(), "Recovered").customizeDataSet(recoveredEntries, chart)
76 |
77 | affectedDataSet.setDrawHighlightIndicators(false)
78 | deathsDataSet.setDrawHighlightIndicators(false)
79 | recoveredDataSet.setDrawHighlightIndicators(false)
80 |
81 | dataSets.add(affectedDataSet)
82 | dataSets.add(deathsDataSet)
83 | dataSets.add(recoveredDataSet)
84 |
85 | val lineData = LineData(dataSets)
86 | chart.data = lineData
87 | chart.invalidate()
88 | }
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | xmlns:android
17 |
18 | ^$
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | xmlns:.*
28 |
29 | ^$
30 |
31 |
32 | BY_NAME
33 |
34 |
35 |
36 |
37 |
38 |
39 | .*:id
40 |
41 | http://schemas.android.com/apk/res/android
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | .*:name
51 |
52 | http://schemas.android.com/apk/res/android
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | name
62 |
63 | ^$
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | style
73 |
74 | ^$
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | .*
84 |
85 | ^$
86 |
87 |
88 | BY_NAME
89 |
90 |
91 |
92 |
93 |
94 |
95 | .*
96 |
97 | http://schemas.android.com/apk/res/android
98 |
99 |
100 | ANDROID_ATTRIBUTE_ORDER
101 |
102 |
103 |
104 |
105 |
106 |
107 | .*
108 |
109 | .*
110 |
111 |
112 | BY_NAME
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/coronavirus_stats/util/Location.kt:
--------------------------------------------------------------------------------
1 | package com.example.coronavirus_stats.util
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.content.SharedPreferences
6 | import android.location.Address
7 | import android.location.Geocoder
8 | import android.location.Location
9 | import android.location.LocationManager
10 | import android.os.Looper
11 | import androidx.core.content.ContextCompat.getSystemService
12 | import com.google.android.gms.location.*
13 | import com.google.android.material.snackbar.Snackbar
14 | import kotlinx.coroutines.CoroutineScope
15 | import kotlinx.coroutines.Dispatchers
16 | import kotlinx.coroutines.launch
17 | import java.io.IOException
18 | import java.util.*
19 |
20 | object Location {
21 | private lateinit var fusedLocationClient: FusedLocationProviderClient
22 | private lateinit var settingsClient: SettingsClient
23 | private lateinit var locationCallback: LocationCallback
24 |
25 |
26 | fun initializeLocationClients(context: Context, prefs: SharedPreferences){
27 | fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
28 |
29 | settingsClient = LocationServices.getSettingsClient(context)
30 |
31 | locationCallback = object : LocationCallback() {
32 | override fun onLocationResult(locationResult: LocationResult?) {
33 | val currentLocation = locationResult?.lastLocation
34 | getCountry(currentLocation, context, prefs)
35 | }
36 | }
37 | }
38 |
39 | fun getLocation(activity: Activity, context: Context, prefs: SharedPreferences) {
40 |
41 | fusedLocationClient.lastLocation.addOnSuccessListener { location: Location? ->
42 |
43 | getCountry(location, context, prefs)
44 |
45 | if(SharedVariables.country == null){
46 | statusCheck(activity)
47 | startLocationUpdates()
48 | }
49 | }
50 | }
51 |
52 | private fun statusCheck(activity: Activity) {
53 | val manager =
54 | activity.getSystemService(Context.LOCATION_SERVICE) as LocationManager
55 | if (!manager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
56 | Snackbar.make(activity.findViewById(android.R.id.content),
57 | "Your GPS seems to be disabled, enable it and restart the app",
58 | Snackbar.LENGTH_LONG).show()
59 | }
60 | }
61 |
62 | private fun startLocationUpdates() {
63 | val locationRequest = LocationRequest.create().apply {
64 | interval = 0
65 | fastestInterval = 0
66 | numUpdates = 1
67 | priority = LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY
68 | }
69 |
70 | val locationSettingsRequest = LocationSettingsRequest.Builder().addLocationRequest(locationRequest).build()
71 |
72 | settingsClient.checkLocationSettings(locationSettingsRequest)
73 | .addOnSuccessListener {
74 | fusedLocationClient.requestLocationUpdates(
75 | locationRequest,
76 | locationCallback,
77 | Looper.getMainLooper()
78 | )
79 | }
80 | }
81 |
82 | private fun saveCountry(prefs: SharedPreferences){
83 | CoroutineScope(Dispatchers.IO).launch {
84 | prefs.edit().putString(Constants.COUNTRY_NAME, SharedVariables.country).apply()
85 | }
86 | }
87 |
88 | private fun getCountry(location: Location?, context: Context, prefs: SharedPreferences){
89 | val addresses: List?
90 | val geocoder = Geocoder(context, Locale.getDefault())
91 |
92 | try {
93 | addresses =
94 | location?.let { geocoder.getFromLocation(it.latitude, it.longitude, 1) }
95 | ?.toList()
96 | if (addresses != null) {
97 | SharedVariables.country = addresses[0].countryName.toString()
98 |
99 | saveCountry(prefs)
100 | }
101 |
102 | } catch (e: IOException) {
103 | e.printStackTrace()
104 | }
105 | }
106 |
107 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Coronavirus Stats
2 | Coronavirus Stats is an Android application that provides you the latest stats about Covid19, featured with a summary notification about your country delivered at the end of the day.
3 |
4 | Download from here:
5 |
6 | [](https://github.com/Ahmed-Sellami/Coronavirus-Stats/raw/master/apk/Coronavirus%20Stats.apk)
7 |
8 |
9 |
10 | Main Screen
11 | Main Screen (Overall Chart + Selected country)
12 | Notification
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | ## About
22 | At first you need to allow location permission if you want to get all app features. Afterwards, your country stat will be at the first of the list as shown in screenshots. Also, you will get a notification every day at 11:59 delivering a summary about the latest stat in your country.
23 |
24 | ## Built with
25 | - [Android Architecture Components](https://developer.android.com/topic/libraries/architecture) - Collection of libraries that help you design robust, testable, and maintainable apps.
26 | - [Material Components for Android](https://github.com/material-components/material-components-android) - Modular and customizable Material Design UI components for Android.
27 | - [Retrofit](https://square.github.io/retrofit/) - A type-safe HTTP client for Android and Java.
28 | - [Moshi](https://github.com/square/moshi) - A modern JSON library for Kotlin and Java.
29 | - [Moshi Converter](https://github.com/square/retrofit/tree/master/retrofit-converters/moshi) - A Converter which uses Moshi for serialization to and from JSON.
30 | - [Fused Location Provider](https://developers.google.com/android/reference/com/google/android/gms/location/FusedLocationProviderClient.html) - Provide the last known location of the user's device.
31 | - [MPAndroidChart](https://github.com/PhilJay/MPAndroidChart) - A powerful 🚀 Android chart view / graph view library, supporting line- bar- pie- radar- bubble- and candlestick charts as well as scaling, dragging and animations.
32 | - [Android SpinKit](https://github.com/ybq/Android-SpinKit) - Collection of Android loading animations.
33 | - [CircularProgressIndicator](https://github.com/antonKozyriatskyi/CircularProgressIndicator) - Simple but customizable view for displaying progress.
34 |
35 | ## Credits
36 | Thanks to:
37 |
38 | COVID-19 data for free api that provides updated statistic every 15 minutes.
39 | Covid API for historical stats.
40 | Coronavirus (Covid-19) Dashboard for neat and inspiring UI.
41 |
42 |
43 | ## Licence
44 | ```
45 | MIT License
46 |
47 | Copyright (c) 2020 Ahmed Sellami
48 |
49 | Permission is hereby granted, free of charge, to any person obtaining a copy
50 | of this software and associated documentation files (the "Software"), to deal
51 | in the Software without restriction, including without limitation the rights
52 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
53 | copies of the Software, and to permit persons to whom the Software is
54 | furnished to do so, subject to the following conditions:
55 |
56 | The above copyright notice and this permission notice shall be included in all
57 | copies or substantial portions of the Software.
58 |
59 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
60 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
61 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
62 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
63 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
64 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
65 | SOFTWARE.
66 | ```
67 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/coronavirus_stats/ui/adapters/CountryStatAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.example.coronavirus_stats.ui.adapters
2 |
3 | import android.view.LayoutInflater
4 | import android.view.ViewGroup
5 | import androidx.recyclerview.widget.ListAdapter
6 | import androidx.recyclerview.widget.DiffUtil
7 | import androidx.recyclerview.widget.RecyclerView
8 | import com.example.coronavirus_stats.databinding.ListItemBinding
9 | import com.example.coronavirus_stats.models.CountryCurrentStat
10 | import com.example.coronavirus_stats.util.SharedVariables.country
11 | import com.example.coronavirus_stats.util.SortEnum
12 | import com.example.coronavirus_stats.util.setCountriesFlagEmoji
13 | import java.util.*
14 |
15 |
16 | private val countriesFlagsEmoji =
17 | setCountriesFlagEmoji()
18 |
19 | typealias ListChangeListener = () -> Unit
20 | //Todo: Transform long types to typealias
21 |
22 | class CountryStatAdapter(
23 | private val listChangeListener: ListChangeListener,
24 | private val clickListener: CountryCurrentStatListener?
25 | ) : ListAdapter(
26 | DiffCallback
27 | ) {
28 |
29 |
30 | override fun onCreateViewHolder(
31 | parent: ViewGroup,
32 | viewType: Int
33 | ): CountryCurrentStatViewHolder {
34 | return CountryCurrentStatViewHolder(
35 | ListItemBinding.inflate(LayoutInflater.from(parent.context))
36 | )
37 | }
38 |
39 | override fun onBindViewHolder(holder: CountryCurrentStatViewHolder, position: Int) {
40 | val countryStat = getItem(position)
41 | holder.bind(countryStat, clickListener!!)
42 | }
43 |
44 | companion object DiffCallback : DiffUtil.ItemCallback() {
45 | override fun areItemsTheSame(
46 | oldItem: CountryCurrentStat,
47 | newItem: CountryCurrentStat
48 | ): Boolean {
49 | return oldItem === newItem
50 | }
51 |
52 | override fun areContentsTheSame(
53 | oldItem: CountryCurrentStat,
54 | newItem: CountryCurrentStat
55 | ): Boolean {
56 | return oldItem.country == newItem.country
57 | }
58 | }
59 |
60 | class CountryCurrentStatViewHolder(private var binding: ListItemBinding) :
61 | RecyclerView.ViewHolder(binding.root) {
62 | fun bind(countryStat: CountryCurrentStat, listener: CountryCurrentStatListener) {
63 | binding.countryCurrentStat = countryStat
64 |
65 | val emoji = countriesFlagsEmoji[countryStat.country]
66 | binding.emoji.text = emoji
67 |
68 | binding.clickListener = listener
69 | binding.executePendingBindings()
70 |
71 | if(country == countryStat.country){
72 | binding.home.visibility = ViewGroup.VISIBLE
73 | } else {
74 | binding.home.visibility = ViewGroup.GONE
75 | }
76 | }
77 | }
78 |
79 | override fun onCurrentListChanged(
80 | previousList: MutableList,
81 | currentList: MutableList
82 | ) {
83 | super.onCurrentListChanged(previousList, currentList)
84 |
85 | listChangeListener()
86 | }
87 | }
88 |
89 | class CountryCurrentStatListener(val clickListener: (countryCurrentStat: CountryCurrentStat) -> Unit) {
90 | fun onClick(country: CountryCurrentStat) {
91 | clickListener(country)
92 | }
93 | }
94 |
95 | fun MutableList.sortStatsBy(sortOrder: SortEnum): List {
96 | val list = this.toMutableList()
97 | val home = list[0]
98 | if (country != null){
99 | list.removeAt(0)
100 | }
101 | when (sortOrder) {
102 | SortEnum.AFFECTED -> {
103 | list.sortByDescending { it?.confirmed }
104 | }
105 | SortEnum.DEATHS -> {
106 | list.sortByDescending { it?.deaths }
107 | }
108 | SortEnum.RECOVERED -> {
109 | list.sortByDescending { it?.recovered}
110 | }
111 | }
112 | if (country != null){
113 | list.add(0, home)
114 | }
115 | return list
116 | }
117 |
118 | fun MutableList.search(query: String?): List {
119 | val list = this.toMutableList()
120 | if (!query.isNullOrEmpty()) {
121 | var index = 0
122 | while (index < list.size) {
123 | val item = list[index]
124 |
125 | val posOfQueryInsideCountryName =
126 | item?.country?.toLowerCase()?.indexOf(query.toLowerCase())
127 |
128 | if (posOfQueryInsideCountryName != 0) {
129 | list.removeAt(index)
130 | index--
131 | }
132 | index++
133 | }
134 | }
135 |
136 | return list
137 | }
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/list_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
11 |
12 |
15 |
16 |
17 |
18 |
25 |
26 |
40 |
41 |
52 |
53 |
65 |
66 |
75 |
76 |
88 |
89 |
99 |
100 |
110 |
111 |
121 |
122 |
132 |
133 |
134 |
135 |
136 |
137 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/coronavirus_stats/ui/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.coronavirus_stats.ui
2 |
3 | import android.Manifest.permission.ACCESS_FINE_LOCATION
4 | import android.app.AlarmManager
5 | import android.app.NotificationChannel
6 | import android.app.NotificationManager
7 | import android.app.PendingIntent
8 | import android.content.Context
9 | import android.content.Intent
10 | import android.content.SharedPreferences
11 | import android.graphics.Color
12 | import android.os.Build
13 | import android.os.Bundle
14 | import android.os.Looper
15 | import androidx.appcompat.app.AppCompatActivity
16 | import androidx.core.app.ActivityCompat
17 | import androidx.core.content.PermissionChecker
18 | import com.example.coronavirus_stats.util.Constants.COUNTRY_NAME
19 | import com.example.coronavirus_stats.util.Constants.NOTIFICATION_INTENT_ACTION
20 | import com.example.coronavirus_stats.util.Constants.REMINDER_HOUR
21 | import com.example.coronavirus_stats.util.Constants.REMINDER_MINUTE
22 | import com.example.coronavirus_stats.R
23 | import com.example.coronavirus_stats.util.SharedVariables.country
24 | import com.example.coronavirus_stats.receiver.AlarmReceiver
25 | import com.example.coronavirus_stats.util.Preferences.getPrefs
26 | import com.example.coronavirus_stats.util.Location.initializeLocationClients
27 | import com.example.coronavirus_stats.util.Location.getLocation
28 | import com.example.coronavirus_stats.util.isNetworkAvailable
29 | import com.google.android.gms.location.*
30 | import com.google.android.material.snackbar.Snackbar
31 | import kotlinx.coroutines.CoroutineScope
32 | import kotlinx.coroutines.Dispatchers
33 | import kotlinx.coroutines.launch
34 | import java.io.IOException
35 | import java.util.*
36 |
37 |
38 | class MainActivity : AppCompatActivity() {
39 |
40 | private val activityScope = CoroutineScope(Dispatchers.Default)
41 |
42 | private lateinit var prefs: SharedPreferences
43 |
44 | private val REQUEST_PERMISSIONS_REQUEST_CODE = 34
45 |
46 |
47 | override fun onCreate(savedInstanceState: Bundle?) {
48 | super.onCreate(savedInstanceState)
49 | setContentView(R.layout.activity_main)
50 |
51 | if (isNetworkAvailable(this)){
52 |
53 | createChannel(
54 | getString(R.string.summary_channel_id),
55 | getString(R.string.summary_channel_name)
56 | )
57 |
58 | prefs = getPrefs(this)
59 |
60 | initializeLocationClients(this, prefs)
61 |
62 | if (!checkPermissions()) {
63 | requestPermissions()
64 | } else {
65 | activityScope.launch {
66 | val countryName = prefs.getString(COUNTRY_NAME, null)
67 | country = countryName
68 |
69 | if(country == null){
70 | getLocation(this@MainActivity, this@MainActivity, prefs)
71 | }
72 | }
73 |
74 | }
75 |
76 | setupAlarmManager()
77 |
78 | } else {
79 | Snackbar.make(findViewById(android.R.id.content), "You are offline :(", Snackbar.LENGTH_LONG).show()
80 | }
81 |
82 | }
83 |
84 | private fun setupAlarmManager(){
85 | val alarmManager = this.getSystemService(Context.ALARM_SERVICE) as AlarmManager
86 |
87 | val REQUEST_CODE = 0
88 | val calendar: Calendar = Calendar.getInstance().apply {
89 | timeInMillis = System.currentTimeMillis()
90 | set(Calendar.HOUR_OF_DAY, REMINDER_HOUR)
91 | set(Calendar.MINUTE, REMINDER_MINUTE)
92 | }
93 |
94 | val notifyIntent = Intent(this, AlarmReceiver::class.java)
95 | notifyIntent.action = NOTIFICATION_INTENT_ACTION
96 |
97 | val notifyPendingIntent = PendingIntent.getBroadcast(
98 | application,
99 | REQUEST_CODE,
100 | notifyIntent,
101 | PendingIntent.FLAG_UPDATE_CURRENT
102 | )
103 |
104 | alarmManager.setInexactRepeating(
105 | AlarmManager.RTC_WAKEUP,
106 | calendar.timeInMillis,
107 | AlarmManager.INTERVAL_DAY,
108 | notifyPendingIntent
109 | )
110 |
111 | }
112 |
113 | override fun onRequestPermissionsResult(
114 | requestCode: Int,
115 | permissions: Array,
116 | grantResults: IntArray
117 | ) {
118 | super.onRequestPermissionsResult(requestCode, permissions, grantResults)
119 |
120 | getLocation(this@MainActivity, this@MainActivity, prefs)
121 | }
122 |
123 | private fun checkPermissions() =
124 | ActivityCompat.checkSelfPermission(
125 | this, ACCESS_FINE_LOCATION
126 | ) == PermissionChecker.PERMISSION_GRANTED
127 |
128 | private fun startLocationPermissionRequest() =
129 | ActivityCompat.requestPermissions(
130 | this, arrayOf(ACCESS_FINE_LOCATION),
131 | REQUEST_PERMISSIONS_REQUEST_CODE
132 | )
133 |
134 | private fun requestPermissions() {
135 | if (!ActivityCompat.shouldShowRequestPermissionRationale(this, ACCESS_FINE_LOCATION)) {
136 | startLocationPermissionRequest()
137 | }
138 | }
139 |
140 | private fun createChannel(channelId: String, channelName: String) {
141 |
142 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
143 | val notificationChannel = NotificationChannel(
144 | channelId,
145 | channelName,
146 | NotificationManager.IMPORTANCE_HIGH
147 | )
148 |
149 | notificationChannel.enableLights(true)
150 | notificationChannel.lightColor = Color.RED
151 | notificationChannel.enableVibration(true)
152 | notificationChannel.description = "Time for today's summary"
153 |
154 | val notificationManager = this.getSystemService(
155 | NotificationManager::class.java
156 | )
157 | notificationManager?.createNotificationChannel(notificationChannel)
158 | }
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/active_cases_stat_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
10 |
11 |
12 |
15 |
16 |
38 |
39 |
50 |
51 |
62 |
63 |
72 |
73 |
84 |
85 |
95 |
96 |
97 |
98 |
108 |
109 |
120 |
121 |
122 |
131 |
132 |
143 |
144 |
154 |
155 |
156 |
157 |
158 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/coronavirus_stats/ui/fragments/OverviewFragment.kt:
--------------------------------------------------------------------------------
1 | package com.example.coronavirus_stats.ui.fragments
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.AdapterView
8 | import android.widget.ArrayAdapter
9 | import android.widget.SearchView
10 | import androidx.fragment.app.Fragment
11 | import androidx.lifecycle.Observer
12 | import androidx.lifecycle.ViewModelProvider
13 | import androidx.recyclerview.widget.LinearLayoutManager
14 | import com.example.coronavirus_stats.*
15 | import com.example.coronavirus_stats.databinding.FragmentOverviewBinding
16 | import com.example.coronavirus_stats.models.CountryCurrentStat
17 | import com.example.coronavirus_stats.ui.OverviewViewModel
18 | import com.example.coronavirus_stats.ui.adapters.*
19 | import com.example.coronavirus_stats.util.MarginItemDecoration
20 | import com.example.coronavirus_stats.util.SharedVariables.country
21 | import com.example.coronavirus_stats.util.SortEnum
22 | import com.example.coronavirus_stats.util.hideKeyboard
23 | import com.google.android.material.tabs.TabLayoutMediator
24 | import kotlinx.coroutines.*
25 |
26 |
27 | class OverviewFragment : Fragment(){
28 |
29 | private lateinit var viewModel : OverviewViewModel
30 |
31 | private lateinit var binding : FragmentOverviewBinding
32 |
33 | private var adapter =
34 | CountryStatAdapter({}, null)
35 |
36 | private var adapterOriginalList = mutableListOf()
37 |
38 | private val coroutineScope = CoroutineScope(
39 | Job() + Dispatchers.Main
40 | )
41 |
42 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
43 | savedInstanceState: Bundle?): View? {
44 |
45 | binding = FragmentOverviewBinding.inflate(inflater)
46 |
47 | viewModel = activity?.run {
48 | ViewModelProvider(this)[OverviewViewModel::class.java]
49 | } ?: throw Exception("Invalid Activity")
50 |
51 | // Allow Data Binding to Observe LiveData with the lifecycle of this Fragment's view
52 | binding.lifecycleOwner = viewLifecycleOwner
53 |
54 | binding.viewModel = viewModel
55 |
56 | binding.viewPager.adapter =
57 | ChartsPagerAdapter(this)
58 |
59 |
60 | setupTabLayout()
61 | setupSpinner()
62 | setupRecyclerView()
63 |
64 | adapter = binding.recyclerView.adapter as CountryStatAdapter
65 |
66 | setupObservers()
67 | setupSearching()
68 | setupClickListener()
69 |
70 | return binding.root
71 | }
72 |
73 |
74 | private fun setupRecyclerView(){
75 | binding.recyclerView.apply {
76 | layoutManager = LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false)
77 |
78 | addItemDecoration(
79 | MarginItemDecoration(
80 | resources.getDimension(R.dimen.item_margin).toInt()
81 | )
82 | )
83 |
84 | adapter =
85 | CountryStatAdapter(
86 | { binding.recyclerView.layoutManager?.scrollToPosition(0) },
87 | CountryCurrentStatListener { innerItem ->
88 | coroutineScope.launch {
89 | viewModel.getCountryHistoryStat(innerItem)
90 | }
91 | viewModel.isGlobal = false
92 | binding.globe.visibility = View.VISIBLE
93 | context.hideKeyboard(this)
94 | binding.root.postDelayed(
95 | {
96 | binding.root.scrollTo(0, binding.root.top)
97 | }, 100
98 | )
99 | binding.spinKit.visibility = View.VISIBLE
100 | }
101 | )
102 | }
103 | }
104 |
105 | private fun setupObservers(){
106 | viewModel.countriesCurrentStat.observe(viewLifecycleOwner, Observer {
107 | adapterOriginalList = it.toMutableList()
108 | if (country != null){
109 | var index = -1
110 | do {
111 | index++
112 | } while ((adapterOriginalList[index]?.country != country) && (index < adapterOriginalList.size - 1))
113 | val home = adapterOriginalList[index]
114 | adapterOriginalList.removeAt(index)
115 | adapterOriginalList.add(0, home)
116 | }
117 | adapter.submitList(adapterOriginalList)
118 | })
119 |
120 |
121 | viewModel.isListReady.observe(viewLifecycleOwner, Observer {
122 | if(it){
123 | binding.spinKit1.visibility = View.GONE
124 | }
125 | })
126 | viewModel.countryHistoryStat.observe(viewLifecycleOwner, Observer {
127 | if(it != null){
128 | binding.spinKit.visibility = View.GONE
129 | }
130 | })
131 |
132 | }
133 |
134 | private fun setupSearching(){
135 | binding.searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener{
136 | override fun onQueryTextSubmit(query: String?): Boolean {
137 | adapter.submitList(adapterOriginalList.search(query))
138 | return false
139 | }
140 |
141 | override fun onQueryTextChange(query: String?): Boolean {
142 | adapter.submitList(adapterOriginalList.search(query))
143 | binding.root.scrollTo(0, binding.recyclerView.bottom)
144 | return false
145 | }
146 |
147 | })
148 | }
149 |
150 | private fun setupClickListener(){
151 | binding.globe.setOnClickListener {
152 | it.visibility = View.GONE
153 | viewModel.isGlobal = true
154 | coroutineScope.launch {
155 | viewModel.getCountryHistoryStat(null)
156 | }
157 | binding.spinKit.visibility = View.VISIBLE
158 | }
159 | }
160 |
161 | private fun setupSpinner(){
162 | ArrayAdapter.createFromResource(
163 | requireContext(),
164 | R.array.sort_choices_options,
165 | android.R.layout.simple_spinner_item
166 | ).also { adapter ->
167 | adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
168 | binding.spinner.adapter = adapter
169 | }
170 |
171 | var selected : SortEnum
172 |
173 | binding.spinner.onItemSelectedListener = object: AdapterView.OnItemSelectedListener{
174 | override fun onItemSelected(parent: AdapterView<*>?, view: View?, pos: Int, id: Long) {
175 | selected = when(pos){
176 | 1 -> SortEnum.DEATHS
177 | 2 -> SortEnum.RECOVERED
178 | else -> SortEnum.AFFECTED
179 | }
180 |
181 | if (adapter.itemCount != 0) {
182 | adapter.submitList(adapter.currentList.sortStatsBy(selected))
183 | }
184 | }
185 |
186 | override fun onNothingSelected(parent: AdapterView<*>?) {
187 | selected = SortEnum.AFFECTED
188 | }
189 | }
190 | }
191 |
192 | private fun setupTabLayout(){
193 | TabLayoutMediator(binding.tabLayout, binding.viewPager) { currentTab, currentPosition ->
194 | currentTab.text = when (currentPosition) {
195 | ChartsPagerAdapter.ACTIVE_CASES_POSITION -> getString(
196 | R.string.active_cases
197 | )
198 | ChartsPagerAdapter.OVERALL_POSITION -> getString(
199 | R.string.overall
200 | )
201 | else -> getString(R.string.no_item)
202 | }
203 | }.attach()
204 |
205 | for (i in 0 until binding.tabLayout.tabCount) {
206 | val tab = (binding.tabLayout.getChildAt(0) as ViewGroup).getChildAt(i)
207 | val p = tab.layoutParams as ViewGroup.MarginLayoutParams
208 | p.setMargins(20, 0, 20, 0)
209 | tab.requestLayout()
210 | }
211 | }
212 |
213 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/coronavirus_stats/ui/OverviewViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.example.coronavirus_stats.ui
2 |
3 | import android.util.Log
4 | import androidx.lifecycle.*
5 | import com.example.coronavirus_stats.models.CountryCurrentStat
6 | import com.example.coronavirus_stats.api.CoronavirusHistoryApi
7 | import com.example.coronavirus_stats.api.CoronavirusMonitorApi
8 | import com.example.coronavirus_stats.models.CountryStatHistoryPerDay
9 | import com.example.coronavirus_stats.util.formatToBeShowed
10 | import com.example.coronavirus_stats.util.getTheSixPreviousDays
11 | import com.example.coronavirus_stats.util.setCountriesFlagEmoji
12 | import com.example.coronavirus_stats.util.toStringWithCommas
13 | import kotlinx.coroutines.*
14 | import java.util.*
15 | import kotlin.collections.set
16 |
17 | class OverviewViewModel : ViewModel() {
18 |
19 | private var viewModelJob = Job()
20 | private val coroutineScope = CoroutineScope(
21 | viewModelJob + Dispatchers.Main
22 | )
23 |
24 | private val _countryCurrentStat = MutableLiveData()
25 | val countryCurrentStat: LiveData
26 | get() = _countryCurrentStat
27 |
28 | private val _countryHistoryStat = MutableLiveData>()
29 | val countryHistoryStat: LiveData>
30 | get() = _countryHistoryStat
31 |
32 | private val _countryHistoryAffected = MutableLiveData>()
33 | val countryHistoryAffected: LiveData>
34 | get() = _countryHistoryAffected
35 |
36 | private val _countryHistoryActiveCases = MutableLiveData>()
37 | val countryHistoryActiveCases: LiveData>
38 | get() = _countryHistoryActiveCases
39 |
40 | private var _countriesCurrentStat = MutableLiveData>()
41 | val countriesCurrentStat: LiveData>
42 | get() = _countriesCurrentStat
43 |
44 | private val _isListReady = MutableLiveData()
45 | val isListReady: LiveData
46 | get() = _isListReady
47 |
48 | private val _totalAffected = MutableLiveData()
49 | val totalAffected: LiveData
50 | get() = _totalAffected
51 | private val _totalDeaths = MutableLiveData()
52 | val totalDeaths: LiveData
53 | get() = _totalDeaths
54 | private val _totalRecovered = MutableLiveData()
55 | val totalRecovered: LiveData
56 | get() = _totalRecovered
57 |
58 | private val _newAffected = MutableLiveData()
59 | val newAffected: LiveData
60 | get() = _newAffected
61 | private val _newDeaths = MutableLiveData()
62 | val newDeaths: LiveData
63 | get() = _newDeaths
64 | private val _newRecovered = MutableLiveData()
65 | val newRecovered: LiveData
66 | get() = _newRecovered
67 |
68 | private val _totalAffectedSmallFormat = MutableLiveData()
69 | val totalAffectedSmallFormat: LiveData
70 | get() = _totalAffectedSmallFormat
71 |
72 | private val _totalActiveCases = MutableLiveData()
73 | val totalActiveCases: LiveData
74 | get() = _totalActiveCases
75 |
76 | private val _countryName = MutableLiveData()
77 | val countryName: LiveData
78 | get() = _countryName
79 | private val _countryFlag = MutableLiveData()
80 | val countryFlag: LiveData
81 | get() = _countryFlag
82 |
83 | var isGlobal = true
84 |
85 | private val countriesISO = setCountriesISOCode()
86 | private val countriesFlagsEmoji =
87 | setCountriesFlagEmoji()
88 | private val previousSixDays =
89 | getTheSixPreviousDays(
90 | "yyyy-MM-dd",
91 | false
92 | )
93 |
94 |
95 |
96 | init {
97 | viewModelScope.launch {
98 | getCountryHistoryStat(null)
99 | getCurrentStatForEachCountry()
100 | }
101 | }
102 |
103 | suspend fun getCountryHistoryStat(countryCurrentStat: CountryCurrentStat?) =
104 | withContext(Dispatchers.Main) {
105 | val countryHistoryStatDelegate = mutableListOf()
106 | val countryHistoryAffectedDelegate = mutableListOf()
107 | val countryHistoryActiveCasesDelegate = mutableListOf()
108 | val todayAffected: Double
109 | val todayDeaths: Double
110 | val todayRecovered: Double
111 |
112 | var countryCurrentStatRef: CountryCurrentStat?
113 | if (isGlobal) {
114 | try {
115 | val worldStat = CoronavirusMonitorApi.retrofitService.getWorldStat().await()
116 |
117 | countryCurrentStatRef =
118 | CountryCurrentStat(
119 | "Global",
120 | worldStat[0].confirmed.toDouble(),
121 | worldStat[0].deaths.toDouble(),
122 | worldStat[0].recovered.toDouble()
123 | )
124 |
125 | } catch (e: Exception) {
126 | countryCurrentStatRef = null
127 | Log.e(
128 | "OverviewViewModel",
129 | "Problem with getting current world stat: ${e.message}"
130 | )
131 | }
132 | } else {
133 | countryCurrentStatRef = countryCurrentStat
134 | }
135 | _countryCurrentStat.value = countryCurrentStatRef
136 |
137 | val countryTodayStat: CountryStatHistoryPerDay? =
138 | CountryStatHistoryPerDay(
139 | countryCurrentStatRef?.confirmed ?: 0.0,
140 | countryCurrentStatRef?.deaths ?: 0.0,
141 | countryCurrentStatRef?.recovered ?: 0.0
142 | )
143 | todayAffected = countryCurrentStatRef?.confirmed ?: 0.0
144 | todayDeaths = countryCurrentStatRef?.deaths ?: 0.0
145 | todayRecovered = countryCurrentStatRef?.recovered ?: 0.0
146 | countryHistoryAffectedDelegate.add(todayAffected)
147 | countryHistoryActiveCasesDelegate.add(todayAffected - todayDeaths - todayRecovered)
148 |
149 |
150 | if (!isGlobal) {
151 | _countryName.value = countryCurrentStatRef?.country
152 | _countryFlag.value = countriesFlagsEmoji[_countryName.value]
153 | } else {
154 | _countryName.value = "Global"
155 | _countryFlag.value = "\uD83C\uDF0E"
156 | }
157 |
158 | _totalAffectedSmallFormat.value =
159 | countryCurrentStatRef?.confirmed?.toInt()?.toStringWithCommas()?.formatToBeShowed()
160 |
161 | _totalActiveCases.value =
162 | countryHistoryActiveCasesDelegate[0]?.toInt()?.toStringWithCommas()
163 | ?.formatToBeShowed()
164 |
165 | countryHistoryStatDelegate.add(countryTodayStat)
166 |
167 | var countryISO: String? = null
168 | if (!isGlobal) {
169 | countryISO = countriesISO[countryCurrentStat?.country]
170 |
171 | if (countryISO == null) {
172 | countryISO = when (countryCurrentStat?.country) {
173 | "USA" -> "USA"
174 | "UK" -> "GBR"
175 | "S. Korea" -> "KOR"
176 | "UAE" -> "ARE"
177 | "Bosnia & Herzegovina" -> "BIH"
178 | else -> countryCurrentStat?.country
179 | }
180 | }
181 | }
182 |
183 | var updateHeaderValues = true
184 |
185 | for (day in previousSixDays) {
186 | try {
187 | val result: CountryStatHistoryPerDay? = if (isGlobal) {
188 | val worldHistoryStatResponse =
189 | CoronavirusHistoryApi.retrofitService.getWorldStatHistoryPerDay(day)
190 | .await()
191 |
192 | worldHistoryStatResponse.result
193 | } else {
194 | val countryStatHistoryResponse =
195 | CoronavirusHistoryApi.retrofitService.getCountryStatHistoryPerDay(
196 | countryISO,
197 | day
198 | ).await()
199 |
200 | countryStatHistoryResponse.result[day]
201 | }
202 |
203 | if (updateHeaderValues) {
204 | notifyHeaderValues(
205 | todayAffected.toInt().toStringWithCommas(),
206 | todayDeaths.toInt().toStringWithCommas(),
207 | todayRecovered.toInt().toStringWithCommas(),
208 | (todayAffected - result?.confirmed!!).toInt().toStringWithCommas(),
209 | (todayDeaths - result.deaths).toInt().toStringWithCommas(),
210 | (todayRecovered - result.recovered).toInt().toStringWithCommas()
211 | )
212 | updateHeaderValues = false
213 | }
214 |
215 | countryHistoryAffectedDelegate.add(result?.confirmed)
216 | countryHistoryActiveCasesDelegate.add(result?.confirmed!! - result.deaths - result.recovered)
217 | countryHistoryStatDelegate.add(result)
218 |
219 | } catch (e: Exception) {
220 | continue
221 | }
222 | }
223 | _countryHistoryAffected.value = countryHistoryAffectedDelegate
224 | _countryHistoryActiveCases.value = countryHistoryActiveCasesDelegate
225 | _countryHistoryStat.value = countryHistoryStatDelegate
226 | }
227 |
228 |
229 | private suspend fun getCurrentStatForEachCountry() = withContext(Dispatchers.Main) {
230 |
231 | val countriesStats = mutableListOf()
232 | for (country in countriesISO.keys) {
233 | try {
234 | val countryName = when (country) {
235 | "United States" -> "USA"
236 | else -> country
237 | }
238 |
239 | val countryStat =
240 | CoronavirusMonitorApi.retrofitService.getCountryStat(countryName).await()
241 | countriesStats.add(countryStat[0])
242 | countriesStats.sortByDescending { it?.confirmed }
243 | _countriesCurrentStat.value = countriesStats
244 |
245 | } catch (e: Exception) {
246 | Log.i(
247 | "OverviewViewModel",
248 | "Problem with $country | Error: " + e.message
249 | )
250 | continue
251 | } finally {
252 | // The free subscription to the api service requires you
253 | // to make one single request every second.
254 | delay(1200)
255 | }
256 | }
257 |
258 | _isListReady.value = true
259 | }
260 |
261 |
262 | private fun notifyHeaderValues(
263 | totalCases: String?,
264 | totalDeath: String?,
265 | totalRecovered: String?,
266 | newCases: String?,
267 | newDeaths: String?,
268 | newRecovered: String?
269 | ) {
270 | _totalAffected.value = totalCases
271 | _totalDeaths.value = totalDeath
272 | _totalRecovered.value = totalRecovered
273 |
274 | _newAffected.value = newCases
275 | _newDeaths.value = newDeaths
276 | _newRecovered.value = newRecovered
277 | }
278 |
279 | private fun setCountriesISOCode(): MutableMap {
280 | val countriesISO: MutableMap = HashMap()
281 |
282 | for (iso in Locale.getISOCountries()) {
283 | val l = Locale("", iso)
284 | countriesISO[l.displayCountry] = l.isO3Country
285 | }
286 | return countriesISO
287 | }
288 |
289 | override fun onCleared() {
290 | super.onCleared()
291 | viewModelJob.cancel()
292 | }
293 |
294 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_overview.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
10 |
11 |
12 |
15 |
16 |
20 |
21 |
28 |
29 |
40 |
41 |
42 |
54 |
55 |
69 |
70 |
85 |
86 |
97 |
98 |
105 |
106 |
118 |
119 |
128 |
129 |
139 |
140 |
141 |
142 |
152 |
153 |
160 |
161 |
173 |
174 |
183 |
184 |
194 |
195 |
196 |
197 |
207 |
208 |
215 |
216 |
228 |
229 |
238 |
239 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
260 |
261 |
267 |
268 |
282 |
283 |
292 |
293 |
301 |
302 |
303 |
304 |
314 |
315 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
--------------------------------------------------------------------------------