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