diff --git a/assets/images/latest-numbers.png b/assets/images/latest-numbers.png index e12b3c2..0cd4d43 100644 Binary files a/assets/images/latest-numbers.png and b/assets/images/latest-numbers.png differ diff --git a/assets/images/myth-busters.png b/assets/images/myth-busters.png new file mode 100644 index 0000000..fb63fea Binary files /dev/null and b/assets/images/myth-busters.png differ diff --git a/assets/images/symptoms.png b/assets/images/symptoms.png new file mode 100644 index 0000000..582bd0d Binary files /dev/null and b/assets/images/symptoms.png differ diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 0de8df6..a65366f 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,4 +1,9 @@ PODS: + - connectivity (0.0.1): + - Flutter + - Reachability + - connectivity_macos (0.0.1): + - Flutter - Flutter (1.0.0) - FMDB (2.7.5): - FMDB/standard (= 2.7.5) @@ -7,6 +12,7 @@ PODS: - Flutter - path_provider_macos (0.0.1): - Flutter + - Reachability (3.2) - sqflite (0.0.1): - Flutter - FMDB (~> 2.7.2) @@ -18,6 +24,8 @@ PODS: - Flutter DEPENDENCIES: + - connectivity (from `.symlinks/plugins/connectivity/ios`) + - connectivity_macos (from `.symlinks/plugins/connectivity_macos/ios`) - Flutter (from `Flutter`) - path_provider (from `.symlinks/plugins/path_provider/ios`) - path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`) @@ -29,8 +37,13 @@ DEPENDENCIES: SPEC REPOS: trunk: - FMDB + - Reachability EXTERNAL SOURCES: + connectivity: + :path: ".symlinks/plugins/connectivity/ios" + connectivity_macos: + :path: ".symlinks/plugins/connectivity_macos/ios" Flutter: :path: Flutter path_provider: @@ -47,10 +60,13 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/url_launcher_web/ios" SPEC CHECKSUMS: + connectivity: 6e94255659cc86dcbef1d452ad3e0491bb1b3e75 + connectivity_macos: e2e9731b6b22dda39eb1b128f6969d574460e191 Flutter: 0e3d915762c693b495b44d77113d4970485de6ec FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a path_provider: fb74bd0465e96b594bb3b5088ee4a4e7bb1f2a9d path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0 + Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 sqflite: 4001a31ff81d210346b500c55b17f4d6c7589dd0 url_launcher: a1c0cc845906122c4784c542523d8cacbded5626 url_launcher_macos: fd7894421cd39320dce5f292fc99ea9270b2a313 diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 88e73a1..bfe5e2c 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -26,6 +26,8 @@ LaunchScreen UIMainStoryboardFile Main + UIStatusBarStyle + UIStatusBarStyleDarkContent UISupportedInterfaceOrientations UIInterfaceOrientationPortrait diff --git a/lib/constants/strings.dart b/lib/constants/strings.dart index 90170c7..713a8d9 100644 --- a/lib/constants/strings.dart +++ b/lib/constants/strings.dart @@ -28,6 +28,10 @@ class Strings { static const preventionTitle = 'Prevention'; + static const symptomsTitle = 'Symptoms'; + + static const mythBusterTitle = 'Myth Busters'; + /// ------------------------------------------------------ /// Font Styles for Statistics and StatisticsLoadingWidget /// used in (ui/statistics) and (ui/statistics/widgets) diff --git a/lib/constants/text_styles.dart b/lib/constants/text_styles.dart index f39162d..e68d9fd 100644 --- a/lib/constants/text_styles.dart +++ b/lib/constants/text_styles.dart @@ -38,7 +38,7 @@ class TextStyles { /// ------------------------------------------------------ /// Font Styles for Statistics Screen - /// used in (ui/statistics) + /// used in (ui/statistics) and (ui/statistics/widgets) /// static const statisticsHeadingTextStlye = TextStyle( fontWeight: FontWeight.w700, @@ -64,6 +64,16 @@ class TextStyles { color: AppColors.offBlackColor, ); + static const statisticsLabelTextStyle = TextStyle( + fontWeight: FontWeight.w500, + color: AppColors.accentBlackColor, + ); + + static const statisticsToopTipTextStyle = TextStyle( + fontWeight: FontWeight.w500, + color: AppColors.whiteColor, + ); + /// ------------------------------------------------------ /// Font Styles for Home Screen /// used in (ui/home) diff --git a/lib/data/countries_list_data.dart b/lib/data/countries_list_data.dart index ddb506e..14723dd 100644 --- a/lib/data/countries_list_data.dart +++ b/lib/data/countries_list_data.dart @@ -542,11 +542,6 @@ const countriesListJSON = ''' "ISO2": "LV" }, { -"Country": "Netherlands Antilles", -"Slug": "netherlands-antilles", -"ISO2": "AN" -}, -{ "Country": "Peru", "Slug": "peru", "ISO2": "PE" diff --git a/lib/data/network/constants/endpoints.dart b/lib/data/network/constants/endpoints.dart index 6bd60b9..7069499 100644 --- a/lib/data/network/constants/endpoints.dart +++ b/lib/data/network/constants/endpoints.dart @@ -4,14 +4,8 @@ class Endpoints { // base url static const baseUrlStatistics = "https://api.covid19api.com"; - // base url to retreieve user's IP - static const baseUrlIP = "https://httpbin.org/ip"; - - // base url to retrive iso2 code based on IP - static const baseUrlCurrentCountry = 'https://freegeoip.live/json'; - - // base url to retrieve country flags - static const baseUrlCountryFlags = 'https://www.countryflags.io/'; + // base url to retreieve user's country ISO2 code + static const baseUrlIPLookup = "https://ip.rootnet.in/lookup"; // base url for Covid-19 prevention Infographic static const baseUrlPreventionInfographic = @@ -38,10 +32,5 @@ class Endpoints { static String get fetchCountryStatistics => baseUrlStatistics + _fetchCountryStatistics; - - static String get fetchIP => baseUrlIP; - - static String get fetchCurrentCountry => baseUrlCurrentCountry; - - static String get fetchCountryFlags => baseUrlCountryFlags; + static String get fetchCurrentCountry => baseUrlIPLookup; } diff --git a/lib/data/repository/base_repository.dart b/lib/data/repository/base_repository.dart index 8ff1795..c618095 100644 --- a/lib/data/repository/base_repository.dart +++ b/lib/data/repository/base_repository.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:covid19/models/application/ip_model.dart'; import 'package:covid19/models/application/country_information_model.dart'; import 'package:covid19/models/statistics/countries_list_model.dart'; import 'package:covid19/models/statistics/statistics_response_model.dart'; @@ -10,12 +9,8 @@ import 'package:covid19/models/statistics/country_statistics_day_model.dart'; /// [TestRepository] and [UserRepository] extend the BaseRepository /// to override and implement the API requests abstract class BaseRepository { - /// Fetch List of all countries - Future fetchUserIP(); - /// Fetch Current Country iso2 for the user - Future fetchUserCountryInformation( - {@required String ipAddress}); + Future fetchUserCountryInformation(); /// Fetch List of all countries Future> fetchCountriesList(); diff --git a/lib/data/repository/test_repository.dart b/lib/data/repository/test_repository.dart index d9eda44..e15c2d2 100644 --- a/lib/data/repository/test_repository.dart +++ b/lib/data/repository/test_repository.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:covid19/data/repository/base_repository.dart'; -import 'package:covid19/models/application/ip_model.dart'; import 'package:covid19/models/application/country_information_model.dart'; import 'package:covid19/models/statistics/countries_list_model.dart'; import 'package:covid19/models/statistics/country_statistics_day_model.dart'; @@ -15,22 +14,14 @@ class TestRepository implements BaseRepository { await Future.delayed(const Duration(milliseconds: 1000)); } - @override - Future fetchUserIP() async { - _wait(); - return IPModel( - origin: "103.69.21.105", - ); - } - @override Future fetchUserCountryInformation({ @required String ipAddress, }) async { _wait(); return CountryInformationModel( - countryName: "India", - countryCode: "IN", + ip: "127.0.0.1", + country: "IN", ); } diff --git a/lib/data/repository/user_repository.dart b/lib/data/repository/user_repository.dart index e945810..20fe855 100644 --- a/lib/data/repository/user_repository.dart +++ b/lib/data/repository/user_repository.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:covid19/data/countries_list_data.dart'; import 'package:covid19/data/network/constants/endpoints.dart'; import 'package:covid19/data/repository/base_repository.dart'; -import 'package:covid19/models/application/ip_model.dart'; import 'package:covid19/models/statistics/statistics_response_model.dart'; import 'package:covid19/models/application/country_information_model.dart'; import 'package:covid19/models/statistics/countries_list_model.dart'; @@ -12,26 +11,13 @@ import 'package:covid19/utils/request_util.dart'; /// Extends the [BaseRepository] to implement the API request methods class UserRepository implements BaseRepository { - @override - Future fetchUserIP() async { - final jsonResponse = await HttpRequestUtil.getRequest( - Endpoints.fetchIP, - ); - - HttpRequestUtil.handleResponseError( - jsonResponse, - 'Error fetching User\'s IP', - ); - - return IPModel.fromJson(jsonResponse); - } - @override Future fetchUserCountryInformation({ @required String ipAddress, }) async { final jsonResponse = await HttpRequestUtil.getRequest( - '${Endpoints.fetchCurrentCountry}/$ipAddress', + url: '${Endpoints.fetchCurrentCountry}', + shouldCache: false, ); HttpRequestUtil.handleResponseError( @@ -53,7 +39,8 @@ class UserRepository implements BaseRepository { @required String iso2, }) async { final jsonResponse = await HttpRequestUtil.getRequest( - '${Endpoints.fetchHomeData}', + url: '${Endpoints.fetchHomeData}', + shouldCache: true, ); HttpRequestUtil.handleResponseError( @@ -70,7 +57,8 @@ class UserRepository implements BaseRepository { CountryStatistics countryStatistics; final responseMap = await HttpRequestUtil.getRequest( - '${Endpoints.fetchCountryStatistics}$iso2/status/confirmed', + url: '${Endpoints.fetchCountryStatistics}$iso2/status/confirmed', + shouldCache: true, ); for (int i = 0; i < responseMap.length; i++) { diff --git a/lib/icons/covid19_icons.dart b/lib/icons/covid19_icons.dart index 7184ea7..e649420 100644 --- a/lib/icons/covid19_icons.dart +++ b/lib/icons/covid19_icons.dart @@ -1,26 +1,3 @@ -/// Flutter icons Covid19 -/// Copyright (C) 2020 by original authors @ fluttericon.com, fontello.com -/// This font was generated by FlutterIcon.com, which is derived from Fontello. -/// -/// To use this font, place it in your fonts/ directory and include the -/// following in your pubspec.yaml -/// -/// flutter: -/// fonts: -/// - family: Covid19 -/// fonts: -/// - asset: fonts/Covid19.ttf -/// -/// -/// * Material Design Icons, Copyright (C) Google, Inc -/// Author: Google -/// License: Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) -/// Homepage: https://design.google.com/icons/ -/// * Entypo, Copyright (C) 2012 by Daniel Bruce -/// Author: Daniel Bruce -/// License: SIL (http://scripts.sil.org/OFL) -/// Homepage: http://www.entypo.com -/// import 'package:flutter/widgets.dart'; class Covid19Icons { diff --git a/lib/main.dart b/lib/main.dart index 8e2a11d..177e286 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,8 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:bloc/bloc.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:provider/provider.dart'; import 'package:covid19/constants/app_theme.dart'; import 'package:covid19/constants/colors.dart'; import 'package:covid19/constants/strings.dart'; @@ -13,10 +17,7 @@ import 'package:covid19/ui/static/static_error_screen.dart'; import 'package:covid19/utils/bloc/application_bloc.dart'; import 'package:covid19/utils/bloc/application_events.dart'; import 'package:covid19/utils/bloc/application_state.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:provider/provider.dart'; +import 'package:covid19/utils/connection_status_singleton.dart'; /// [SimpleBloocDelegate] handles all the Bloc events delegated by the [BlocSupervisor] class SimpleBlocDelegate extends BlocDelegate { @@ -46,6 +47,11 @@ Future main() async { // Initialize WidgetsBinding before runApp WidgetsFlutterBinding.ensureInitialized(); + + final ConnectionStatusSingleton connectionStatus = + ConnectionStatusSingleton.getInstance(); + connectionStatus.initialize(); + SystemChrome.setPreferredOrientations([ DeviceOrientation.portraitUp, ]).then( @@ -107,16 +113,6 @@ class MyApp extends StatelessWidget { userRepository: repository, ), child: Scaffold( - // [AppBar] with 0 size used to set the statusbar background color and - // statusbat text/icon color - appBar: PreferredSize( - preferredSize: const Size.fromHeight(0.0), - child: AppBar( - backgroundColor: AppColors.whiteColor, - brightness: Brightness.light, - elevation: 0.0, - ), - ), body: HomeNavigator(), ), ); diff --git a/lib/models/application/country_information_model.dart b/lib/models/application/country_information_model.dart index b18cf20..552ae54 100644 --- a/lib/models/application/country_information_model.dart +++ b/lib/models/application/country_information_model.dart @@ -1,57 +1,18 @@ -/// [Json2Dart](https://javiercbk.github.io/json_to_dart/) was used to generate the model class class CountryInformationModel { String ip; - String countryCode; - String countryName; - String regionCode; - String regionName; - String city; - String zipCode; - String timeZone; - double latitude; - double longitude; - int metroCode; + String country; - CountryInformationModel( - {this.ip, - this.countryCode, - this.countryName, - this.regionCode, - this.regionName, - this.city, - this.zipCode, - this.timeZone, - this.latitude, - this.longitude, - this.metroCode}); + CountryInformationModel({this.ip, this.country}); CountryInformationModel.fromJson(Map json) { ip = json['ip'] as String; - countryCode = json['country_code'] as String; - countryName = json['country_name'] as String; - regionCode = json['region_code'] as String; - regionName = json['region_name'] as String; - city = json['city'] as String; - zipCode = json['zip_code'] as String; - timeZone = json['time_zone'] as String; - latitude = json['latitude'] as double; - longitude = json['longitude'] as double; - metroCode = json['metro_code'] as int; + country = json['country'] as String; } Map toJson() { final Map data = Map(); data['ip'] = ip; - data['country_code'] = countryCode; - data['country_name'] = countryName; - data['region_code'] = regionCode; - data['region_name'] = regionName; - data['city'] = city; - data['zip_code'] = zipCode; - data['time_zone'] = timeZone; - data['latitude'] = latitude; - data['longitude'] = longitude; - data['metro_code'] = metroCode; + data['country'] = country; return data; } } diff --git a/lib/models/application/ip_model.dart b/lib/models/application/ip_model.dart deleted file mode 100644 index 2183638..0000000 --- a/lib/models/application/ip_model.dart +++ /dev/null @@ -1,16 +0,0 @@ -/// [Json2Dart](https://javiercbk.github.io/json_to_dart/) was used to generate the model class -class IPModel { - String origin; - - IPModel({this.origin}); - - IPModel.fromJson(Map json) { - origin = json['origin'] as String; - } - - Map toJson() { - final Map data = Map(); - data['origin'] = origin; - return data; - } -} diff --git a/lib/res/asset_animations.dart b/lib/res/asset_animations.dart index f9a4d62..d30eb3b 100644 --- a/lib/res/asset_animations.dart +++ b/lib/res/asset_animations.dart @@ -1,4 +1,4 @@ -//// Generated by spider on 2020-04-16 20:24:50.641193 +//// Generated by spider on 2020-04-20 10:43:00.182784 class AssetAnimations { static const String splash = 'assets/animations/splash.gif'; diff --git a/lib/res/asset_images.dart b/lib/res/asset_images.dart index 3338549..6645cbb 100644 --- a/lib/res/asset_images.dart +++ b/lib/res/asset_images.dart @@ -1,9 +1,11 @@ -//// Generated by spider on 2020-04-16 20:25:08.578492 +//// Generated by spider on 2020-04-20 11:45:57.777930 class AssetImages { static const String noInternet = 'assets/images/no-internet.png'; + static const String mythBusters = 'assets/images/myth-busters.png'; static const String virus = 'assets/images/virus.png'; static const String prevention = 'assets/images/prevention.png'; static const String genericError = 'assets/images/generic-error.png'; static const String latestNumbers = 'assets/images/latest-numbers.png'; + static const String symptoms = 'assets/images/symptoms.png'; } diff --git a/lib/routes.dart b/lib/routes.dart index e4b9fc9..a456815 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -1,7 +1,7 @@ +import 'package:flutter/material.dart'; import 'package:covid19/ui/prevention/prevention_screen.dart'; import 'package:covid19/ui/splash/splash_screen.dart'; import 'package:covid19/ui/symptomChecker/symptom_checker_screen.dart'; -import 'package:flutter/material.dart'; /// Defines routes for the application which can be used via the [routes] /// from outside the class diff --git a/lib/ui/home/home_navigator.dart b/lib/ui/home/home_navigator.dart index 3de99dc..56277be 100644 --- a/lib/ui/home/home_navigator.dart +++ b/lib/ui/home/home_navigator.dart @@ -1,3 +1,4 @@ +import 'package:covid19/constants/colors.dart'; import 'package:flutter/material.dart'; import 'package:covid19/ui/home/home_screen.dart'; import 'package:covid19/ui/statistics/statistics_screen.dart'; @@ -55,12 +56,14 @@ class HomeRouter { case HomeRoutes.home: return MaterialPageRoute(builder: (_) => HomeScreen()); case HomeRoutes.latestNumbers: - debugPrint('Entering here inside Latest Numbers'); return MaterialPageRoute(builder: (_) => StatisticsScreen()); case HomeRoutes.prevention: return MaterialPageRoute(builder: (_) => PreventionScreen()); case HomeRoutes.symptomChecker: - return MaterialPageRoute(builder: (_) => SymptomCheckerScreen()); + // return MaterialPageRoute(builder: (_) => SymptomCheckerScreen()); + return CustomPageRoute(SymptomCheckerScreen()); + // return FadePageRoute(builder: (_) => SymptomCheckerScreen()); + default: return MaterialPageRoute(builder: (_) => HomeScreen()); } @@ -71,10 +74,18 @@ class HomeRouter { /// [HomeScreen]. /// /// Add any new routes that have to be re-directed from the [HomeScreen] +/// [HomeNavigator] can handle the following [HomeRoutes] +/// +/// 1. [home] - To lead the use to the default [HomeScreen] +/// 2. [latestNumbers] - To lead the user to the [StatisticsScreen] +/// 3. [prevention] - To lead the user to the [PreventionScreen] +/// 4. [symptomChecker] - To lead the user to the [SymptomCheckerScreen] class HomeNavigator extends StatelessWidget { static final GlobalKey navigatorKey = GlobalKey(); + final HeroController _heroController = HeroController(); + // Convenience static method for extended functionality static void pop(BuildContext context) { HomeRouter.routesStack.removeLast(); @@ -95,9 +106,38 @@ class HomeNavigator extends StatelessWidget { }, child: Navigator( key: navigatorKey, + observers: [_heroController], initialRoute: HomeRoutes.home.name, onGenerateRoute: (settings) => HomeRouter.generateRoute(settings), ), ); } } + +class CustomPageRoute extends PageRoute { + CustomPageRoute(this.child); + @override + // TODO: implement barrierColor + Color get barrierColor => AppColors.primaryColor; + + @override + String get barrierLabel => null; + + final Widget child; + + @override + Widget buildPage(BuildContext context, Animation animation, + Animation secondaryAnimation) { + return child; + // return FadeTransition( + // opacity: animation, + // child: child, + // ); + } + + @override + bool get maintainState => true; + + @override + Duration get transitionDuration => const Duration(milliseconds: 500); +} diff --git a/lib/ui/home/home_screen.dart b/lib/ui/home/home_screen.dart index f189bd5..9071831 100644 --- a/lib/ui/home/home_screen.dart +++ b/lib/ui/home/home_screen.dart @@ -19,6 +19,16 @@ class HomeScreen extends StatelessWidget { final screenHeight = DeviceUtils.getScaledHeight(context, 1); return Material( child: Scaffold( + // [AppBar] with 0 size used to set the statusbar background color and + // statusbat text/icon color + appBar: PreferredSize( + preferredSize: const Size.fromHeight(0.0), + child: AppBar( + backgroundColor: AppColors.transparentColor, + brightness: Brightness.light, + elevation: 0.0, + ), + ), body: Container( padding: EdgeInsets.fromLTRB( Dimens.horizontalPadding, @@ -47,12 +57,10 @@ class HomeScreen extends StatelessWidget { Expanded( flex: 1, child: HomeCardWidget( - start: -screenWidth / 10, - end: -screenWidth / 10, - bottom: -screenHeight / 20, backgroundColor: AppColors.primaryColor, title: Strings.latestNumbersTitle, imagePath: AssetImages.latestNumbers, + backgroundImage: true, route: HomeRoutes.latestNumbers.name, ), ), @@ -61,8 +69,6 @@ class HomeScreen extends StatelessWidget { Expanded( flex: 1, child: HomeCardWidget( - end: -screenWidth / 5, - bottom: -screenHeight / 100, backgroundColor: AppColors.primaryColor, title: Strings.preventionTitle, imagePath: AssetImages.prevention, @@ -79,7 +85,40 @@ class HomeScreen extends StatelessWidget { GestureDetector( onTap: () => HomeNavigator.navigatorKey.currentState .pushNamed(HomeRoutes.symptomChecker.name), - child: SymptomCheckerCardWidget(), + child: Hero( + tag: 'symptomChecker', + child: SymptomCheckerCardWidget(), + ), + ), + + // Verical Spacing + SizedBoxHeightWidget(screenHeight / 50), + + // Symptoms and Myth Busters + Row( + children: [ + // Symptoms Card + Expanded( + flex: 1, + child: HomeCardWidget( + backgroundColor: AppColors.primaryColor, + title: Strings.symptomsTitle, + imagePath: AssetImages.symptoms, + route: HomeRoutes.latestNumbers.name, + ), + ), + + // Myth Busters Card + Expanded( + flex: 1, + child: HomeCardWidget( + backgroundColor: AppColors.primaryColor, + title: Strings.mythBusterTitle, + imagePath: AssetImages.mythBusters, + route: HomeRoutes.prevention.name, + ), + ), + ], ), ], ), diff --git a/lib/ui/home/widgets/home_card_widget.dart b/lib/ui/home/widgets/home_card_widget.dart index 129c0c0..5d8b7a8 100644 --- a/lib/ui/home/widgets/home_card_widget.dart +++ b/lib/ui/home/widgets/home_card_widget.dart @@ -5,22 +5,27 @@ import 'package:covid19/constants/text_styles.dart'; import 'package:covid19/ui/home/home_navigator.dart'; import 'package:covid19/utils/device/device_utils.dart'; +/// Displays the navigation Home Cards +/// +/// **Requires** +/// 1. [title] - For the title text +/// 2. [backgroundColor] - For the backgroundColor of the Card +/// 3. [imagePath] - For the Image to be displayed +/// 4. [backgroundImage] - Determines if the image is a background Image or not +/// 5. [route] - For the value to be used to navigate to the desired screen using [HomeNavigator] class HomeCardWidget extends StatelessWidget { final String title; final Color backgroundColor; - final double start, top, bottom, end; final String imagePath; + final bool backgroundImage; final String route; const HomeCardWidget({ Key key, this.title, this.backgroundColor, - this.start = 0, - this.top = 0, - this.bottom = 0, - this.end = 0, this.imagePath, + this.backgroundImage = false, this.route, }) : super(key: key); @override @@ -29,12 +34,25 @@ class HomeCardWidget extends StatelessWidget { final screenHeight = DeviceUtils.getScaledHeight(context, 1); return GestureDetector( onTap: () => HomeNavigator.navigatorKey.currentState.pushNamed(route), + // Rounded Card child: Container( + height: screenHeight / 8, margin: const EdgeInsets.symmetric( horizontal: Dimens.horizontalPadding / 2, ), - height: screenHeight / 8, decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage(imagePath), + // Setting the property to fill the complete card + // and centering it inside the card at the bottom + // if the backgroundImage property is set to true + // or else centering it to the bottom right of the card + // and scaling it + fit: backgroundImage ? BoxFit.contain : BoxFit.scaleDown, + alignment: backgroundImage + ? Alignment.bottomCenter + : Alignment.bottomRight, + ), boxShadow: const [ BoxShadow( offset: Offset(0, 0), @@ -47,36 +65,22 @@ class HomeCardWidget extends StatelessWidget { ), color: backgroundColor, ), - child: Stack( - children: [ - PositionedDirectional( - start: start, - top: top, - end: end, - bottom: bottom, - child: Image( - width: screenWidth / 10, - image: AssetImage( - imagePath, - ), + + // Text Label + child: Container( + padding: EdgeInsets.symmetric( + vertical: screenHeight / 55, + horizontal: screenWidth / 25, + ), + child: SizedBox( + width: screenWidth / 1.5, + child: Text( + title, + style: TextStyles.homeCardTitle.copyWith( + fontSize: screenWidth / 20, ), ), - Container( - padding: EdgeInsets.symmetric( - vertical: screenHeight / 55, - horizontal: screenWidth / 25, - ), - child: SizedBox( - width: screenWidth / 1.5, - child: Text( - title, - style: TextStyles.homeCardTitle.copyWith( - fontSize: screenWidth / 20, - ), - ), - ), - ) - ], + ), ), ), ); diff --git a/lib/ui/prevention/prevention_screen.dart b/lib/ui/prevention/prevention_screen.dart index 7f6d80d..22642b6 100644 --- a/lib/ui/prevention/prevention_screen.dart +++ b/lib/ui/prevention/prevention_screen.dart @@ -1,20 +1,49 @@ +import 'dart:convert'; +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/gestures.dart'; import 'package:cached_network_image/cached_network_image.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:covid19/constants/dimens.dart'; import 'package:covid19/constants/strings.dart'; import 'package:covid19/constants/colors.dart'; import 'package:covid19/constants/text_styles.dart'; import 'package:covid19/icons/covid19_icons.dart'; import 'package:covid19/data/network/constants/endpoints.dart'; +import 'package:covid19/models/statistics/statistics_response_model.dart'; import 'package:covid19/utils/custom_scroll_behaviour.dart'; -import 'package:covid19/utils/image_cache_manager.dart'; +import 'package:covid19/utils/cache_manager.dart'; import 'package:covid19/utils/device/device_utils.dart'; import 'package:covid19/widgets/custom_alert_dialog.dart'; -import 'package:url_launcher/url_launcher.dart'; /// Displays the information in regards to prevention of Coronavirus /// and reference to where the data is taken from -class PreventionScreen extends StatelessWidget { +class PreventionScreen extends StatefulWidget { + @override + _PreventionScreenState createState() => _PreventionScreenState(); +} + +class _PreventionScreenState extends State { + File preventionImage; + @override + void initState() { + super.initState(); + getStatisticsFile(); + } + + Future getStatisticsFile() async { + final cachedImage = + await CacheManager().getSingleFile(Endpoints.fetchHomeData); + + final contents = await cachedImage.readAsString(); + + final jsonResponse = jsonDecode(contents); + + final countryInformation = StatisticsResponseModel.fromJson(jsonResponse); + + debugPrint('${countryInformation.global.newConfirmed}'); + } + @override Widget build(BuildContext context) { final screenWidth = DeviceUtils.getScaledWidth(context, 1); @@ -25,21 +54,25 @@ class PreventionScreen extends StatelessWidget { extendBodyBehindAppBar: true, appBar: PreferredSize( preferredSize: Size.fromHeight( - screenHeight / 25, + screenHeight / 20, ), child: AppBar( // Leading set to empty container to remove the back button leading: Container(), + // Setting the background color to transparent so that the + // image can be full screen without any froeground colors backgroundColor: AppColors.transparentColor, elevation: 0, actions: [ // Adding padding to the action info button so it doesn't stick - // to the end of the screen + // to the side of the screen Padding( padding: EdgeInsets.only( - right: screenWidth / 25, + right: screenWidth / 50, ), child: GestureDetector( + // Displaying the dialog to reference the source of the + // image onTap: () => showDialog( context: context, builder: (BuildContext context) { @@ -47,12 +80,16 @@ class PreventionScreen extends StatelessWidget { title: RichText( softWrap: true, text: TextSpan(children: [ + // Dialog Title - Data Source TextSpan( text: '${Strings.dataSource}\n\n\n', style: TextStyles.hightlightText.copyWith( fontSize: screenWidth / 20, ), ), + + // Dialoog description referncing and linking the blog post + // and the Author TextSpan( style: TextStyles.statisticsSubHeadingTextStlye .copyWith( @@ -68,6 +105,8 @@ class PreventionScreen extends StatelessWidget { decoration: TextDecoration.underline, color: AppColors.accentBlueColor, ), + // Launcing the URL of the blog post + // throwing an error if the user doesn't have any browswer to open the link (Shouldn't ever happen) recognizer: TapGestureRecognizer() ..onTap = () async => await canLaunch( Endpoints.dataSourceReferenceURL) @@ -77,6 +116,8 @@ class PreventionScreen extends StatelessWidget { TextSpan( text: Strings.writtenBy, ), + // Launcing the URL of the Author's Website + // throwing an error if the user doesn't have any browswer to open the link (Shouldn't ever happen) TextSpan( text: Strings.author, style: TextStyle( @@ -93,8 +134,9 @@ class PreventionScreen extends StatelessWidget { ), ]), ), + + // Defining the Action item [Close] for the Dialog actions: [ - // Dialog Close Button GestureDetector( onTap: () => Navigator.of(context).pop(), child: Container( @@ -105,8 +147,8 @@ class PreventionScreen extends StatelessWidget { decoration: BoxDecoration( boxShadow: const [ BoxShadow( - offset: Offset(-2, 10), - blurRadius: 10, + offset: Offset(-2, 4), + blurRadius: 2, color: AppColors.boxShadowColor, ), ], @@ -128,15 +170,10 @@ class PreventionScreen extends StatelessWidget { ); }, ), + + // Adding the information Icon to the [AppBar] child: Container( decoration: BoxDecoration( - boxShadow: const [ - BoxShadow( - offset: Offset(-1, 1), - blurRadius: 2, - color: AppColors.boxShadowColor, - ), - ], borderRadius: BorderRadius.all( Radius.circular(screenWidth / 15), ), @@ -152,32 +189,24 @@ class PreventionScreen extends StatelessWidget { ], ), ), - // Settingt the background color to avoid the default - // white color of the SafeArea widget - backgroundColor: AppColors.preventionBackgroundColor, body: ScrollConfiguration( behavior: const CustomScrollBehaviour(), child: SingleChildScrollView( - // Adding SafeArea to avoid the image being fullScreen // Padding been added to keep the VISME logo visible at the bottom - child: SafeArea( - child: Container( - padding: EdgeInsets.only( - bottom: screenHeight / 20, - ), - color: AppColors.preventionBackgroundColor, - child: Stack( - children: [ - CachedNetworkImage( - imageUrl: Endpoints.baseUrlPreventionInfographic, - cacheManager: ImageCacheManager(), - ), - ], - ), + child: Container( + padding: EdgeInsets.only( + top: Dimens.verticalPadding / 0.2, + bottom: Dimens.verticalPadding / 0.15, + ), + color: AppColors.preventionBackgroundColor, + child: CachedNetworkImage( + imageUrl: Endpoints.baseUrlPreventionInfographic, + cacheManager: CacheManager(), ), ), ), ), + // Back button to lead back to the [HomeScreen] // Floating Action Button used so that the item remains fixed when the image is scolled floatingActionButton: Padding( padding: EdgeInsets.only( diff --git a/lib/ui/splash/splash_screen.dart b/lib/ui/splash/splash_screen.dart index d02a676..0df306d 100644 --- a/lib/ui/splash/splash_screen.dart +++ b/lib/ui/splash/splash_screen.dart @@ -1,4 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:covid19/data/network/constants/endpoints.dart'; +import 'package:covid19/utils/cache_manager.dart'; import 'package:covid19/constants/colors.dart'; import 'package:covid19/constants/strings.dart'; import 'package:covid19/constants/text_styles.dart'; @@ -16,29 +19,44 @@ class SplashScreen extends StatelessWidget { return Scaffold( backgroundColor: AppColors.primaryColor.withAlpha(200), - body: Column( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.max, + body: Stack( children: [ - // Actual SplashScreen - Center( - child: Image( - width: screenWidth / 0.75, - image: AssetImage( - AssetAnimations.splash, - ), + // Preload prevention Image to avoid the very short spanning white screen + // before the Image is loaded + Opacity( + opacity: 0, + child: CachedNetworkImage( + imageUrl: Endpoints.baseUrlPreventionInfographic, + cacheManager: CacheManager(), ), ), - // Vertical Spacing - SizedBoxHeightWidget(screenHeight / 25), + // Actual Splash Screen Data + Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + // Actual SplashScreen + Center( + child: Image( + width: screenWidth / 0.75, + image: AssetImage( + AssetAnimations.splash, + ), + ), + ), - Text( - Strings.appName, - style: TextStyles.hightlightText.copyWith( - fontSize: screenWidth / 15, - color: AppColors.whiteColor, - ), + // Vertical Spacing + SizedBoxHeightWidget(screenHeight / 25), + + Text( + Strings.appName, + style: TextStyles.hightlightText.copyWith( + fontSize: screenWidth / 15, + color: AppColors.whiteColor, + ), + ), + ], ), ], ), diff --git a/lib/ui/statistics/statistics_screen.dart b/lib/ui/statistics/statistics_screen.dart index 6d37ce8..a6d88b1 100644 --- a/lib/ui/statistics/statistics_screen.dart +++ b/lib/ui/statistics/statistics_screen.dart @@ -1,14 +1,13 @@ +import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:provider/provider.dart'; -import 'package:cached_network_image/cached_network_image.dart'; import 'package:covid19/constants/app_theme.dart'; import 'package:covid19/constants/colors.dart'; import 'package:covid19/constants/dimens.dart'; import 'package:covid19/constants/strings.dart'; import 'package:covid19/constants/text_styles.dart'; -import 'package:covid19/data/network/constants/endpoints.dart'; import 'package:covid19/models/statistics/countries_list_model.dart'; import 'package:covid19/res/asset_images.dart'; import 'package:covid19/icons/covid19_icons.dart'; @@ -21,10 +20,11 @@ import 'package:covid19/utils/bloc/application_bloc.dart'; import 'package:covid19/utils/bloc/application_state.dart'; import 'package:covid19/utils/custom_scroll_behaviour.dart'; import 'package:covid19/utils/device/device_utils.dart'; -import 'package:covid19/utils/image_cache_manager.dart'; +import 'package:covid19/utils/emoji_flags.dart'; import 'package:covid19/widgets/country_picker/country_picker_dialog.dart'; import 'package:covid19/widgets/progress_indicator_widget.dart'; import 'package:covid19/widgets/sized_box_height_widget.dart'; +import 'package:covid19/widgets/sized_box_width_widget.dart'; /// Displays the country Information and country statistics /// Handles the various states of the [HomeChangeNotifier] to perform @@ -35,10 +35,12 @@ class StatisticsScreen extends StatefulWidget { } class _StatisticsScreenState extends State { + bool isOffline = false; + List countriesList; - DateTime today; + DateTime today = DateTime.now(); List countrySearchItems = []; - String selectedCountry, selectedCountryISO2; + String selectedCountry = '', selectedCountryISO2 = ''; @override void initState() { @@ -60,9 +62,11 @@ class _StatisticsScreenState extends State { if (state is ApplicationInitialized) { setState( () { - selectedCountry = state.userCountryInformation.countryName; - selectedCountryISO2 = state.userCountryInformation.countryCode; + selectedCountryISO2 = state.userCountryInformation.country; countriesList = state.countriesList; + selectedCountry = countriesList + .firstWhere((item) => item.iso2 == selectedCountryISO2) + .name; // Adding all the items of the [countriesList] to [countrySearchItems] for (final item in countriesList) { countrySearchItems.add( @@ -93,6 +97,7 @@ class _StatisticsScreenState extends State { iso2: iso2, ); setState(() { + // Setting today's date value today = DateTime.now(); }); } @@ -101,14 +106,20 @@ class _StatisticsScreenState extends State { Widget _buildDialogItem(Countries country) { return Row( children: [ - CachedNetworkImage( - imageUrl: - '${Endpoints.baseUrlCountryFlags}${country.iso2}/flat/32.png', - cacheManager: ImageCacheManager(), + Text( + Emoji.byISOCode('flag_${country.iso2.toLowerCase()}').char, + style: const TextStyle( + fontSize: 30, + ), ), - const SizedBox(width: 8.0), + const SizedBoxWidthWidget(15), Flexible( - child: Text(country.name), + child: Text( + country.name, + style: TextStyles.statisticsHeadingTextStlye.copyWith( + fontSize: 16, + ), + ), ) ], ); @@ -150,6 +161,7 @@ class _StatisticsScreenState extends State { // timeDilation = 15.0; final screenWidth = DeviceUtils.getScaledWidth(context, 1); final screenHeight = DeviceUtils.getScaledHeight(context, 1); + debugPrint('Todays Date : $today'); return SafeArea( child: Scaffold( backgroundColor: AppColors.whiteColor, @@ -203,7 +215,6 @@ class _StatisticsScreenState extends State { // Back Icon GestureDetector( onTap: () => Navigator.of(context).pop(), - // padding: const EdgeInsets.all(0), child: Icon( Covid19Icons.keyboardArrowLeft, size: screenWidth / 12, @@ -377,8 +388,8 @@ class _StatisticsScreenState extends State { // Verical Spacing SizedBoxHeightWidget(screenHeight / 35), - Padding( - padding: const EdgeInsets.only( + const Padding( + padding: EdgeInsets.only( right: Dimens.horizontalPadding, ), child: Divider( @@ -417,10 +428,13 @@ class _StatisticsScreenState extends State { ), Flexible( flex: 1, - child: CachedNetworkImage( - imageUrl: - '${Endpoints.baseUrlCountryFlags}$selectedCountryISO2/flat/32.png', - cacheManager: ImageCacheManager(), + child: Text( + Emoji.byISOCode( + 'flag_${selectedCountryISO2.toLowerCase()}') + .char, + style: const TextStyle( + fontSize: 30, + ), ), ), Flexible( diff --git a/lib/ui/statistics/widgets/info_graph_widgert.dart b/lib/ui/statistics/widgets/info_graph_widgert.dart index 4ecd326..c623b6b 100644 --- a/lib/ui/statistics/widgets/info_graph_widgert.dart +++ b/lib/ui/statistics/widgets/info_graph_widgert.dart @@ -1,4 +1,7 @@ import 'dart:convert'; +import 'dart:math' as math; +import 'package:flutter/material.dart'; +import 'package:fl_chart/fl_chart.dart'; import 'package:covid19/constants/colors.dart'; import 'package:covid19/constants/dimens.dart'; import 'package:covid19/constants/strings.dart'; @@ -6,8 +9,6 @@ import 'package:covid19/constants/text_styles.dart'; import 'package:covid19/models/statistics/country_statistics_day_model.dart'; import 'package:covid19/utils/device/device_utils.dart'; import 'package:covid19/widgets/sized_box_width_widget.dart'; -import 'package:fl_chart/fl_chart.dart'; -import 'package:flutter/material.dart'; class InfoGraphWidget extends StatefulWidget { final List countryStatisticsList; @@ -85,7 +86,10 @@ class _InfoGraphWidgetState extends State { child: (widget.countryStatisticsList.isNotEmpty) ? BarChart( mainBarData( - countryStatisticsList: widget.countryStatisticsList, + countryStatisticsList: widget.countryStatisticsList.sublist( + (widget.countryStatisticsList.length) - 7, + (widget.countryStatisticsList.length) - 0, + ), screenWidth: screenWidth, screenHeight: screenHeight, ), @@ -98,14 +102,24 @@ class _InfoGraphWidgetState extends State { ), ), Padding( - padding: const EdgeInsets.symmetric( - horizontal: Dimens.horizontalPadding, + padding: const EdgeInsets.only( + left: Dimens.horizontalPadding, + top: Dimens.verticalPadding / 0.45, + right: Dimens.horizontalPadding, ), // Checking if the list has any elemnts i.e the API provided // data for the queried country child: (widget.countryStatisticsList.isNotEmpty) ? LineChart( - mainData(widget.countryStatisticsList), + // Using the latest 15 days days for the Weekly Information + areaChartData( + countryStatisticsList: widget.countryStatisticsList.sublist( + (widget.countryStatisticsList.length) - 15, + (widget.countryStatisticsList.length) - 0, + ), + screenWidth: screenWidth, + screenHeight: screenHeight, + ), ) : Padding( padding: const EdgeInsets.only( @@ -252,33 +266,45 @@ BarChartData mainBarData({ // Empty [BarChartGroupData] List. Intialised with [] to avoid error when trying to // add items using the .add method final List barChartData = []; - // Using the latest 7 days days for the Daily Information - countryStatisticsList - .sublist( - countryStatisticsListLength - 7, - countryStatisticsListLength - 0, - ) - .asMap() - .forEach( + + // Calculating the max value in the array to be passed onto the normalization function + // [normalizedValue] + final dailyStatisticsMax = + countryStatisticsList[countryStatisticsListLength - 1].cases.toDouble(); + + // Calculating the min value in the array to be passed onto the normalization function + // [normalizedValue] + final dailyStatisticsMin = + countryStatisticsList[countryStatisticsListLength - 7].cases.toDouble(); + + // Creating a buffer value to display the the top-most title on the left side bar + // for the [BarChartData] + final leftTitleTopBuffer = + countryStatisticsList[countryStatisticsListLength - 1].cases.toDouble() + + countryStatisticsList[countryStatisticsListLength - 2].cases / 7.5; + + // Looping through the list to add each individual item with the normalized values + // to the [BarChartGroupData] which is used to plot the points on the graph + countryStatisticsList.asMap().forEach( (index, item) { barChartData.add( BarChartGroupData( x: index, barRods: [ BarChartRodData( - y: item.cases.toDouble(), + y: normalizedValue( + oldValue: item.cases, + oldMin: dailyStatisticsMin, + oldMax: dailyStatisticsMax, + newMin: 4, + ), color: AppColors.blueColor, width: screenWidth / 20, backDrawRodData: BackgroundBarChartRodData( show: true, // Setting the max value of the Bar Chart (Y Axis) with a buffer // so that the graph is not painted outside the container - y: countryStatisticsList[countryStatisticsListLength - 1] - .cases - .toDouble() + - countryStatisticsList[countryStatisticsListLength - 2] - .cases / - 7.5, + y: 11, // Background colour of the Bar Chart - Lighter Version than the filled lines color: AppColors.accentBlueColor, ), @@ -288,10 +314,16 @@ BarChartData mainBarData({ ); }, ); + return BarChartData( - groupsSpace: screenWidth / 12, + groupsSpace: screenWidth / 14, alignment: BarChartAlignment.center, + + // Define the properties for the Bar Chart when touched over a plot barTouchData: BarTouchData( + // Defining the properties of the ToolTip which occurs when touched/hovered over a plotted item + // [fitInsideHorizontally] and [fitInsideVertically] are set to true to fit the tooltip in the + // view of the Widget touchTooltipData: BarTouchTooltipData( fitInsideHorizontally: true, fitInsideVertically: true, @@ -345,24 +377,47 @@ BarChartData mainBarData({ break; } return BarTooltipItem( - '$weekDay \n ${(rod.y - 1).toInt()}', - const TextStyle( - color: AppColors.whiteColor, + '$weekDay \n ${countryStatisticsList[group.x].cases}', + TextStyles.statisticsToopTipTextStyle.copyWith( + fontSize: screenWidth / 30, ), ); }, ), ), + + // Side and Bottom Tiles titlesData: FlTitlesData( show: true, + // Side Titles + leftTitles: SideTitles( + showTitles: true, + textStyle: TextStyles.statisticsLabelTextStyle.copyWith( + fontSize: screenWidth / 33, + ), + margin: screenWidth / 22, + reservedSize: screenWidth / 15, + getTitles: (value) { + if (value == 2) { + return '${getUnitValue(countryStatisticsList[2].cases.toDouble())}'; + } else if (value == 6) { + return '${getUnitValue(countryStatisticsList[3].cases.toDouble())}'; + } else if (value == 10) { + return '${getUnitValue(leftTitleTopBuffer)}'; + } else { + return ''; + } + }, + ), + + // Bottom Titles bottomTitles: SideTitles( showTitles: true, - textStyle: TextStyle( - color: AppColors.blackColor, - fontWeight: FontWeight.bold, - fontSize: screenWidth / 35, + textStyle: TextStyles.statisticsLabelTextStyle.copyWith( + fontSize: screenWidth / 30, ), - margin: screenWidth / 50, + reservedSize: screenWidth / 20, + margin: screenHeight / 175, getTitles: (double value) { // Iterating through the 7 items with the index of the [countryStatisticsListLength] // to obtain the Month @@ -407,10 +462,9 @@ BarChartData mainBarData({ } }, ), - leftTitles: const SideTitles( - showTitles: false, - ), ), + + // Hiding the Box plotted around the Bar Chart borderData: FlBorderData( show: false, ), @@ -419,7 +473,11 @@ BarChartData mainBarData({ } // Smooth Area Graph Visualisation for Weekly Data -LineChartData mainData(List countryStatisticsList) { +LineChartData areaChartData({ + @required List countryStatisticsList, + @required double screenWidth, + @required double screenHeight, +}) { final int countryStatisticsListLength = countryStatisticsList.length; final List gradientColors = [ AppColors.blueColor, @@ -429,20 +487,36 @@ LineChartData mainData(List countryStatisticsList) { // add items using the .add method final List flSpotData = []; - // Using the latest 15 days days for the Weekly Information - countryStatisticsList - .sublist( - countryStatisticsListLength - 15, - countryStatisticsListLength - 0, - ) - .asMap() - .forEach( + // Calculating the max value in the array to be passed onto the normalization function + // [normalizedValue] + final weeklyStatisticsMax = + countryStatisticsList[countryStatisticsListLength - 1].cases.toDouble(); + + // Calculating the min value in the array to be passed onto the normalization function + // [normalizedValue] + final weeklyStatisticsMin = + countryStatisticsList[countryStatisticsListLength - 15].cases.toDouble(); + + // Creating a buffer value to display the the top-most title on the left side bar + // for the [LineChartData] + final leftTitleTopBuffer = + countryStatisticsList[countryStatisticsListLength - 1].cases.toDouble() + + countryStatisticsList[countryStatisticsListLength - 2].cases / 5; + + // Looping through the list to add each individual item with the normalized values + // to [flSpotData] which is used to plot the points on the graph + countryStatisticsList.asMap().forEach( (index, item) { // Adding the indexes and the value to [flSpotData] flSpotData.add( FlSpot( index.toDouble(), - item.cases.toDouble(), + normalizedValue( + oldValue: item.cases, + oldMin: weeklyStatisticsMin, + oldMax: weeklyStatisticsMax, + newMin: 0, + ), ), ); }, @@ -451,24 +525,71 @@ LineChartData mainData(List countryStatisticsList) { gridData: const FlGridData( show: false, ), - titlesData: const FlTitlesData( - show: false, + titlesData: FlTitlesData( + // Side Titles + leftTitles: SideTitles( + showTitles: true, + textStyle: TextStyles.statisticsLabelTextStyle.copyWith( + fontSize: screenWidth / 33, + ), + margin: screenWidth / 22, + reservedSize: screenWidth / 15, + + // Setting the cases values using the [getUnitValue] method across the graph + getTitles: (value) { + switch (value.toInt()) { + case 1: + return '${getUnitValue(countryStatisticsList[1].cases.toDouble())}'; + break; + case 7: + return '${getUnitValue(countryStatisticsList[9].cases.toDouble())}'; + break; + case 13: + return '${getUnitValue(leftTitleTopBuffer)}'; + break; + } + return ''; + }, + ), + + // Bottom Titles + bottomTitles: SideTitles( + showTitles: true, + reservedSize: screenWidth / 20, + margin: screenHeight / 175, + textStyle: TextStyles.statisticsLabelTextStyle.copyWith( + fontSize: screenWidth / 30, + ), + + // Setting the date values using the [dailyMonthData] method across the graph + getTitles: (value) { + switch (value.toInt()) { + case 2: + return '${dailyMonthData(date: countryStatisticsList[2].date)}'; + case 7: + return '${dailyMonthData(date: countryStatisticsList[7].date)}'; + case 12: + return '${dailyMonthData(date: countryStatisticsList[12].date)}'; + } + return ''; + }, + ), ), borderData: FlBorderData( show: false, ), + + // Setting the range of values on x-axis between 0 and 14 to accompany the 15 days minX: 0, maxX: 14, - // Creating a buffer for the minY and maxY so that the graph is not painted - // outside the container - minY: countryStatisticsList[countryStatisticsListLength - 15] - .cases - .toDouble() - - countryStatisticsList[countryStatisticsListLength - 8].cases.toDouble(), - maxY: countryStatisticsList[countryStatisticsListLength - 1] - .cases - .toDouble() + - countryStatisticsList[countryStatisticsListLength - 2].cases / 2.5, + + // Setting the range of values on y-axis between -5 to 13 to create a buffer between + // the minimum and maximum value. + // The actual number of entries are 10 which are plotted between [0...10] + minY: -5, + maxY: 13, + + // Define the properties for the graph when touched over a point lineTouchData: LineTouchData( getTouchedSpotIndicator: (LineChartBarData barData, List spotIndexes) { @@ -476,37 +597,44 @@ LineChartData mainData(List countryStatisticsList) { (spotIndex) { // Adding the dot and the line (on click of the dot) with relevant styling return TouchedSpotIndicatorData( - const FlLine( - color: AppColors.accentBlueColor, + FlLine( + color: AppColors.accentBlueColor.withOpacity(0.5), ), - const FlDotData( - dotSize: 8, + FlDotData( + dotSize: screenWidth / 50, dotColor: AppColors.blueColor, ), ); }, ).toList(); }, + + // Defining the properties of the ToolTip which occurs when touched/hovered over a plotted item + // [fitInsideHorizontally] and [fitInsideVertically] are set to true to fit the tooltip in the + // view of the Widget touchTooltipData: LineTouchTooltipData( fitInsideHorizontally: true, fitInsideVertically: true, tooltipBgColor: Colors.blueAccent, - tooltipRoundedRadius: 5, + tooltipRoundedRadius: screenWidth / 50, getTooltipItems: (List touchedBarSpots) { // Iterating through [touchBarSpots] to create the [LineToolTipItem] to display // date, case count and `Confirmed` static String return touchedBarSpots.map((barSpot) { final flSpot = barSpot; return LineTooltipItem( - '${dailyMonthData(date: countryStatisticsList[countryStatisticsListLength - 15 + flSpot.x.toInt()].date)}\n${flSpot.y.toInt()}\nConfirmed', - const TextStyle( - color: AppColors.whiteColor, + '${dailyMonthData(date: countryStatisticsList[flSpot.x.toInt()].date)}\n${countryStatisticsList[flSpot.x.toInt()].cases}\nConfirmed', + TextStyles.statisticsToopTipTextStyle.copyWith( + fontSize: screenWidth / 30, ), ); }).toList(); }, ), ), + + // Setting the properties for the Line Graph + // Setting the [belowBarData] property to display a gradient below the plotted Line Graph lineBarsData: [ LineChartBarData( spots: flSpotData, @@ -514,12 +642,12 @@ LineChartData mainData(List countryStatisticsList) { colors: gradientColors, gradientFrom: const Offset(0, 0), gradientTo: const Offset(-1, -1), - barWidth: 3, + barWidth: screenWidth / 150, isStrokeCapRound: true, - dotData: const FlDotData( + dotData: FlDotData( show: true, dotColor: AppColors.blueColor, - dotSize: 6.0, + dotSize: screenWidth / 75, ), belowBarData: BarAreaData( show: true, @@ -537,7 +665,7 @@ LineChartData mainData(List countryStatisticsList) { ); } -// Method to return the Month for the Daily Results +/// Method to return the Month from a given String of date of the `Zulu time` (UTC) format String dailyMonthData({String date}) { const monthData = '{ "1" : "Jan", "2" : "Feb", "3" : "Mar", "4" : "Apr", "5" : "May", "6" : "June", "7" : "Jul", "8" : "Aug", "9" : "Sep", "10" : "Oct", "11" : "Nov", "12" : "Dec" }'; @@ -547,7 +675,7 @@ String dailyMonthData({String date}) { return '${parsedDateTime.toLocal().day.toString()} ${jsonDecode(monthData)['${parsedDateTime.toLocal().month}']}'; } -// Method to return the Weekday for the Daily Results +/// Method to return the Weekday from a given String of date of the `Zulu time` (UTC) format String dailyWeekDay({String date}) { const dayData = '{ "1" : "Monday", "2" : "Tuesday", "3" : "Wednesday", "4" : "Thursday", "5" : "Friday", "6" : "Saturday", "7" : "Sunday" }'; @@ -556,3 +684,25 @@ String dailyWeekDay({String date}) { return jsonDecode(dayData)['${parsedDateTime.toLocal().weekday}']; } + +/// Used to normalize the value of number of cases to a smaller quantity +double normalizedValue( + {int oldValue, double oldMax, double oldMin, int newMin}) { + const int newMax = 10; + final double newValue = + (((oldValue - oldMin) * (newMax - newMin)) / (oldMax - oldMin)) + newMin; + return double.parse(newValue.toStringAsExponential(3)); +} + +/// Returns the value in K, M, G, T, P and E for a given number +/// (Returns the Human Readable or humanized number) +String getUnitValue(double count) { + // https://stackoverflow.com/questions/9769554/how-to-convert-number-into-k-thousands-m-million-and-b-billion-suffix-in-jsp + if (count < 1000) return "${count.toStringAsFixed(0)}"; + + // ~/ is effective division to product an integer result + final int exp = math.log(count) ~/ math.log(1000); + final String value = 'KMGTPE'[exp - 1]; + + return '${(count / math.pow(1000, exp)).toStringAsFixed(0)} $value'; +} diff --git a/lib/ui/statistics/widgets/info_widget.dart b/lib/ui/statistics/widgets/info_widget.dart index 9116b90..316f4e9 100644 --- a/lib/ui/statistics/widgets/info_widget.dart +++ b/lib/ui/statistics/widgets/info_widget.dart @@ -11,8 +11,9 @@ import 'package:covid19/widgets/sized_box_height_widget.dart'; /// **Requires** /// 1. [infoColor] - For the iconColor and the title text /// 2. [infoIcon] - For the icon to displayed based on the label -/// 3. [infoValue] - For the value of the case count for the provided label -/// 4. [infoLabel] - For the value of the label +/// 3. [infoValueNew] - For the value of new case count for the provided label +/// 4. [infoValue] - For the value of the case count for the provided label +/// 5. [infoLabel] - For the value of the label class InfoCard extends StatelessWidget { final Color infoColor; final IconData infoIcon; @@ -67,7 +68,7 @@ class InfoCard extends StatelessWidget { borderRadius: const BorderRadius.all( Radius.circular(5), ), - color: infoColor.withOpacity(0.4), + color: infoColor.withOpacity(0.3), ), child: Text( '+ $infoValueNew', diff --git a/lib/ui/statistics/widgets/statistics_loading_widget.dart b/lib/ui/statistics/widgets/statistics_loading_widget.dart index 0d3e596..7595c07 100644 --- a/lib/ui/statistics/widgets/statistics_loading_widget.dart +++ b/lib/ui/statistics/widgets/statistics_loading_widget.dart @@ -1,15 +1,13 @@ import 'package:flutter/material.dart'; -import 'package:cached_network_image/cached_network_image.dart'; import 'package:covid19/constants/colors.dart'; import 'package:covid19/constants/dimens.dart'; import 'package:covid19/constants/strings.dart'; import 'package:covid19/constants/text_styles.dart'; -import 'package:covid19/data/network/constants/endpoints.dart'; import 'package:covid19/icons/covid19_icons.dart'; import 'package:covid19/ui/statistics/statistics_screen.dart'; import 'package:covid19/utils/custom_scroll_behaviour.dart'; import 'package:covid19/utils/device/device_utils.dart'; -import 'package:covid19/utils/image_cache_manager.dart'; +import 'package:covid19/utils/emoji_flags.dart'; import 'package:covid19/widgets/custom_shimmer.dart'; import 'package:covid19/widgets/sized_box_height_widget.dart'; import 'package:covid19/widgets/sized_box_width_widget.dart'; @@ -39,7 +37,7 @@ class HomeLoadingWidget extends StatelessWidget { behavior: const CustomScrollBehaviour(), child: SingleChildScrollView( child: Padding( - padding: EdgeInsets.fromLTRB( + padding: const EdgeInsets.fromLTRB( Dimens.horizontalPadding, Dimens.verticalPadding / 0.75, 0, @@ -209,8 +207,8 @@ class HomeLoadingWidget extends StatelessWidget { // Verical Spacing SizedBoxHeightWidget(screenHeight / 35), - Padding( - padding: const EdgeInsets.only( + const Padding( + padding: EdgeInsets.only( right: Dimens.horizontalPadding, ), child: Divider( @@ -244,10 +242,13 @@ class HomeLoadingWidget extends StatelessWidget { ), Flexible( flex: 1, - child: CachedNetworkImage( - imageUrl: - '${Endpoints.baseUrlCountryFlags}$selectedCountryISO2/flat/32.png', - cacheManager: ImageCacheManager(), + child: Text( + Emoji.byISOCode( + 'flag_${selectedCountryISO2.toLowerCase()}') + .char, + style: const TextStyle( + fontSize: 30, + ), ), ), Flexible( diff --git a/lib/ui/symptomChecker/symptom_checker_screen.dart b/lib/ui/symptomChecker/symptom_checker_screen.dart index b179387..506b51e 100644 --- a/lib/ui/symptomChecker/symptom_checker_screen.dart +++ b/lib/ui/symptomChecker/symptom_checker_screen.dart @@ -1,4 +1,3 @@ -import 'package:flutter/scheduler.dart'; import 'package:flutter/material.dart'; import 'package:covid19/constants/colors.dart'; import 'package:covid19/constants/dimens.dart'; @@ -24,95 +23,127 @@ class SymptomCheckerScreen extends StatefulWidget { } class _SymptomCheckerScreenState extends State { + @override + void initState() { + super.initState(); + + // Uncomment if you need a call back after the Hero Animation is over + // and a task needs to be performed + // WidgetsBinding.instance.addPostFrameCallback((_) { + //Future.delayed(const Duration(seconds: 5), () => + + //); + // }); + } + @override Widget build(BuildContext context) { final screenWidth = DeviceUtils.getScaledWidth(context, 1); final screenHeight = DeviceUtils.getScaledHeight(context, 1); - return Container( - color: AppColors.primaryColor, - child: SafeArea( - child: Scaffold( - backgroundColor: AppColors.primaryColor, - body: ScrollConfiguration( - behavior: const CustomScrollBehaviour(), - child: SingleChildScrollView( - child: Padding( - padding: EdgeInsets.fromLTRB( - Dimens.horizontalPadding, - Dimens.verticalPadding / 0.75, - Dimens.horizontalPadding, - 0, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - // Back Icon - IconButton( - // padding: const EdgeInsets.all(0), - icon: Icon( - Covid19Icons.keyboardArrowLeft, + return Hero( + tag: 'symptomChecker', + flightShuttleBuilder: ( + BuildContext flightContext, + Animation animation, + HeroFlightDirection flightDirection, + BuildContext fromHeroContext, + BuildContext toHeroContext, + ) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(screenWidth / 10), + ), + color: AppColors.primaryColor, + ), + ); + }, + child: Container( + color: AppColors.primaryColor, + child: SafeArea( + child: Scaffold( + backgroundColor: AppColors.primaryColor, + body: ScrollConfiguration( + behavior: const CustomScrollBehaviour(), + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.fromLTRB( + Dimens.horizontalPadding, + Dimens.verticalPadding / 0.75, + Dimens.horizontalPadding, + 0, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + // Back Icon + IconButton( + // padding: const EdgeInsets.all(0), + icon: Icon( + Covid19Icons.keyboardArrowLeft, + ), + iconSize: screenWidth / 12, + color: AppColors.whiteColor, + onPressed: () => Navigator.of(context).pop(), ), - iconSize: screenWidth / 12, - color: AppColors.whiteColor, - onPressed: () => Navigator.of(context).pop(), - ), - // Verical Spacing - SizedBoxHeightWidget(screenHeight / 50), + // Verical Spacing + SizedBoxHeightWidget(screenHeight / 50), - // Wrapping other items inside a container to add extra padding - // to meet the default padding of Material Icons - Container( - padding: const EdgeInsets.only( - left: Dimens.horizontalPadding / 0.75, - right: Dimens.horizontalPadding / 0.75, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Symtpom Checker Heading - Text( - Strings.symptomCheckerTiitle, - style: TextStyles.symptonCheckerHeadingTextStyle - .copyWith( - fontSize: screenWidth / 25, + // Wrapping other items inside a container to add extra padding + // to meet the default padding of Material Icons + Container( + padding: const EdgeInsets.only( + left: Dimens.horizontalPadding / 0.75, + right: Dimens.horizontalPadding / 0.75, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Symtpom Checker Heading + Text( + Strings.symptomCheckerTiitle, + style: TextStyles.symptonCheckerHeadingTextStyle + .copyWith( + fontSize: screenWidth / 25, + ), ), - ), - // Verical Spacing - SizedBoxHeightWidget(screenHeight / 100), + // Verical Spacing + SizedBoxHeightWidget(screenHeight / 100), - // Symptom Checker Disclaimer - Text( - Strings.disclaimerText, - style: TextStyles.symptonCheckerHeadingTextStyle - .copyWith( - fontSize: screenWidth / 30, + // Symptom Checker Disclaimer + Text( + Strings.disclaimerText, + style: TextStyles.symptonCheckerHeadingTextStyle + .copyWith( + fontSize: screenWidth / 30, + ), ), - ), - // Verical Spacing - SizedBoxHeightWidget(screenHeight / 35), - ], + // Verical Spacing + SizedBoxHeightWidget(screenHeight / 35), + ], + ), ), - ), - // Stacked Cards of - Container( - height: screenHeight / 1.8, - child: const SymptomCheckerCards(), - ), + // Stacked Cards of + Container( + height: screenHeight / 1.8, + child: const SymptomCheckerCards(), + ), - // Verical Spacing - SizedBoxHeightWidget(screenHeight / 35), + // Verical Spacing + SizedBoxHeightWidget(screenHeight / 35), - // Question Progress and Count - QuestionProgressCount( - screenWidth: screenWidth, - screenHeight: screenHeight, - ), - ], + // Question Progress and Count + QuestionProgressCount( + screenWidth: screenWidth, + screenHeight: screenHeight, + ), + ], + ), ), ), ), @@ -324,7 +355,6 @@ class _SymptomCheckerCardsState extends State @override Widget build(BuildContext context) { - timeDilation = 0.4; const double initialBottom = 15.0; final dataLength = data.length; double backCardPosition = initialBottom + (dataLength - 1) * 10 + 10; diff --git a/lib/ui/symptomChecker/widgets/active_card_widget.dart b/lib/ui/symptomChecker/widgets/active_card_widget.dart index 8a80b3b..9c671ef 100644 --- a/lib/ui/symptomChecker/widgets/active_card_widget.dart +++ b/lib/ui/symptomChecker/widgets/active_card_widget.dart @@ -66,7 +66,7 @@ class ActiveCardWidget extends StatelessWidget { ), child: Text( "I'M IN $index", - style: TextStyle(color: Colors.white), + style: const TextStyle(color: Colors.white), ), ), ), diff --git a/lib/ui/symptomChecker/widgets/inactive_card_widget.dart b/lib/ui/symptomChecker/widgets/inactive_card_widget.dart index ef0144d..c1636b0 100644 --- a/lib/ui/symptomChecker/widgets/inactive_card_widget.dart +++ b/lib/ui/symptomChecker/widgets/inactive_card_widget.dart @@ -47,7 +47,7 @@ class InActiveCardWidget extends StatelessWidget { ), child: Text( "I'M IN $index", - style: TextStyle(color: Colors.white), + style: const TextStyle(color: Colors.white), ), ), ), diff --git a/lib/utils/animations/controlled_animation.dart b/lib/utils/animations/controlled_animation.dart new file mode 100644 index 0000000..cbd9063 --- /dev/null +++ b/lib/utils/animations/controlled_animation.dart @@ -0,0 +1,139 @@ +import 'package:flutter/widgets.dart'; + +/// Widget to create custom, managed, tween-based animations in a very simple way. +/// +/// --- +/// +/// An internal [AnimationController] will do everything you tell him by +/// dynamically assigning the one [Playback] to [playback] property. +/// By default the animation will start playing forward and stops at the end. +/// +/// A minimum set of properties are [duration] (time span of the animation), +/// [tween] (values to interpolate among the animation) and a [builder] function +/// (defines the animated scene). +/// +/// Instead of using [builder] as building function you can use for performance +/// critical scenarios [builderWithChild] along with a prebuild [child]. +/// +/// --- +/// +/// The following properties are optional: +/// +/// - You can apply a [delay] that forces the animation to pause a +/// specified time before the animation will perform the defined [playback] +/// instruction. +/// +/// - You can specify a [curve] that modifies the [tween] by applying a +/// non-linear animation function. You can find curves in [Curves], for +/// example [Curves.easeOut] or [Curves.easeIn]. +/// +/// - You can track the animation by setting an [AnimationStatusListener] to +/// the property [animationControllerStatusListener]. The internal [AnimationController] then +/// will route out any events that occur. [ControlledAnimation] doesn't filter +/// or modifies these events. These events are currently only reliable for the +/// [playback]-types [Playback.PLAY_FORWARD] and [Playback.PLAY_REVERSE]. +/// +/// - You can set the start position of animation by specifying [startPosition] +/// with a value between *0.0* and *1.0*. The [startPosition] is only +/// evaluated once during the initialization of the widget. +/// +class ControlledAnimation extends StatefulWidget { + final Animatable tween; + final Curve curve; + final Duration duration; + final Duration delay; + final Widget Function(BuildContext buildContext, T animatedValue) builder; + final Widget Function(BuildContext, Widget child, T animatedValue) + builderWithChild; + final Widget child; + final AnimationStatusListener animationControllerStatusListener; + final double startPosition; + + const ControlledAnimation({ + @required this.tween, + this.curve = Curves.linear, + @required this.duration, + this.delay, + this.builder, + this.builderWithChild, + this.child, + this.animationControllerStatusListener, + this.startPosition = 0.0, + Key key, + }) : assert(duration != null, + "Please set property duration. Example: Duration(milliseconds: 500)"), + assert(tween != null, + "Please set property tween. Example: Tween(from: 0.0, to: 100.0)"), + assert( + (builderWithChild != null && child != null && builder == null) || + (builder != null && builderWithChild == null && child == null), + "Either use just builder and keep buildWithChild and child null. " + "Or keep builder null and set a builderWithChild and a child."), + assert( + startPosition >= 0 && startPosition <= 1, + "The property startPosition " + "must have a value between 0.0 and 1.0."), + super(key: key); + + @override + _ControlledAnimationState createState() => _ControlledAnimationState(); +} + +class _ControlledAnimationState extends State> + with SingleTickerProviderStateMixin { + AnimationController _controller; + Animation _animation; + + @override + void initState() { + _controller = AnimationController(vsync: this, duration: widget.duration) + ..addListener(() { + setState(() {}); + }) + ..value = widget.startPosition; + + _animation = widget.tween + .chain(CurveTween(curve: widget.curve)) + .animate(_controller); + + if (widget.animationControllerStatusListener != null) { + _controller.addStatusListener(widget.animationControllerStatusListener); + } + + initialize(); + super.initState(); + } + + Future initialize() async { + if (widget.delay != null) { + await Future.delayed(widget.delay); + } + } + + @override + void didUpdateWidget(ControlledAnimation oldWidget) { + _controller.duration = widget.duration; + + _animation = widget.tween + .chain(CurveTween(curve: widget.curve)) + .animate(_controller); + + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext context) { + if (widget.builder != null) { + return widget.builder(context, _animation.value); + } else if (widget.builderWithChild != null && widget.child != null) { + return widget.builderWithChild(context, widget.child, _animation.value); + } + return Container(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } +} diff --git a/lib/utils/animations/multi_track_tween.dart b/lib/utils/animations/multi_track_tween.dart new file mode 100644 index 0000000..1002b5a --- /dev/null +++ b/lib/utils/animations/multi_track_tween.dart @@ -0,0 +1,156 @@ +import 'dart:math'; + +import 'package:flutter/widgets.dart'; + +/// Animatable that tweens multiple parallel properties (called [Track]s). +/// --- +/// The constructor of [MultiTrackTween] expects a list of [Track] objects. +/// You can fetch the specified total duration via [duration] getter. +/// --- +/// Example: +/// +/// ```dart +/// final tween = MultiTrackTween([ +/// Track("color") +/// .add(Duration(seconds: 1), ColorTween(begin: Colors.red, end: Colors.blue)) +/// .add(Duration(seconds: 1), ColorTween(begin: Colors.blue, end: Colors.yellow)), +/// Track("width") +/// .add(Duration(milliseconds: 500), ConstantTween(200.0)) +/// .add(Duration(milliseconds: 1500), Tween(begin: 200.0, end: 400.0), +/// curve: Curves.easeIn) +/// ]); +/// +/// return ControlledAnimation( +/// duration: tween.duration, +/// tween: tween, +/// builder: (context, values) { +/// ... +/// } +/// ); +/// ``` +class MultiTrackTween extends Animatable> { + final _tracksToTween = Map(); + var _maxDuration = 0; + + MultiTrackTween(List tracks) + : assert(tracks != null && tracks.isNotEmpty, + "Add a List to configure the tween."), + assert(tracks.where((track) => track.items.isEmpty).isEmpty, + "Each Track needs at least one added Tween by using the add()-method.") { + _computeMaxDuration(tracks); + _computeTrackTweens(tracks); + } + + void _computeMaxDuration(List tracks) { + for (final track in tracks) { + final trackDuration = track.items + .map((item) => item.duration.inMilliseconds) + .reduce((sum, item) => sum + item); + _maxDuration = max(_maxDuration, trackDuration); + } + } + + void _computeTrackTweens(List tracks) { + for (final track in tracks) { + final trackDuration = track.items + .map((item) => item.duration.inMilliseconds) + .reduce((sum, item) => sum + item); + + final sequenceItems = track.items + .map((item) => TweenSequenceItem( + tween: item.tween, + weight: item.duration.inMilliseconds / _maxDuration)) + .toList(); + + if (trackDuration < _maxDuration) { + sequenceItems.add(TweenSequenceItem( + tween: ConstantTween(null), + weight: (_maxDuration - trackDuration) / _maxDuration)); + } + + final sequence = TweenSequence(sequenceItems); + + _tracksToTween[track.name] = + _TweenData(tween: sequence, maxTime: trackDuration / _maxDuration); + } + } + + /// Returns the highest duration specified by [Track]s. + /// --- + /// Use it to pass it into an [ControlledAnimation]. + /// + /// You can also scale it by multiplying a double value. + /// + /// Example: + /// ```dart + /// final tween = MultiTrackTween(listOfTracks); + /// + /// return ControlledAnimation( + /// duration: tween.duration * 1.25, // stretch animation by 25% + /// tween: tween, + /// builder: (context, values) { + /// ... + /// } + /// ); + /// ``` + Duration get duration { + return Duration(milliseconds: _maxDuration); + } + + /// Computes the map of specific values for the animation. + /// You don't need to call it yourself. It's been implicitly used by + /// [Tween]-consuming classes. + /// + /// See [Animatable.transform]. + @override + Map transform(double t) { + final Map result = Map(); + _tracksToTween.forEach((name, tweenData) { + final double tTween = max(0, min(t, tweenData.maxTime - 0.0001)); + result[name] = tweenData.tween.transform(tTween); + }); + return result; + } +} + +/// Single property to tween. Used by [MultiTrackTween]. +class Track { + final String name; + final List<_TrackItem> items = []; + + Track(this.name) : assert(name != null, "Track name must not be null."); + + /// Adds a "piece of animation" to a [Track]. + /// + /// You need to pass a [duration] and a [tween]. It will return the track, so + /// you can specify a track in a builder's style. + /// + /// Optionally you can set a named parameter [curve] that applies an easing + /// curve to the tween. + Track add(Duration duration, Animatable tween, {Curve curve}) { + items.add(_TrackItem(duration, tween, curve: curve)); + return this; + } +} + +class _TrackItem { + final Duration duration; + Animatable tween; + + _TrackItem(this.duration, Animatable _tween, {Curve curve}) + : assert(duration != null, "Please set a duration."), + assert(_tween != null, "Please set a tween.") { + if (curve != null) { + tween = _tween.chain(CurveTween(curve: curve)); + } else { + tween = _tween; + } + } +} + +class _TweenData { + final Animatable tween; + final double maxTime; + + _TweenData({this.tween, this.maxTime}); +} diff --git a/lib/utils/bloc/application_bloc.dart b/lib/utils/bloc/application_bloc.dart index 8167cd8..029eb8c 100644 --- a/lib/utils/bloc/application_bloc.dart +++ b/lib/utils/bloc/application_bloc.dart @@ -1,14 +1,13 @@ +import 'package:flutter/material.dart'; import 'package:bloc/bloc.dart'; import 'package:covid19/data/network/constants/endpoints.dart'; import 'package:covid19/data/network/exceptions/network_exceptions.dart'; import 'package:covid19/data/repository/base_repository.dart'; import 'package:covid19/models/application/country_information_model.dart'; -import 'package:covid19/models/application/ip_model.dart'; import 'package:covid19/models/statistics/countries_list_model.dart'; import 'package:covid19/utils/bloc/application_events.dart'; import 'package:covid19/utils/bloc/application_state.dart'; -import 'package:covid19/utils/image_cache_manager.dart'; -import 'package:flutter/material.dart'; +import 'package:covid19/utils/cache_manager.dart'; /// This class controls the state of the application and informs the main method about the /// flow of the application and any changes to the states of the application. @@ -39,47 +38,23 @@ class ApplicationBloc extends Bloc { // Wrapping the Network Requests in a try/catch statement to catch any errors accounted and // not accounted for try { - // Fetching the user's Address - final IPModel ipaddress = await userRepository.fetchUserIP(); - - // Fetching Information about User's country using the retrieved IP Address + // Fetching Information about User's country ISO2 and IP Address (IP Addess isn't used in the application) final CountryInformationModel userCountryInformation = - await userRepository.fetchUserCountryInformation( - ipAddress: ipaddress.origin, - ); + await userRepository.fetchUserCountryInformation(); // Fetching List of countries for the Covid-19 Tracker Information final List countriesList = await userRepository.fetchCountriesList(); // Instantiating the ImageCacheManager to cache Image data - final ImageCacheManager cacheManager = ImageCacheManager(); + final CacheManager cacheManager = CacheManager(); // Caching the Covid-19 Prevention Do's and Don'ts await cacheManager.downloadFile(Endpoints.baseUrlPreventionInfographic); - // Caching all the Flag images for the List of countries obtained - await Future.forEach( - countriesList, - (item) async* { - try { - await cacheManager.downloadFile( - '${Endpoints.baseUrlCountryFlags}${item.iso2}/flat/32.png', - ); - } - // Catching any erros thrown by the API - Considering all erros as Network absent error - catch (e) { - debugPrint( - 'Error while pre-caching: https://www.countryflags.io/+${item.iso2}+/flat/32.png due to: $e'); - yield ApplicationNetworkException(); - } - }, - ); - // Yielding the value to the output stream by passing all the data fetched yield ApplicationInitialized( countriesList: countriesList, - ipAddress: ipaddress.origin, userCountryInformation: userCountryInformation, ); } diff --git a/lib/utils/bloc/application_state.dart b/lib/utils/bloc/application_state.dart index 6a18dd6..c25273f 100644 --- a/lib/utils/bloc/application_state.dart +++ b/lib/utils/bloc/application_state.dart @@ -1,6 +1,6 @@ +import 'package:flutter/material.dart'; import 'package:covid19/models/application/country_information_model.dart'; import 'package:covid19/models/statistics/countries_list_model.dart'; -import 'package:flutter/material.dart'; /// Base class for the states of Authentication /// @@ -14,12 +14,11 @@ class ApplicationUninitialized extends ApplicationState {} /// The required start-up data has been fetched and loaded in memory class ApplicationInitialized extends ApplicationState { final List countriesList; - final String ipAddress; + final CountryInformationModel userCountryInformation; ApplicationInitialized({ @required this.countriesList, - @required this.ipAddress, @required this.userCountryInformation, }); } diff --git a/lib/utils/image_cache_manager.dart b/lib/utils/cache_manager.dart similarity index 58% rename from lib/utils/image_cache_manager.dart rename to lib/utils/cache_manager.dart index aa1f3ba..faea542 100644 --- a/lib/utils/image_cache_manager.dart +++ b/lib/utils/cache_manager.dart @@ -1,4 +1,3 @@ -import 'package:http/http.dart' as http; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; @@ -6,26 +5,25 @@ import 'package:flutter_cache_manager/flutter_cache_manager.dart'; /// A custom cache manager to be used with [CachedNetworkImage] /// /// This method helps to pre-cache the images in memory -class ImageCacheManager extends BaseCacheManager { - static const key = "CovidImageCache"; +class CacheManager extends BaseCacheManager { + static const key = "CovidCache"; // Instantiating the class - static ImageCacheManager _instance; + static CacheManager _instance; // Factory function is used to return the instance of the class - factory ImageCacheManager() { + factory CacheManager() { // Using dart's Null-aware operator to assign the value only if null - return _instance ??= ImageCacheManager._(); + return _instance ??= CacheManager._(); } // Overriding the method by BaseCacheManager to set parameters such as // cache key, cache age, max cache objects - ImageCacheManager._() + CacheManager._() : super( key, - maxAgeCacheObject: const Duration(days: 7), + maxAgeCacheObject: const Duration(days: 1), maxNrOfCacheObjects: 20, - fileFetcher: _customHttpGetter, ); // Overriding the method by BaseCacheManager to user the Temporary Director @@ -34,10 +32,4 @@ class ImageCacheManager extends BaseCacheManager { final directory = await getTemporaryDirectory(); return p.join(directory.path, key); } - - // Fetching the images using the provided URLs - static Future _customHttpGetter(String url, - {Map headers}) async { - return HttpFileFetcherResponse(await http.get(url, headers: headers)); - } } diff --git a/lib/utils/connection_status_singleton.dart b/lib/utils/connection_status_singleton.dart new file mode 100644 index 0000000..fb4b0ed --- /dev/null +++ b/lib/utils/connection_status_singleton.dart @@ -0,0 +1,67 @@ +import 'dart:async'; //For StreamController/Stream +import 'dart:io'; //InternetAddress utility + +import 'package:connectivity/connectivity.dart'; + +class ConnectionStatusSingleton { + //This creates the single instance by calling the `_internal` constructor specified below + static final ConnectionStatusSingleton _singleton = + ConnectionStatusSingleton._internal(); + ConnectionStatusSingleton._internal(); + + //This is what's used to retrieve the instance through the app + static ConnectionStatusSingleton getInstance() => _singleton; + + //This tracks the current connection status + bool hasConnection = false; + + //This is how we'll allow subscribing to connection changes + StreamController connectionChangeController = StreamController.broadcast(); + + //flutter_connectivity + final Connectivity _connectivity = Connectivity(); + + //Hook into flutter_connectivity's Stream to listen for changes + //And check the connection status out of the gate + void initialize() { + _connectivity.onConnectivityChanged.listen(_connectionChange); + checkConnection(); + } + + Stream get connectionChange => connectionChangeController.stream; + + //A clean up method to close our StreamController + // Because this is meant to exist through the entire application life cycle this isn't + // really an issue + void dispose() { + connectionChangeController.close(); + } + + //flutter_connectivity's listener + void _connectionChange(ConnectivityResult result) { + checkConnection(); + } + + //The test to actually see if there is a connection + Future checkConnection() async { + final bool previousConnection = hasConnection; + + try { + final result = await InternetAddress.lookup('google.com'); + if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) { + hasConnection = true; + } else { + hasConnection = false; + } + } on SocketException catch (_) { + hasConnection = false; + } + + //The connection status changed send out an update to all listeners + if (previousConnection != hasConnection) { + connectionChangeController.add(hasConnection); + } + + return hasConnection; + } +} diff --git a/lib/utils/emoji_flags.dart b/lib/utils/emoji_flags.dart new file mode 100644 index 0000000..196a523 --- /dev/null +++ b/lib/utils/emoji_flags.dart @@ -0,0 +1,1320 @@ +/// List of All Flag Emojis. +final List _emojis = [ + Emoji( + name: 'flag: Ascension Island', + char: '\u{1F1E6}\u{1F1E8}', + iso2Code: 'flag_ac', + ), + Emoji( + name: 'flag: Andorra', + char: '\u{1F1E6}\u{1F1E9}', + iso2Code: 'flag_ad', + ), + Emoji( + name: 'flag: United Arab Emirates', + char: '\u{1F1E6}\u{1F1EA}', + iso2Code: 'flag_ae', + ), + Emoji( + name: 'flag: Afghanistan', + char: '\u{1F1E6}\u{1F1EB}', + iso2Code: 'flag_af', + ), + Emoji( + name: 'flag: Antigua & Barbuda', + char: '\u{1F1E6}\u{1F1EC}', + iso2Code: 'flag_ag', + ), + Emoji( + name: 'flag: Anguilla', + char: '\u{1F1E6}\u{1F1EE}', + iso2Code: 'flag_ai', + ), + Emoji( + name: 'flag: Albania', + char: '\u{1F1E6}\u{1F1F1}', + iso2Code: 'flag_al', + ), + Emoji( + name: 'flag: Armenia', + char: '\u{1F1E6}\u{1F1F2}', + iso2Code: 'flag_am', + ), + Emoji( + name: 'flag: Angola', + char: '\u{1F1E6}\u{1F1F4}', + iso2Code: 'flag_ao', + ), + Emoji( + name: 'flag: Antarctica', + char: '\u{1F1E6}\u{1F1F6}', + iso2Code: 'flag_aq', + ), + Emoji( + name: 'flag: Argentina', + char: '\u{1F1E6}\u{1F1F7}', + iso2Code: 'flag_ar', + ), + Emoji( + name: 'flag: American Samoa', + char: '\u{1F1E6}\u{1F1F8}', + iso2Code: 'flag_as', + ), + Emoji( + name: 'flag: Austria', + char: '\u{1F1E6}\u{1F1F9}', + iso2Code: 'flag_at', + ), + Emoji( + name: 'flag: Australia', + char: '\u{1F1E6}\u{1F1FA}', + iso2Code: 'flag_au', + ), + Emoji( + name: 'flag: Aruba', + char: '\u{1F1E6}\u{1F1FC}', + iso2Code: 'flag_aw', + ), + Emoji( + name: 'flag: Åland Islands', + char: '\u{1F1E6}\u{1F1FD}', + iso2Code: 'flag_ax', + ), + Emoji( + name: 'flag: Azerbaijan', + char: '\u{1F1E6}\u{1F1FF}', + iso2Code: 'flag_az', + ), + Emoji( + name: 'flag: Bosnia & Herzegovina', + char: '\u{1F1E7}\u{1F1E6}', + iso2Code: 'flag_ba', + ), + Emoji( + name: 'flag: Barbados', + char: '\u{1F1E7}\u{1F1E7}', + iso2Code: 'flag_bb', + ), + Emoji( + name: 'flag: Bangladesh', + char: '\u{1F1E7}\u{1F1E9}', + iso2Code: 'flag_bd', + ), + Emoji( + name: 'flag: Belgium', + char: '\u{1F1E7}\u{1F1EA}', + iso2Code: 'flag_be', + ), + Emoji( + name: 'flag: Burkina Faso', + char: '\u{1F1E7}\u{1F1EB}', + iso2Code: 'flag_bf', + ), + Emoji( + name: 'flag: Bulgaria', + char: '\u{1F1E7}\u{1F1EC}', + iso2Code: 'flag_bg', + ), + Emoji( + name: 'flag: Bahrain', + char: '\u{1F1E7}\u{1F1ED}', + iso2Code: 'flag_bh', + ), + Emoji( + name: 'flag: Burundi', + char: '\u{1F1E7}\u{1F1EE}', + iso2Code: 'flag_bi', + ), + Emoji( + name: 'flag: Benin', + char: '\u{1F1E7}\u{1F1EF}', + iso2Code: 'flag_bj', + ), + Emoji( + name: 'flag: St. Barthélemy', + char: '\u{1F1E7}\u{1F1F1}', + iso2Code: 'flag_bl', + ), + Emoji( + name: 'flag: Bermuda', + char: '\u{1F1E7}\u{1F1F2}', + iso2Code: 'flag_bm', + ), + Emoji( + name: 'flag: Brunei', + char: '\u{1F1E7}\u{1F1F3}', + iso2Code: 'flag_bn', + ), + Emoji( + name: 'flag: Bolivia', + char: '\u{1F1E7}\u{1F1F4}', + iso2Code: 'flag_bo', + ), + Emoji( + name: 'flag: Caribbean Netherlands', + char: '\u{1F1E7}\u{1F1F6}', + iso2Code: 'flag_bq', + ), + Emoji( + name: 'flag: Brazil', + char: '\u{1F1E7}\u{1F1F7}', + iso2Code: 'flag_br', + ), + Emoji( + name: 'flag: Bahamas', + char: '\u{1F1E7}\u{1F1F8}', + iso2Code: 'flag_bs', + ), + Emoji( + name: 'flag: Bhutan', + char: '\u{1F1E7}\u{1F1F9}', + iso2Code: 'flag_bt', + ), + Emoji( + name: 'flag: Bouvet Island', + char: '\u{1F1E7}\u{1F1FB}', + iso2Code: 'flag_bv', + ), + Emoji( + name: 'flag: Botswana', + char: '\u{1F1E7}\u{1F1FC}', + iso2Code: 'flag_bw', + ), + Emoji( + name: 'flag: Belarus', + char: '\u{1F1E7}\u{1F1FE}', + iso2Code: 'flag_by', + ), + Emoji( + name: 'flag: Belize', + char: '\u{1F1E7}\u{1F1FF}', + iso2Code: 'flag_bz', + ), + Emoji( + name: 'flag: Canada', + char: '\u{1F1E8}\u{1F1E6}', + iso2Code: 'flag_ca', + ), + Emoji( + name: 'flag: Cocos (Keeling) Islands', + char: '\u{1F1E8}\u{1F1E8}', + iso2Code: 'flag_cc', + ), + Emoji( + name: 'flag: Congo - Kinshasa', + char: '\u{1F1E8}\u{1F1E9}', + iso2Code: 'flag_cd', + ), + Emoji( + name: 'flag: Central African Republic', + char: '\u{1F1E8}\u{1F1EB}', + iso2Code: 'flag_cf', + ), + Emoji( + name: 'flag: Congo - Brazzaville', + char: '\u{1F1E8}\u{1F1EC}', + iso2Code: 'flag_cg', + ), + Emoji( + name: 'flag: Switzerland', + char: '\u{1F1E8}\u{1F1ED}', + iso2Code: 'flag_ch', + ), + Emoji( + name: 'flag: Côte d’Ivoire', + char: '\u{1F1E8}\u{1F1EE}', + iso2Code: 'flag_ci', + ), + Emoji( + name: 'flag: Cook Islands', + char: '\u{1F1E8}\u{1F1F0}', + iso2Code: 'flag_ck', + ), + Emoji( + name: 'flag: Chile', + char: '\u{1F1E8}\u{1F1F1}', + iso2Code: 'flag_cl', + ), + Emoji( + name: 'flag: Cameroon', + char: '\u{1F1E8}\u{1F1F2}', + iso2Code: 'flag_cm', + ), + Emoji( + name: 'flag: China', + char: '\u{1F1E8}\u{1F1F3}', + iso2Code: 'flag_cn', + ), + Emoji( + name: 'flag: Colombia', + char: '\u{1F1E8}\u{1F1F4}', + iso2Code: 'flag_co', + ), + Emoji( + name: 'flag: Clipperton Island', + char: '\u{1F1E8}\u{1F1F5}', + iso2Code: 'flag_cp', + ), + Emoji( + name: 'flag: Costa Rica', + char: '\u{1F1E8}\u{1F1F7}', + iso2Code: 'flag_cr', + ), + Emoji( + name: 'flag: Cuba', + char: '\u{1F1E8}\u{1F1FA}', + iso2Code: 'flag_cu', + ), + Emoji( + name: 'flag: Cape Verde', + char: '\u{1F1E8}\u{1F1FB}', + iso2Code: 'flag_cv', + ), + Emoji( + name: 'flag: Curaçao', + char: '\u{1F1E8}\u{1F1FC}', + iso2Code: 'flag_cw', + ), + Emoji( + name: 'flag: Christmas Island', + char: '\u{1F1E8}\u{1F1FD}', + iso2Code: 'flag_cx', + ), + Emoji( + name: 'flag: Cyprus', + char: '\u{1F1E8}\u{1F1FE}', + iso2Code: 'flag_cy', + ), + Emoji( + name: 'flag: Czechia', + char: '\u{1F1E8}\u{1F1FF}', + iso2Code: 'flag_cz', + ), + Emoji( + name: 'flag: Germany', + char: '\u{1F1E9}\u{1F1EA}', + iso2Code: 'flag_de', + ), + Emoji( + name: 'flag: Diego Garcia', + char: '\u{1F1E9}\u{1F1EC}', + iso2Code: 'flag_dg', + ), + Emoji( + name: 'flag: Djibouti', + char: '\u{1F1E9}\u{1F1EF}', + iso2Code: 'flag_dj', + ), + Emoji( + name: 'flag: Denmark', + char: '\u{1F1E9}\u{1F1F0}', + iso2Code: 'flag_dk', + ), + Emoji( + name: 'flag: Dominica', + char: '\u{1F1E9}\u{1F1F2}', + iso2Code: 'flag_dm', + ), + Emoji( + name: 'flag: Dominican Republic', + char: '\u{1F1E9}\u{1F1F4}', + iso2Code: 'flag_do', + ), + Emoji( + name: 'flag: Algeria', + char: '\u{1F1E9}\u{1F1FF}', + iso2Code: 'flag_dz', + ), + Emoji( + name: 'flag: Ceuta & Melilla', + char: '\u{1F1EA}\u{1F1E6}', + iso2Code: 'flag_ea', + ), + Emoji( + name: 'flag: Ecuador', + char: '\u{1F1EA}\u{1F1E8}', + iso2Code: 'flag_ec', + ), + Emoji( + name: 'flag: Estonia', + char: '\u{1F1EA}\u{1F1EA}', + iso2Code: 'flag_ee', + ), + Emoji( + name: 'flag: Egypt', + char: '\u{1F1EA}\u{1F1EC}', + iso2Code: 'flag_eg', + ), + Emoji( + name: 'flag: Western Sahara', + char: '\u{1F1EA}\u{1F1ED}', + iso2Code: 'flag_eh', + ), + Emoji( + name: 'flag: Eritrea', + char: '\u{1F1EA}\u{1F1F7}', + iso2Code: 'flag_er', + ), + Emoji( + name: 'flag: Spain', + char: '\u{1F1EA}\u{1F1F8}', + iso2Code: 'flag_es', + ), + Emoji( + name: 'flag: Ethiopia', + char: '\u{1F1EA}\u{1F1F9}', + iso2Code: 'flag_et', + ), + Emoji( + name: 'flag: European Union', + char: '\u{1F1EA}\u{1F1FA}', + iso2Code: 'flag_eu', + ), + Emoji( + name: 'flag: Finland', + char: '\u{1F1EB}\u{1F1EE}', + iso2Code: 'flag_fi', + ), + Emoji( + name: 'flag: Fiji', + char: '\u{1F1EB}\u{1F1EF}', + iso2Code: 'flag_fj', + ), + Emoji( + name: 'flag: Falkland Islands', + char: '\u{1F1EB}\u{1F1F0}', + iso2Code: 'flag_fk', + ), + Emoji( + name: 'flag: Micronesia', + char: '\u{1F1EB}\u{1F1F2}', + iso2Code: 'flag_fm', + ), + Emoji( + name: 'flag: Faroe Islands', + char: '\u{1F1EB}\u{1F1F4}', + iso2Code: 'flag_fo', + ), + Emoji( + name: 'flag: France', + char: '\u{1F1EB}\u{1F1F7}', + iso2Code: 'flag_fr', + ), + Emoji( + name: 'flag: Gabon', + char: '\u{1F1EC}\u{1F1E6}', + iso2Code: 'flag_ga', + ), + Emoji( + name: 'flag: United Kingdom', + char: '\u{1F1EC}\u{1F1E7}', + iso2Code: 'flag_gb', + ), + Emoji( + name: 'flag: Grenada', + char: '\u{1F1EC}\u{1F1E9}', + iso2Code: 'flag_gd', + ), + Emoji( + name: 'flag: Georgia', + char: '\u{1F1EC}\u{1F1EA}', + iso2Code: 'flag_ge', + ), + Emoji( + name: 'flag: French Guiana', + char: '\u{1F1EC}\u{1F1EB}', + iso2Code: 'flag_gf', + ), + Emoji( + name: 'flag: Guernsey', + char: '\u{1F1EC}\u{1F1EC}', + iso2Code: 'flag_gg', + ), + Emoji( + name: 'flag: Ghana', + char: '\u{1F1EC}\u{1F1ED}', + iso2Code: 'flag_gh', + ), + Emoji( + name: 'flag: Gibraltar', + char: '\u{1F1EC}\u{1F1EE}', + iso2Code: 'flag_gi', + ), + Emoji( + name: 'flag: Greenland', + char: '\u{1F1EC}\u{1F1F1}', + iso2Code: 'flag_gl', + ), + Emoji( + name: 'flag: Gambia', + char: '\u{1F1EC}\u{1F1F2}', + iso2Code: 'flag_gm', + ), + Emoji( + name: 'flag: Guinea', + char: '\u{1F1EC}\u{1F1F3}', + iso2Code: 'flag_gn', + ), + Emoji( + name: 'flag: Guadeloupe', + char: '\u{1F1EC}\u{1F1F5}', + iso2Code: 'flag_gp', + ), + Emoji( + name: 'flag: Equatorial Guinea', + char: '\u{1F1EC}\u{1F1F6}', + iso2Code: 'flag_gq', + ), + Emoji( + name: 'flag: Greece', + char: '\u{1F1EC}\u{1F1F7}', + iso2Code: 'flag_gr', + ), + Emoji( + name: 'flag: South Georgia & South Sandwich Islands', + char: '\u{1F1EC}\u{1F1F8}', + iso2Code: 'flag_gs', + ), + Emoji( + name: 'flag: Guatemala', + char: '\u{1F1EC}\u{1F1F9}', + iso2Code: 'flag_gt', + ), + Emoji( + name: 'flag: Guam', + char: '\u{1F1EC}\u{1F1FA}', + iso2Code: 'flag_gu', + ), + Emoji( + name: 'flag: Guinea-Bissau', + char: '\u{1F1EC}\u{1F1FC}', + iso2Code: 'flag_gw', + ), + Emoji( + name: 'flag: Guyana', + char: '\u{1F1EC}\u{1F1FE}', + iso2Code: 'flag_gy', + ), + Emoji( + name: 'flag: Hong Kong SAR China', + char: '\u{1F1ED}\u{1F1F0}', + iso2Code: 'flag_hk', + ), + Emoji( + name: 'flag: Heard & McDonald Islands', + char: '\u{1F1ED}\u{1F1F2}', + iso2Code: 'flag_hm', + ), + Emoji( + name: 'flag: Honduras', + char: '\u{1F1ED}\u{1F1F3}', + iso2Code: 'flag_hn', + ), + Emoji( + name: 'flag: Croatia', + char: '\u{1F1ED}\u{1F1F7}', + iso2Code: 'flag_hr', + ), + Emoji( + name: 'flag: Haiti', + char: '\u{1F1ED}\u{1F1F9}', + iso2Code: 'flag_ht', + ), + Emoji( + name: 'flag: Hungary', + char: '\u{1F1ED}\u{1F1FA}', + iso2Code: 'flag_hu', + ), + Emoji( + name: 'flag: Canary Islands', + char: '\u{1F1EE}\u{1F1E8}', + iso2Code: 'flag_ic', + ), + Emoji( + name: 'flag: Indonesia', + char: '\u{1F1EE}\u{1F1E9}', + iso2Code: 'flag_id', + ), + Emoji( + name: 'flag: Ireland', + char: '\u{1F1EE}\u{1F1EA}', + iso2Code: 'flag_ie', + ), + Emoji( + name: 'flag: Israel', + char: '\u{1F1EE}\u{1F1F1}', + iso2Code: 'flag_il', + ), + Emoji( + name: 'flag: Isle of Man', + char: '\u{1F1EE}\u{1F1F2}', + iso2Code: 'flag_im', + ), + Emoji( + name: 'flag: India', + char: '\u{1F1EE}\u{1F1F3}', + iso2Code: 'flag_in', + ), + Emoji( + name: 'flag: British Indian Ocean Territory', + char: '\u{1F1EE}\u{1F1F4}', + iso2Code: 'flag_io', + ), + Emoji( + name: 'flag: Iraq', + char: '\u{1F1EE}\u{1F1F6}', + iso2Code: 'flag_iq', + ), + Emoji( + name: 'flag: Iran', + char: '\u{1F1EE}\u{1F1F7}', + iso2Code: 'flag_ir', + ), + Emoji( + name: 'flag: Iceland', + char: '\u{1F1EE}\u{1F1F8}', + iso2Code: 'flag_is', + ), + Emoji( + name: 'flag: Italy', + char: '\u{1F1EE}\u{1F1F9}', + iso2Code: 'flag_it', + ), + Emoji( + name: 'flag: Jersey', + char: '\u{1F1EF}\u{1F1EA}', + iso2Code: 'flag_je', + ), + Emoji( + name: 'flag: Jamaica', + char: '\u{1F1EF}\u{1F1F2}', + iso2Code: 'flag_jm', + ), + Emoji( + name: 'flag: Jordan', + char: '\u{1F1EF}\u{1F1F4}', + iso2Code: 'flag_jo', + ), + Emoji( + name: 'flag: Japan', + char: '\u{1F1EF}\u{1F1F5}', + iso2Code: 'flag_jp', + ), + Emoji( + name: 'flag: Kenya', + char: '\u{1F1F0}\u{1F1EA}', + iso2Code: 'flag_ke', + ), + Emoji( + name: 'flag: Kyrgyzstan', + char: '\u{1F1F0}\u{1F1EC}', + iso2Code: 'flag_kg', + ), + Emoji( + name: 'flag: Cambodia', + char: '\u{1F1F0}\u{1F1ED}', + iso2Code: 'flag_kh', + ), + Emoji( + name: 'flag: Kiribati', + char: '\u{1F1F0}\u{1F1EE}', + iso2Code: 'flag_ki', + ), + Emoji( + name: 'flag: Comoros', + char: '\u{1F1F0}\u{1F1F2}', + iso2Code: 'flag_km', + ), + Emoji( + name: 'flag: St. Kitts & Nevis', + char: '\u{1F1F0}\u{1F1F3}', + iso2Code: 'flag_kn', + ), + Emoji( + name: 'flag: North Korea', + char: '\u{1F1F0}\u{1F1F5}', + iso2Code: 'flag_kp', + ), + Emoji( + name: 'flag: South Korea', + char: '\u{1F1F0}\u{1F1F7}', + iso2Code: 'flag_kr', + ), + Emoji( + name: 'flag: Kuwait', + char: '\u{1F1F0}\u{1F1FC}', + iso2Code: 'flag_kw', + ), + Emoji( + name: 'flag: Cayman Islands', + char: '\u{1F1F0}\u{1F1FE}', + iso2Code: 'flag_ky', + ), + Emoji( + name: 'flag: Kazakhstan', + char: '\u{1F1F0}\u{1F1FF}', + iso2Code: 'flag_kz', + ), + Emoji( + name: 'flag: Laos', + char: '\u{1F1F1}\u{1F1E6}', + iso2Code: 'flag_la', + ), + Emoji( + name: 'flag: Lebanon', + char: '\u{1F1F1}\u{1F1E7}', + iso2Code: 'flag_lb', + ), + Emoji( + name: 'flag: St. Lucia', + char: '\u{1F1F1}\u{1F1E8}', + iso2Code: 'flag_lc', + ), + Emoji( + name: 'flag: Liechtenstein', + char: '\u{1F1F1}\u{1F1EE}', + iso2Code: 'flag_li', + ), + Emoji( + name: 'flag: Sri Lanka', + char: '\u{1F1F1}\u{1F1F0}', + iso2Code: 'flag_lk', + ), + Emoji( + name: 'flag: Liberia', + char: '\u{1F1F1}\u{1F1F7}', + iso2Code: 'flag_lr', + ), + Emoji( + name: 'flag: Lesotho', + char: '\u{1F1F1}\u{1F1F8}', + iso2Code: 'flag_ls', + ), + Emoji( + name: 'flag: Lithuania', + char: '\u{1F1F1}\u{1F1F9}', + iso2Code: 'flag_lt', + ), + Emoji( + name: 'flag: Luxembourg', + char: '\u{1F1F1}\u{1F1FA}', + iso2Code: 'flag_lu', + ), + Emoji( + name: 'flag: Latvia', + char: '\u{1F1F1}\u{1F1FB}', + iso2Code: 'flag_lv', + ), + Emoji( + name: 'flag: Libya', + char: '\u{1F1F1}\u{1F1FE}', + iso2Code: 'flag_ly', + ), + Emoji( + name: 'flag: Morocco', + char: '\u{1F1F2}\u{1F1E6}', + iso2Code: 'flag_ma', + ), + Emoji( + name: 'flag: Monaco', + char: '\u{1F1F2}\u{1F1E8}', + iso2Code: 'flag_mc', + ), + Emoji( + name: 'flag: Moldova', + char: '\u{1F1F2}\u{1F1E9}', + iso2Code: 'flag_md', + ), + Emoji( + name: 'flag: Montenegro', + char: '\u{1F1F2}\u{1F1EA}', + iso2Code: 'flag_me', + ), + Emoji( + name: 'flag: St. Martin', + char: '\u{1F1F2}\u{1F1EB}', + iso2Code: 'flag_mf', + ), + Emoji( + name: 'flag: Madagascar', + char: '\u{1F1F2}\u{1F1EC}', + iso2Code: 'flag_mg', + ), + Emoji( + name: 'flag: Marshall Islands', + char: '\u{1F1F2}\u{1F1ED}', + iso2Code: 'flag_mh', + ), + Emoji( + name: 'flag: North Macedonia', + char: '\u{1F1F2}\u{1F1F0}', + iso2Code: 'flag_mk', + ), + Emoji( + name: 'flag: Mali', + char: '\u{1F1F2}\u{1F1F1}', + iso2Code: 'flag_ml', + ), + Emoji( + name: 'flag: Myanmar (Burma)', + char: '\u{1F1F2}\u{1F1F2}', + iso2Code: 'flag_mm', + ), + Emoji( + name: 'flag: Mongolia', + char: '\u{1F1F2}\u{1F1F3}', + iso2Code: 'flag_mn', + ), + Emoji( + name: 'flag: Macao SAR China', + char: '\u{1F1F2}\u{1F1F4}', + iso2Code: 'flag_mo', + ), + Emoji( + name: 'flag: Northern Mariana Islands', + char: '\u{1F1F2}\u{1F1F5}', + iso2Code: 'flag_mp', + ), + Emoji( + name: 'flag: Martinique', + char: '\u{1F1F2}\u{1F1F6}', + iso2Code: 'flag_mq', + ), + Emoji( + name: 'flag: Mauritania', + char: '\u{1F1F2}\u{1F1F7}', + iso2Code: 'flag_mr', + ), + Emoji( + name: 'flag: Montserrat', + char: '\u{1F1F2}\u{1F1F8}', + iso2Code: 'flag_ms', + ), + Emoji( + name: 'flag: Malta', + char: '\u{1F1F2}\u{1F1F9}', + iso2Code: 'flag_mt', + ), + Emoji( + name: 'flag: Mauritius', + char: '\u{1F1F2}\u{1F1FA}', + iso2Code: 'flag_mu', + ), + Emoji( + name: 'flag: Maldives', + char: '\u{1F1F2}\u{1F1FB}', + iso2Code: 'flag_mv', + ), + Emoji( + name: 'flag: Malawi', + char: '\u{1F1F2}\u{1F1FC}', + iso2Code: 'flag_mw', + ), + Emoji( + name: 'flag: Mexico', + char: '\u{1F1F2}\u{1F1FD}', + iso2Code: 'flag_mx', + ), + Emoji( + name: 'flag: Malaysia', + char: '\u{1F1F2}\u{1F1FE}', + iso2Code: 'flag_my', + ), + Emoji( + name: 'flag: Mozambique', + char: '\u{1F1F2}\u{1F1FF}', + iso2Code: 'flag_mz', + ), + Emoji( + name: 'flag: Namibia', + char: '\u{1F1F3}\u{1F1E6}', + iso2Code: 'flag_na', + ), + Emoji( + name: 'flag: New Caledonia', + char: '\u{1F1F3}\u{1F1E8}', + iso2Code: 'flag_nc', + ), + Emoji( + name: 'flag: Niger', + char: '\u{1F1F3}\u{1F1EA}', + iso2Code: 'flag_ne', + ), + Emoji( + name: 'flag: Norfolk Island', + char: '\u{1F1F3}\u{1F1EB}', + iso2Code: 'flag_nf', + ), + Emoji( + name: 'flag: Nigeria', + char: '\u{1F1F3}\u{1F1EC}', + iso2Code: 'flag_ng', + ), + Emoji( + name: 'flag: Nicaragua', + char: '\u{1F1F3}\u{1F1EE}', + iso2Code: 'flag_ni', + ), + Emoji( + name: 'flag: Netherlands', + char: '\u{1F1F3}\u{1F1F1}', + iso2Code: 'flag_nl', + ), + Emoji( + name: 'flag: Norway', + char: '\u{1F1F3}\u{1F1F4}', + iso2Code: 'flag_no', + ), + Emoji( + name: 'flag: Nepal', + char: '\u{1F1F3}\u{1F1F5}', + iso2Code: 'flag_np', + ), + Emoji( + name: 'flag: Nauru', + char: '\u{1F1F3}\u{1F1F7}', + iso2Code: 'flag_nr', + ), + Emoji( + name: 'flag: Niue', + char: '\u{1F1F3}\u{1F1FA}', + iso2Code: 'flag_nu', + ), + Emoji( + name: 'flag: New Zealand', + char: '\u{1F1F3}\u{1F1FF}', + iso2Code: 'flag_nz', + ), + Emoji( + name: 'flag: Oman', + char: '\u{1F1F4}\u{1F1F2}', + iso2Code: 'flag_om', + ), + Emoji( + name: 'flag: Panama', + char: '\u{1F1F5}\u{1F1E6}', + iso2Code: 'flag_pa', + ), + Emoji( + name: 'flag: Peru', + char: '\u{1F1F5}\u{1F1EA}', + iso2Code: 'flag_pe', + ), + Emoji( + name: 'flag: French Polynesia', + char: '\u{1F1F5}\u{1F1EB}', + iso2Code: 'flag_pf', + ), + Emoji( + name: 'flag: Papua New Guinea', + char: '\u{1F1F5}\u{1F1EC}', + iso2Code: 'flag_pg', + ), + Emoji( + name: 'flag: Philippines', + char: '\u{1F1F5}\u{1F1ED}', + iso2Code: 'flag_ph', + ), + Emoji( + name: 'flag: Pakistan', + char: '\u{1F1F5}\u{1F1F0}', + iso2Code: 'flag_pk', + ), + Emoji( + name: 'flag: Poland', + char: '\u{1F1F5}\u{1F1F1}', + iso2Code: 'flag_pl', + ), + Emoji( + name: 'flag: St. Pierre & Miquelon', + char: '\u{1F1F5}\u{1F1F2}', + iso2Code: 'flag_pm', + ), + Emoji( + name: 'flag: Pitcairn Islands', + char: '\u{1F1F5}\u{1F1F3}', + iso2Code: 'flag_pn', + ), + Emoji( + name: 'flag: Puerto Rico', + char: '\u{1F1F5}\u{1F1F7}', + iso2Code: 'flag_pr', + ), + Emoji( + name: 'flag: Palestinian Territories', + char: '\u{1F1F5}\u{1F1F8}', + iso2Code: 'flag_ps', + ), + Emoji( + name: 'flag: Portugal', + char: '\u{1F1F5}\u{1F1F9}', + iso2Code: 'flag_pt', + ), + Emoji( + name: 'flag: Palau', + char: '\u{1F1F5}\u{1F1FC}', + iso2Code: 'flag_pw', + ), + Emoji( + name: 'flag: Paraguay', + char: '\u{1F1F5}\u{1F1FE}', + iso2Code: 'flag_py', + ), + Emoji( + name: 'flag: Qatar', + char: '\u{1F1F6}\u{1F1E6}', + iso2Code: 'flag_qa', + ), + Emoji( + name: 'flag: Réunion', + char: '\u{1F1F7}\u{1F1EA}', + iso2Code: 'flag_re', + ), + Emoji( + name: 'flag: Romania', + char: '\u{1F1F7}\u{1F1F4}', + iso2Code: 'flag_ro', + ), + Emoji( + name: 'flag: Serbia', + char: '\u{1F1F7}\u{1F1F8}', + iso2Code: 'flag_rs', + ), + Emoji( + name: 'flag: Russia', + char: '\u{1F1F7}\u{1F1FA}', + iso2Code: 'flag_ru', + ), + Emoji( + name: 'flag: Rwanda', + char: '\u{1F1F7}\u{1F1FC}', + iso2Code: 'flag_rw', + ), + Emoji( + name: 'flag: Saudi Arabia', + char: '\u{1F1F8}\u{1F1E6}', + iso2Code: 'flag_sa', + ), + Emoji( + name: 'flag: Solomon Islands', + char: '\u{1F1F8}\u{1F1E7}', + iso2Code: 'flag_sb', + ), + Emoji( + name: 'flag: Seychelles', + char: '\u{1F1F8}\u{1F1E8}', + iso2Code: 'flag_sc', + ), + Emoji( + name: 'flag: Sudan', + char: '\u{1F1F8}\u{1F1E9}', + iso2Code: 'flag_sd', + ), + Emoji( + name: 'flag: Sweden', + char: '\u{1F1F8}\u{1F1EA}', + iso2Code: 'flag_se', + ), + Emoji( + name: 'flag: Singapore', + char: '\u{1F1F8}\u{1F1EC}', + iso2Code: 'flag_sg', + ), + Emoji( + name: 'flag: St. Helena', + char: '\u{1F1F8}\u{1F1ED}', + iso2Code: 'flag_sh', + ), + Emoji( + name: 'flag: Slovenia', + char: '\u{1F1F8}\u{1F1EE}', + iso2Code: 'flag_si', + ), + Emoji( + name: 'flag: Svalbard & Jan Mayen', + char: '\u{1F1F8}\u{1F1EF}', + iso2Code: 'flag_sj', + ), + Emoji( + name: 'flag: Slovakia', + char: '\u{1F1F8}\u{1F1F0}', + iso2Code: 'flag_sk', + ), + Emoji( + name: 'flag: Sierra Leone', + char: '\u{1F1F8}\u{1F1F1}', + iso2Code: 'flag_sl', + ), + Emoji( + name: 'flag: San Marino', + char: '\u{1F1F8}\u{1F1F2}', + iso2Code: 'flag_sm', + ), + Emoji( + name: 'flag: Senegal', + char: '\u{1F1F8}\u{1F1F3}', + iso2Code: 'flag_sn', + ), + Emoji( + name: 'flag: Somalia', + char: '\u{1F1F8}\u{1F1F4}', + iso2Code: 'flag_so', + ), + Emoji( + name: 'flag: Suriname', + char: '\u{1F1F8}\u{1F1F7}', + iso2Code: 'flag_sr', + ), + Emoji( + name: 'flag: South Sudan', + char: '\u{1F1F8}\u{1F1F8}', + iso2Code: 'flag_ss', + ), + Emoji( + name: 'flag: São Tomé & Príncipe', + char: '\u{1F1F8}\u{1F1F9}', + iso2Code: 'flag_st', + ), + Emoji( + name: 'flag: El Salvador', + char: '\u{1F1F8}\u{1F1FB}', + iso2Code: 'flag_sv', + ), + Emoji( + name: 'flag: Sint Maarten', + char: '\u{1F1F8}\u{1F1FD}', + iso2Code: 'flag_sx', + ), + Emoji( + name: 'flag: Syria', + char: '\u{1F1F8}\u{1F1FE}', + iso2Code: 'flag_sy', + ), + Emoji( + name: 'flag: Eswatini', + char: '\u{1F1F8}\u{1F1FF}', + iso2Code: 'flag_sz', + ), + Emoji( + name: 'flag: Tristan da Cunha', + char: '\u{1F1F9}\u{1F1E6}', + iso2Code: 'flag_ta', + ), + Emoji( + name: 'flag: Turks & Caicos Islands', + char: '\u{1F1F9}\u{1F1E8}', + iso2Code: 'flag_tc', + ), + Emoji( + name: 'flag: Chad', + char: '\u{1F1F9}\u{1F1E9}', + iso2Code: 'flag_td', + ), + Emoji( + name: 'flag: French Southern Territories', + char: '\u{1F1F9}\u{1F1EB}', + iso2Code: 'flag_tf', + ), + Emoji( + name: 'flag: Togo', + char: '\u{1F1F9}\u{1F1EC}', + iso2Code: 'flag_tg', + ), + Emoji( + name: 'flag: Thailand', + char: '\u{1F1F9}\u{1F1ED}', + iso2Code: 'flag_th', + ), + Emoji( + name: 'flag: Tajikistan', + char: '\u{1F1F9}\u{1F1EF}', + iso2Code: 'flag_tj', + ), + Emoji( + name: 'flag: Tokelau', + char: '\u{1F1F9}\u{1F1F0}', + iso2Code: 'flag_tk', + ), + Emoji( + name: 'flag: Timor-Leste', + char: '\u{1F1F9}\u{1F1F1}', + iso2Code: 'flag_tl', + ), + Emoji( + name: 'flag: Turkmenistan', + char: '\u{1F1F9}\u{1F1F2}', + iso2Code: 'flag_tm', + ), + Emoji( + name: 'flag: Tunisia', + char: '\u{1F1F9}\u{1F1F3}', + iso2Code: 'flag_tn', + ), + Emoji( + name: 'flag: Tonga', + char: '\u{1F1F9}\u{1F1F4}', + iso2Code: 'flag_to', + ), + Emoji( + name: 'flag: Turkey', + char: '\u{1F1F9}\u{1F1F7}', + iso2Code: 'flag_tr', + ), + Emoji( + name: 'flag: Trinidad & Tobago', + char: '\u{1F1F9}\u{1F1F9}', + iso2Code: 'flag_tt', + ), + Emoji( + name: 'flag: Tuvalu', + char: '\u{1F1F9}\u{1F1FB}', + iso2Code: 'flag_tv', + ), + Emoji( + name: 'flag: Taiwan', + char: '\u{1F1F9}\u{1F1FC}', + iso2Code: 'flag_tw', + ), + Emoji( + name: 'flag: Tanzania', + char: '\u{1F1F9}\u{1F1FF}', + iso2Code: 'flag_tz', + ), + Emoji( + name: 'flag: Ukraine', + char: '\u{1F1FA}\u{1F1E6}', + iso2Code: 'flag_ua', + ), + Emoji( + name: 'flag: Uganda', + char: '\u{1F1FA}\u{1F1EC}', + iso2Code: 'flag_ug', + ), + Emoji( + name: 'flag: U.S. Outlying Islands', + char: '\u{1F1FA}\u{1F1F2}', + iso2Code: 'flag_um', + ), + Emoji( + name: 'flag: United States', + char: '\u{1F1FA}\u{1F1F8}', + iso2Code: 'flag_us', + ), + Emoji( + name: 'flag: Uruguay', + char: '\u{1F1FA}\u{1F1FE}', + iso2Code: 'flag_uy', + ), + Emoji( + name: 'flag: Uzbekistan', + char: '\u{1F1FA}\u{1F1FF}', + iso2Code: 'flag_uz', + ), + Emoji( + name: 'flag: Vatican City', + char: '\u{1F1FB}\u{1F1E6}', + iso2Code: 'flag_va', + ), + Emoji( + name: 'flag: St. Vincent & Grenadines', + char: '\u{1F1FB}\u{1F1E8}', + iso2Code: 'flag_vc', + ), + Emoji( + name: 'flag: Venezuela', + char: '\u{1F1FB}\u{1F1EA}', + iso2Code: 'flag_ve', + ), + Emoji( + name: 'flag: British Virgin Islands', + char: '\u{1F1FB}\u{1F1EC}', + iso2Code: 'flag_vg', + ), + Emoji( + name: 'flag: U.S. Virgin Islands', + char: '\u{1F1FB}\u{1F1EE}', + iso2Code: 'flag_vi', + ), + Emoji( + name: 'flag: Vietnam', + char: '\u{1F1FB}\u{1F1F3}', + iso2Code: 'flag_vn', + ), + Emoji( + name: 'flag: Vanuatu', + char: '\u{1F1FB}\u{1F1FA}', + iso2Code: 'flag_vu', + ), + Emoji( + name: 'flag: Wallis & Futuna', + char: '\u{1F1FC}\u{1F1EB}', + iso2Code: 'flag_wf', + ), + Emoji( + name: 'flag: Samoa', + char: '\u{1F1FC}\u{1F1F8}', + iso2Code: 'flag_ws', + ), + Emoji( + name: 'flag: Kosovo', + char: '\u{1F1FD}\u{1F1F0}', + iso2Code: 'flag_xk', + ), + Emoji( + name: 'flag: Yemen', + char: '\u{1F1FE}\u{1F1EA}', + iso2Code: 'flag_ye', + ), + Emoji( + name: 'flag: Mayotte', + char: '\u{1F1FE}\u{1F1F9}', + iso2Code: 'flag_yt', + ), + Emoji( + name: 'flag: South Africa', + char: '\u{1F1FF}\u{1F1E6}', + iso2Code: 'flag_za', + ), + Emoji( + name: 'flag: Zambia', + char: '\u{1F1FF}\u{1F1F2}', + iso2Code: 'flag_zm', + ), + Emoji( + name: 'flag: Zimbabwe', + char: '\u{1F1FF}\u{1F1FC}', + iso2Code: 'flag_zw', + ), + Emoji( + name: 'flag: England', + char: '\u{1F3F4}\u{E0067}\u{E0062}\u{E0065}\u{E006E}\u{E0067}\u{E007F}', + iso2Code: 'flag_gb', + ), +]; + +/// [Emoji] is a model class which defines the structure required +class Emoji { + final String name; + final String char; + final String iso2Code; + + /// [name] of emoji Flag. + /// [char] character of emoji Flag. + /// [iso2Code] ISO2 of emoji Flag. + Emoji({ + this.name, + this.char, + this.iso2Code, + }); + + /// Get all Emojis + static List all() => _emojis; + + /// Returns the Emoji Flag from the given ISO2 Code of the Country + factory Emoji.byISOCode(String isoCode) { + return Emoji.all().firstWhere((Emoji emoji) => emoji.iso2Code == isoCode); + } + + @override + String toString() => char; +} diff --git a/lib/utils/request_util.dart b/lib/utils/request_util.dart index 094169a..06f00ca 100644 --- a/lib/utils/request_util.dart +++ b/lib/utils/request_util.dart @@ -1,6 +1,7 @@ import 'dart:collection'; import 'dart:convert'; import 'dart:io'; +import 'package:covid19/utils/cache_manager.dart'; import 'package:http/http.dart' as http; import 'package:crypto/crypto.dart' show md5; import 'package:flutter/material.dart'; @@ -41,7 +42,10 @@ class HttpRequestUtil { /// Sends a ***HTTP GET*** request to the specified url /// Dynamic is used to support different types of JSON responses /// Maps and Arrays in the case of the current situation of use - static dynamic getRequest(String url) async { + static dynamic getRequest({ + String url, + bool shouldCache = false, + }) async { debugPrint('getting $url'); try { @@ -49,7 +53,11 @@ class HttpRequestUtil { final jsonResponse = await http.get(url); final responseMap = jsonDecode(jsonResponse.body); - debugPrint('HTTP Response :- \n $responseMap'); + if (shouldCache) { + await CacheManager().downloadFile(url, force: true); + } + + // debugPrint('HTTP Response :- \n $responseMap'); return responseMap; } // Catching the [FormatException] which occurs due to the server not responding @@ -62,8 +70,9 @@ class HttpRequestUtil { // Thus throwing the Network Exception on SocketException { debugPrint('Get Request Socket Exception'); - throw NetworkException( - message: 'You don’t seem to have an active Internet Connection', + return handleSocketException( + url: url, + shouldCache: shouldCache, ); } // Catching any Generic Errors and throwing APIRresponseException to display @@ -76,6 +85,32 @@ class HttpRequestUtil { } } + /// Handle [SocketException]. Try and retrieve cached responses + /// if [shouldCache] is set to true + static dynamic handleSocketException({ + String url, + bool shouldCache, + }) async { + if (!shouldCache) { + throw NetworkException( + message: 'You don’t seem to have an active Internet Connection', + ); + } + // debugPrint('URL is $url'); + try { + final cacheFileIInfo = await CacheManager().getFileFromCache(url); + debugPrint('${cacheFileIInfo.file}'); + final cacheFile = await cacheFileIInfo.file.readAsString(); + final cache = jsonDecode(cacheFile); + return cache; + } catch (e) { + debugPrint('Cache Request Network Exception $e'); + throw NetworkException( + message: 'You don’t seem to have an active Internet Connection', + ); + } + } + /// Handles errors by throwing it up the call stack if the http request to the /// server has failed. /// Has to be implemented in the future to perform a check if the current API diff --git a/lib/widgets/country_picker/country_picker_dialog.dart b/lib/widgets/country_picker/country_picker_dialog.dart index 4dcd699..02382e5 100644 --- a/lib/widgets/country_picker/country_picker_dialog.dart +++ b/lib/widgets/country_picker/country_picker_dialog.dart @@ -1,10 +1,10 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart'; import 'package:covid19/constants/strings.dart'; import 'package:covid19/models/statistics/countries_list_model.dart'; import 'package:covid19/utils/custom_scroll_behaviour.dart'; import 'package:covid19/widgets/country_picker/utils/my_alert_dialog.dart'; import 'package:covid19/widgets/country_picker/utils/typedefs.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; /// Provides a customizable [Dialog] which displays all countries /// with optional search feature @@ -92,7 +92,7 @@ class CountryPickerDialog extends StatefulWidget { this.onValuePicked, this.title, this.titlePadding, - this.contentPadding = const EdgeInsets.fromLTRB(0.0, 12.0, 0.0, 16.0), + this.contentPadding = const EdgeInsets.fromLTRB(0.0, 12.0, 0.0, 0.0), this.semanticLabel, this.sortComparator, this.priorityList, diff --git a/lib/widgets/country_picker/utils/typedefs.dart b/lib/widgets/country_picker/utils/typedefs.dart index fae4127..4ff4dd4 100644 --- a/lib/widgets/country_picker/utils/typedefs.dart +++ b/lib/widgets/country_picker/utils/typedefs.dart @@ -1,5 +1,5 @@ -import 'package:covid19/models/statistics/countries_list_model.dart'; import 'package:flutter/material.dart'; +import 'package:covid19/models/statistics/countries_list_model.dart'; /// Function with alias [ItemBuilder] that can be extended to build /// rows of the content diff --git a/lib/widgets/custom_shimmer.dart b/lib/widgets/custom_shimmer.dart index e472bb4..6432ada 100644 --- a/lib/widgets/custom_shimmer.dart +++ b/lib/widgets/custom_shimmer.dart @@ -1,6 +1,6 @@ -import 'package:covid19/constants/colors.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'package:covid19/constants/colors.dart'; /// /// An enum defines all supported directions of shimmer effect diff --git a/pubspec.lock b/pubspec.lock index e0ded80..873b5d0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -78,6 +78,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.14.11" + connectivity: + dependency: "direct main" + description: + name: connectivity + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.8+2" + connectivity_macos: + dependency: transitive + description: + name: connectivity_macos + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0+2" + connectivity_platform_interface: + dependency: transitive + description: + name: connectivity_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" convert: dependency: transitive description: @@ -611,4 +632,4 @@ packages: version: "2.2.0" sdks: dart: ">=2.7.0 <3.0.0" - flutter: ">=1.12.8 <2.0.0" + flutter: ">=1.12.13+hotfix.5 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 08040c1..c723eac 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -38,6 +38,8 @@ dependencies: cached_network_image: ^2.0.0 # Package to launch OS specific URLs url_launcher: ^5.4.2 + # Package to check the Connection Status of the device + connectivity: ^0.4.8+2 dev_dependencies: flutter_test: diff --git a/test/asset_animations_test.dart b/test/asset_animations_test.dart index d590a7e..0d7e4d2 100644 --- a/test/asset_animations_test.dart +++ b/test/asset_animations_test.dart @@ -1,4 +1,4 @@ -//// Generated by spider on 2020-04-16 20:24:50.642530 +//// Generated by spider on 2020-04-20 10:43:00.185277 import 'dart:io'; diff --git a/test/asset_images_test.dart b/test/asset_images_test.dart index d93d6a0..e111eaf 100644 --- a/test/asset_images_test.dart +++ b/test/asset_images_test.dart @@ -1,4 +1,4 @@ -//// Generated by spider on 2020-04-16 20:25:08.582486 +//// Generated by spider on 2020-04-20 11:45:57.780939 import 'dart:io'; @@ -8,9 +8,11 @@ import 'package:test/test.dart'; void main() { test('asset_images assets test', () { expect(true, File(AssetImages.noInternet).existsSync()); + expect(true, File(AssetImages.mythBusters).existsSync()); expect(true, File(AssetImages.virus).existsSync()); expect(true, File(AssetImages.prevention).existsSync()); expect(true, File(AssetImages.genericError).existsSync()); expect(true, File(AssetImages.latestNumbers).existsSync()); + expect(true, File(AssetImages.symptoms).existsSync()); }); }