From 6f9edc0dc9600eb0f883961d308170891d78aeca Mon Sep 17 00:00:00 2001 From: Qizot Date: Sun, 22 Mar 2020 14:16:12 +0100 Subject: [PATCH] Added save feature for countries additionaly fixed some code problems --- .../coronavirus_visualizer/lib/src/app.dart | 17 ++- .../lib/src/bloc/saved_countries/bloc.dart | 3 + .../saved_countries/saved_countries_bloc.dart | 97 +++++++++++++ .../saved_countries_event.dart | 47 +++++++ .../saved_countries_state.dart | 61 +++++++++ .../src/bloc/timeline_bloc/timeline_bloc.dart | 8 +- .../bloc/timeline_bloc/timeline_event.dart | 14 +- .../bloc/timeline_bloc/timeline_state.dart | 27 +--- .../lib/src/services/countries.dart | 23 +++- .../country_timeline/all_countries_list.dart | 34 +++++ .../country_picker_screen.dart | 127 +++++++++++------- .../ui/country_timeline/country_slidable.dart | 53 ++++++++ .../country_timeline_screen.dart | 1 - .../saved_countries_list.dart | 57 ++++++++ .../global_timeline_sceen.dart | 5 - .../pick_timeline/pick_timeline_screen.dart | 1 - .../lib/src/ui/splash/error_screen.dart | 2 +- .../lib/src/ui/splash/loading_screen.dart | 5 - .../total_timeline_chart_card.dart | 2 +- .../total_timeline/total_timeline_stats.dart | 11 +- .../lib/src/ui/utils/message_snackbar.dart | 21 +++ frontend/coronavirus_visualizer/pubspec.lock | 14 ++ frontend/coronavirus_visualizer/pubspec.yaml | 2 + 23 files changed, 519 insertions(+), 113 deletions(-) create mode 100644 frontend/coronavirus_visualizer/lib/src/bloc/saved_countries/bloc.dart create mode 100644 frontend/coronavirus_visualizer/lib/src/bloc/saved_countries/saved_countries_bloc.dart create mode 100644 frontend/coronavirus_visualizer/lib/src/bloc/saved_countries/saved_countries_event.dart create mode 100644 frontend/coronavirus_visualizer/lib/src/bloc/saved_countries/saved_countries_state.dart create mode 100644 frontend/coronavirus_visualizer/lib/src/ui/country_timeline/all_countries_list.dart create mode 100644 frontend/coronavirus_visualizer/lib/src/ui/country_timeline/country_slidable.dart create mode 100644 frontend/coronavirus_visualizer/lib/src/ui/country_timeline/saved_countries_list.dart create mode 100644 frontend/coronavirus_visualizer/lib/src/ui/utils/message_snackbar.dart diff --git a/frontend/coronavirus_visualizer/lib/src/app.dart b/frontend/coronavirus_visualizer/lib/src/app.dart index 9304185..3344038 100644 --- a/frontend/coronavirus_visualizer/lib/src/app.dart +++ b/frontend/coronavirus_visualizer/lib/src/app.dart @@ -1,5 +1,6 @@ +import 'package:coronavirus_visualizer/src/bloc/saved_countries/bloc.dart'; import 'package:coronavirus_visualizer/src/bloc/timeline_bloc/bloc.dart'; import 'package:coronavirus_visualizer/src/ui/country_timeline/country_picker_screen.dart'; import 'package:coronavirus_visualizer/src/ui/country_timeline/country_timeline_screen.dart'; @@ -16,10 +17,18 @@ class App extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocProvider( - create: (context) => TimelineBloc() - ..add(TimelineInitialize()), - child: MaterialApp( + return MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => TimelineBloc() + ..add(TimelineInitialize()), + ), + BlocProvider( + create: (context) => SavedCountriesBloc() + ..add(SavedCountriesFetch()), + ) + ], + child:MaterialApp( theme: ThemeData( brightness: Brightness.dark, ), diff --git a/frontend/coronavirus_visualizer/lib/src/bloc/saved_countries/bloc.dart b/frontend/coronavirus_visualizer/lib/src/bloc/saved_countries/bloc.dart new file mode 100644 index 0000000..f554e28 --- /dev/null +++ b/frontend/coronavirus_visualizer/lib/src/bloc/saved_countries/bloc.dart @@ -0,0 +1,3 @@ +export 'saved_countries_bloc.dart'; +export 'saved_countries_event.dart'; +export 'saved_countries_state.dart'; \ No newline at end of file diff --git a/frontend/coronavirus_visualizer/lib/src/bloc/saved_countries/saved_countries_bloc.dart b/frontend/coronavirus_visualizer/lib/src/bloc/saved_countries/saved_countries_bloc.dart new file mode 100644 index 0000000..73c31b3 --- /dev/null +++ b/frontend/coronavirus_visualizer/lib/src/bloc/saved_countries/saved_countries_bloc.dart @@ -0,0 +1,97 @@ +import 'dart:async'; +import 'package:coronavirus_visualizer/src/services/countries.dart'; +import 'package:localstorage/localstorage.dart'; +import 'package:bloc/bloc.dart'; +import 'package:coronavirus_visualizer/src/bloc/saved_countries/bloc.dart'; + +class SavedCountriesBloc extends Bloc { + + final _storage = LocalStorage('coronavirus'); + int _fetched = 0; + int _errors = 0; + int _successes = 0; + + + @override + SavedCountriesState get initialState => SavedCountriesUninitialized(); + + @override + Stream mapEventToState(SavedCountriesEvent event) async* { + if (event is SavedCountriesAddCountry) { + yield* _mapAddCountry(event); + } + if (event is SavedCountriesRemoveCountry) { + yield* _mapRemoveCountry(event); + } + if (event is SavedCountriesFetch) { + yield* _mapFetch(event); + } + } + + List _getCountries() { + return (_storage.getItem('countries') as List) + ?.map((country) => Country.fromJson(country)) + ?.toList() ?? []; + } + + Stream _mapAddCountry(SavedCountriesAddCountry event) async* { + try { + final countries = _getCountries(); + + if (countries.indexOf(event.country, 0) != -1) { + _errors += 1; + yield SavedCountriesError(error: "Country has already been saved!", version: _errors); + } else { + countries.add(event.country); + await _storage.setItem('countries', + countries + .map((c) => c.toJson()) + .toList() + ); + _successes += 1; + yield SavedCountriesSuccess(message: "Country has been saved!", version: _successes); + add(SavedCountriesFetch()); + } + } catch (error) { + _errors += 1; + yield SavedCountriesError(error: "Failed to save country!", version: _errors); + } + } + + Stream _mapRemoveCountry(SavedCountriesRemoveCountry event) async* { + try { + final countries = _getCountries(); + + if (countries.indexOf(event.country, 0) == -1) { + + _errors += 1; + yield SavedCountriesError(error: "Country was not previously saved!", version: _errors); + } else { + countries.remove(event.country); + _storage.setItem('countries', countries.map((c) => c.toJson()).toList()); + + _successes += 1; + yield SavedCountriesSuccess(message: "Country has been removed!", version: _successes); + add(SavedCountriesFetch()); + } + + } catch (error) { + + _errors += 1; + yield SavedCountriesError(error: "Failed to remove country!", version: _errors); + } + } + + Stream _mapFetch(SavedCountriesFetch event) async* { + try { + final countries = _getCountries(); + + _fetched += 1; + yield SavedCountriesFetched(countries: countries, version: _fetched); + } catch (error) { + + _errors += 1; + yield SavedCountriesError(error: "Failed to fetch saved countries!", version: _errors); + } + } +} diff --git a/frontend/coronavirus_visualizer/lib/src/bloc/saved_countries/saved_countries_event.dart b/frontend/coronavirus_visualizer/lib/src/bloc/saved_countries/saved_countries_event.dart new file mode 100644 index 0000000..5519592 --- /dev/null +++ b/frontend/coronavirus_visualizer/lib/src/bloc/saved_countries/saved_countries_event.dart @@ -0,0 +1,47 @@ +import 'package:coronavirus_visualizer/src/services/countries.dart'; +import 'package:equatable/equatable.dart'; +import 'package:meta/meta.dart'; + + +abstract class SavedCountriesEvent extends Equatable { + const SavedCountriesEvent(); + + @override + List get props => []; +} + +class SavedCountriesAddCountry extends SavedCountriesEvent { + final Country country; + + SavedCountriesAddCountry({@required this.country}); + + @override + List get props => [country]; + + @override + String toString() { + return 'SavedCountriesAddCountry { country: $country }'; + } +} + +class SavedCountriesRemoveCountry extends SavedCountriesEvent { + final Country country; + + SavedCountriesRemoveCountry({@required this.country}); + + @override + List get props => [country]; + + @override + String toString() { + return 'SavedCountriesRemoveCountry { country: $country }'; + } +} + +class SavedCountriesFetch extends SavedCountriesEvent { + @override + String toString() { + return 'SavedCountriesFetch { }'; + } +} + diff --git a/frontend/coronavirus_visualizer/lib/src/bloc/saved_countries/saved_countries_state.dart b/frontend/coronavirus_visualizer/lib/src/bloc/saved_countries/saved_countries_state.dart new file mode 100644 index 0000000..b61ac06 --- /dev/null +++ b/frontend/coronavirus_visualizer/lib/src/bloc/saved_countries/saved_countries_state.dart @@ -0,0 +1,61 @@ +import 'package:coronavirus_visualizer/src/services/countries.dart'; +import 'package:equatable/equatable.dart'; + + +abstract class SavedCountriesState extends Equatable { + const SavedCountriesState(); + + @override + List get props => []; +} + +class SavedCountriesUninitialized extends SavedCountriesState { + @override + String toString() => 'SavedCountriesUninitialized { }'; + +} + +class SavedCountriesError extends SavedCountriesState { + final String error; + final int version; + + SavedCountriesError({this.error, this.version}); + + @override + List get props => [version, error]; + + @override + String toString() => 'SavedCountriesStateError { version: $version, error: $error }'; +} + +class SavedCountriesSuccess extends SavedCountriesState { + final String message; + final int version; + + SavedCountriesSuccess({this.message, this.version}); + + @override + List get props => [version, message]; + + @override + String toString() => 'SavedCountriesSuccess { version: $version, message: $message }'; +} + +class SavedCountriesLoading extends SavedCountriesState { + @override + String toString() => 'SavedCountriesStateLoading { }'; +} + +class SavedCountriesFetched extends SavedCountriesState { + final List countries; + final int version; + + SavedCountriesFetched({this.countries, this.version}); + + @override + List get props => [version, countries]; + + @override + String toString() => 'SavedCountriesFetched { version: $version, countries: $countries }'; +} + diff --git a/frontend/coronavirus_visualizer/lib/src/bloc/timeline_bloc/timeline_bloc.dart b/frontend/coronavirus_visualizer/lib/src/bloc/timeline_bloc/timeline_bloc.dart index f8e8832..ba6d292 100644 --- a/frontend/coronavirus_visualizer/lib/src/bloc/timeline_bloc/timeline_bloc.dart +++ b/frontend/coronavirus_visualizer/lib/src/bloc/timeline_bloc/timeline_bloc.dart @@ -26,9 +26,9 @@ class TimelineBloc extends Bloc { final globalTimeline = await _repository.getGlobalTimeline(); await showLoadingScreen; yield TimelineFetchedGlobalTimeline(globalTimeline: globalTimeline); - } on DioError catch (e) { + } on DioError { yield TimelineError(error: "Error has occured while communicating with backend!"); - } catch (e) { + } on Exception { yield TimelineError(error: "Unknown error has occured, try again later!"); } } @@ -40,9 +40,9 @@ class TimelineBloc extends Bloc { final countryTimeline = await _repository.getCountryTimeline(event.countryCode); await showLoadingScreen; yield TimelineFetchedCountryTimeline(countryTimeline: countryTimeline); - } on DioError catch (e) { + } on DioError { yield TimelineError(error: "Error has occured while communicating with backend!"); - } catch (e) { + } on Exception { yield TimelineError(error: "Unknown error has occured, try again later!"); } diff --git a/frontend/coronavirus_visualizer/lib/src/bloc/timeline_bloc/timeline_event.dart b/frontend/coronavirus_visualizer/lib/src/bloc/timeline_bloc/timeline_event.dart index 2bd30ca..72b1b62 100644 --- a/frontend/coronavirus_visualizer/lib/src/bloc/timeline_bloc/timeline_event.dart +++ b/frontend/coronavirus_visualizer/lib/src/bloc/timeline_bloc/timeline_event.dart @@ -10,9 +10,7 @@ abstract class TimelineEvent extends Equatable { class TimelineInitialize extends TimelineEvent { @override - String toString() { - return 'TimelineInitialize { }'; - } + String toString() => 'TimelineInitialize { }'; } class TimelineFetchTimeline extends TimelineEvent { @@ -25,9 +23,7 @@ class TimelineFetchTimeline extends TimelineEvent { List get props => [dateFrom, dateTo]; @override - String toString() { - return 'TimelineFetchTimeline { dateFrom: $dateFrom, dateTo: $dateTo }'; - } + String toString() => 'TimelineFetchTimeline { dateFrom: $dateFrom, dateTo: $dateTo }'; } class TimelineFetchCountryTimeline extends TimelineEvent { @@ -41,7 +37,5 @@ class TimelineFetchCountryTimeline extends TimelineEvent { List get props => [countryCode, dateFrom, dateTo]; @override - String toString() { - return 'TimelineFetchCountryTimeline { countryCode: $countryCode, dateFrom: $dateFrom, dateTo: $dateTo }'; - } -} \ No newline at end of file + String toString() => 'TimelineFetchCountryTimeline { countryCode: $countryCode, dateFrom: $dateFrom, dateTo: $dateTo }'; +} diff --git a/frontend/coronavirus_visualizer/lib/src/bloc/timeline_bloc/timeline_state.dart b/frontend/coronavirus_visualizer/lib/src/bloc/timeline_bloc/timeline_state.dart index bee8911..f929918 100644 --- a/frontend/coronavirus_visualizer/lib/src/bloc/timeline_bloc/timeline_state.dart +++ b/frontend/coronavirus_visualizer/lib/src/bloc/timeline_bloc/timeline_state.dart @@ -12,23 +12,17 @@ abstract class TimelineState extends Equatable { class TimelineUninitialized extends TimelineState { @override - String toString() { - return 'TImelineUninitialized { }'; - } + String toString() => 'TImelineUninitialized { }'; } class TimelineLoading extends TimelineState { @override - String toString() { - return 'TimelineLoading { }'; - } + String toString() => 'TimelineLoading { }'; } class TimelineInitialized extends TimelineState { @override - String toString() { - return 'TimelineInitialized { }'; - } + String toString() => 'TimelineInitialized { }'; } class TimelineError extends TimelineState { @@ -40,9 +34,7 @@ class TimelineError extends TimelineState { List get props => [error]; @override - String toString() { - return 'TimelineError { error: $error }'; - } + String toString() => 'TimelineError { error: $error }'; } class TimelineFetchedGlobalTimeline extends TimelineState { @@ -54,9 +46,7 @@ class TimelineFetchedGlobalTimeline extends TimelineState { List get props => [globalTimeline]; @override - String toString() { - return 'TimelineFetchedGlobalTimeline { globalTimeline: $globalTimeline }'; - } + String toString() => 'TimelineFetchedGlobalTimeline { globalTimeline: $globalTimeline }'; } class TimelineFetchedCountryTimeline extends TimelineState { @@ -68,8 +58,5 @@ class TimelineFetchedCountryTimeline extends TimelineState { List get props => [countryTimeline]; @override - String toString() { - return 'TimelineFetchedCountryTimeline { countryTimeline: $countryTimeline }'; - } - -} \ No newline at end of file + String toString() => 'TimelineFetchedCountryTimeline { countryTimeline: $countryTimeline }'; +} diff --git a/frontend/coronavirus_visualizer/lib/src/services/countries.dart b/frontend/coronavirus_visualizer/lib/src/services/countries.dart index 7b632e5..6f6a388 100644 --- a/frontend/coronavirus_visualizer/lib/src/services/countries.dart +++ b/frontend/coronavirus_visualizer/lib/src/services/countries.dart @@ -1,11 +1,28 @@ - - +import 'package:equatable/equatable.dart'; import 'package:meta/meta.dart'; -class Country { +class Country extends Equatable { final String name; final String code; + + @override + List get props => [name, code]; + Country({@required this.name, @required this.code}); + + Country.fromJson(Map json): name = json['name'], code = json['code']; + + Map toJson() { + final Map data = new Map(); + data['name'] = this.name; + data['code'] = this.code; + return data; + } + + @override + String toString() { + return 'Country { name: $name, code: $code }'; + } } class CountriesService { diff --git a/frontend/coronavirus_visualizer/lib/src/ui/country_timeline/all_countries_list.dart b/frontend/coronavirus_visualizer/lib/src/ui/country_timeline/all_countries_list.dart new file mode 100644 index 0000000..fa4d1e6 --- /dev/null +++ b/frontend/coronavirus_visualizer/lib/src/ui/country_timeline/all_countries_list.dart @@ -0,0 +1,34 @@ + + +import 'package:coronavirus_visualizer/src/bloc/saved_countries/bloc.dart'; +import 'package:coronavirus_visualizer/src/services/countries.dart'; +import 'package:coronavirus_visualizer/src/ui/country_timeline/country_slidable.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_slidable/flutter_slidable.dart'; + +class AllCountriesList extends StatelessWidget { + final countries = CountriesService.instance.countries; + + @override + Widget build(BuildContext context) { + return Scrollbar( + child: ListView.builder( + itemCount: countries.length, + itemBuilder: (context, idx) { + return CountrySlidable( + country: countries[idx], + action: IconSlideAction( + caption: "Save", + color: Colors.green, + icon: Icons.save, + onTap: () => BlocProvider.of(context) + .add(SavedCountriesAddCountry(country: countries[idx])) + ) + ); + }, + ), + ); + } +} \ No newline at end of file diff --git a/frontend/coronavirus_visualizer/lib/src/ui/country_timeline/country_picker_screen.dart b/frontend/coronavirus_visualizer/lib/src/ui/country_timeline/country_picker_screen.dart index 6fa9e40..a017f86 100644 --- a/frontend/coronavirus_visualizer/lib/src/ui/country_timeline/country_picker_screen.dart +++ b/frontend/coronavirus_visualizer/lib/src/ui/country_timeline/country_picker_screen.dart @@ -1,73 +1,96 @@ import 'package:coronavirus_visualizer/src/bloc/timeline_bloc/bloc.dart'; -import 'package:coronavirus_visualizer/src/bloc/timeline_bloc/timeline_event.dart'; +import 'package:coronavirus_visualizer/src/bloc/saved_countries/bloc.dart'; import 'package:coronavirus_visualizer/src/services/countries.dart'; +import 'package:coronavirus_visualizer/src/ui/country_timeline/all_countries_list.dart'; +import 'package:coronavirus_visualizer/src/ui/country_timeline/country_slidable.dart'; +import 'package:coronavirus_visualizer/src/ui/country_timeline/saved_countries_list.dart'; +import 'package:coronavirus_visualizer/src/ui/utils/message_snackbar.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:google_fonts/google_fonts.dart'; +import 'package:flutter_slidable/flutter_slidable.dart'; class CountryPickerScreen extends StatelessWidget { - final countries = CountriesService.instance.countries; + + final _tabBarStyle = TextStyle(fontFamily: "Baloo", fontSize: 18, fontWeight: FontWeight.w500); + @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text("Pick country", style: TextStyle(fontFamily: "Baloo", fontSize: 25, fontWeight: FontWeight.w600)), - centerTitle: true, - ), - body: Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - stops: [ - 0.1, - 0.4, - 0.6, - 0.9 - ], - colors: [ - Color(0xFF000000), - Color(0xFF090909), - Color(0xFF131313), - Color(0xFF1A1A1A) + return DefaultTabController( + length: 2, + child: Scaffold( + appBar: AppBar( + title: Text("Pick country", style: TextStyle(fontFamily: "Baloo", fontSize: 25, fontWeight: FontWeight.w600)), + centerTitle: true, + bottom: TabBar( + indicatorColor: Colors.white, + tabs:[ + Tab(child: Text("All", style: _tabBarStyle)), + Tab(child: Text("Saved", style: _tabBarStyle)) ] ) ), - child: Scrollbar( - child: ListView.builder( - itemCount: countries.length, - itemBuilder: (context, idx) { - return ListTile( - title: Row( - children: [ - Text(countries[idx].name, style: TextStyle(fontFamily: "Baloo", fontSize: 15, color: Colors.white, fontWeight: FontWeight.w500)), - SizedBox(width: 6), - Text(_countryEmoji(countries[idx].code), style: TextStyle(fontSize: 20)) - ], + body: BlocListener( + listener: (context, state) { + if (state is SavedCountriesError) { + showErrorSnackbar(context, message: state.error); + } + if (state is SavedCountriesSuccess) { + showSuccessSnackbar(context, message: state.message); + } + }, + child: TabBarView( + children: [ + Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + stops: [ + 0.1, + 0.4, + 0.6, + 0.9 + ], + colors: [ + Color(0xFF000000), + Color(0xFF090909), + Color(0xFF131313), + Color(0xFF1A1A1A) + ] + ) ), - onTap: () { - BlocProvider.of(context).add(TimelineFetchCountryTimeline(countryCode: countries[idx].code)); - Navigator.of(context).pushReplacementNamed('/country-timeline'); - }, - ); - }, + child: AllCountriesList() + ), + Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + stops: [ + 0.1, + 0.4, + 0.6, + 0.9 + ], + colors: [ + Color(0xFF000000), + Color(0xFF090909), + Color(0xFF131313), + Color(0xFF1A1A1A) + ] + ) + ), + child: SavedCountriesList() + ), + ], ), - ), - ) + ) + ), ); } - String _countryEmoji(String countryCode) { - int flagOffset = 0x1F1E6; - int asciiOffset = 0x41; - - int firstChar = countryCode.codeUnitAt(0) - asciiOffset + flagOffset; - int secondChar = countryCode.codeUnitAt(1) - asciiOffset + flagOffset; - - return String.fromCharCode(firstChar) + String.fromCharCode(secondChar); - } } \ No newline at end of file diff --git a/frontend/coronavirus_visualizer/lib/src/ui/country_timeline/country_slidable.dart b/frontend/coronavirus_visualizer/lib/src/ui/country_timeline/country_slidable.dart new file mode 100644 index 0000000..88cc433 --- /dev/null +++ b/frontend/coronavirus_visualizer/lib/src/ui/country_timeline/country_slidable.dart @@ -0,0 +1,53 @@ + + +import 'package:coronavirus_visualizer/src/bloc/saved_countries/bloc.dart'; +import 'package:coronavirus_visualizer/src/bloc/timeline_bloc/bloc.dart'; +import 'package:coronavirus_visualizer/src/services/countries.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_slidable/flutter_slidable.dart'; +import 'package:meta/meta.dart'; + +class CountrySlidable extends StatelessWidget { + final Country country; + final IconSlideAction action; + + CountrySlidable({@required this.country, this.action}); + + + String _countryEmoji(String countryCode) { + int flagOffset = 0x1F1E6; + int asciiOffset = 0x41; + + int firstChar = countryCode.codeUnitAt(0) - asciiOffset + flagOffset; + int secondChar = countryCode.codeUnitAt(1) - asciiOffset + flagOffset; + + return String.fromCharCode(firstChar) + String.fromCharCode(secondChar); + } + + @override + Widget build(BuildContext context) { + return Slidable( + actionPane: SlidableDrawerActionPane(), + actions: [ + action + ], + child: ListTile( + title: Row( + children: [ + Text(country.name, style: TextStyle(fontFamily: "Baloo", fontSize: 15, color: Colors.white, fontWeight: FontWeight.w500)), + SizedBox(width: 6), + Text(_countryEmoji(country.code), style: TextStyle(fontSize: 20)) + ], + ), + onTap: () { + BlocProvider.of(context).add(TimelineFetchCountryTimeline(countryCode: country.code)); + Navigator.of(context).pushNamed('/country-timeline'); + }, + ), + ); + } + + +} \ No newline at end of file diff --git a/frontend/coronavirus_visualizer/lib/src/ui/country_timeline/country_timeline_screen.dart b/frontend/coronavirus_visualizer/lib/src/ui/country_timeline/country_timeline_screen.dart index 045d6d9..eb0ca0b 100644 --- a/frontend/coronavirus_visualizer/lib/src/ui/country_timeline/country_timeline_screen.dart +++ b/frontend/coronavirus_visualizer/lib/src/ui/country_timeline/country_timeline_screen.dart @@ -11,7 +11,6 @@ import 'package:coronavirus_visualizer/src/ui/total_timeline/total_timeline_stat import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:google_fonts/google_fonts.dart'; class CountryTimelineScreen extends StatefulWidget { diff --git a/frontend/coronavirus_visualizer/lib/src/ui/country_timeline/saved_countries_list.dart b/frontend/coronavirus_visualizer/lib/src/ui/country_timeline/saved_countries_list.dart new file mode 100644 index 0000000..c06a906 --- /dev/null +++ b/frontend/coronavirus_visualizer/lib/src/ui/country_timeline/saved_countries_list.dart @@ -0,0 +1,57 @@ + + +import 'package:coronavirus_visualizer/src/bloc/saved_countries/bloc.dart'; +import 'package:coronavirus_visualizer/src/services/countries.dart'; +import 'package:coronavirus_visualizer/src/ui/country_timeline/country_slidable.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_slidable/flutter_slidable.dart'; + +class SavedCountriesList extends StatefulWidget { + + @override + State createState() => _SavedCountriesListState(); +} + +class _SavedCountriesListState extends State { + + List _countries = []; + + @override + void initState() { + BlocProvider.of(context).add(SavedCountriesFetch()); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return BlocListener( + listener: (_, state) { + if (state is SavedCountriesFetched) { + setState(() { + _countries = state.countries ?? []; + print(_countries.length); + }); + } + }, + child: Scrollbar( + child: ListView.builder( + itemCount: _countries.length, + itemBuilder: (context, idx) { + return CountrySlidable( + country: _countries[idx], + action: IconSlideAction( + caption: "Remove", + color: Colors.red, + icon: Icons.delete, + onTap: () => BlocProvider.of(context) + .add(SavedCountriesRemoveCountry(country: _countries[idx])) + ) + ); + }, + ), + ) + ); + } +} \ No newline at end of file diff --git a/frontend/coronavirus_visualizer/lib/src/ui/gloabl_timeline/global_timeline_sceen.dart b/frontend/coronavirus_visualizer/lib/src/ui/gloabl_timeline/global_timeline_sceen.dart index b768b32..c5d6615 100644 --- a/frontend/coronavirus_visualizer/lib/src/ui/gloabl_timeline/global_timeline_sceen.dart +++ b/frontend/coronavirus_visualizer/lib/src/ui/gloabl_timeline/global_timeline_sceen.dart @@ -3,8 +3,6 @@ import 'package:coronavirus_visualizer/src/bloc/timeline_bloc/bloc.dart'; import 'package:coronavirus_visualizer/src/models/global_timeline.dart'; -import 'package:coronavirus_visualizer/src/ui/charts/total_timeline_chart.dart'; -import 'package:coronavirus_visualizer/src/ui/charts/total_timeline_circle_chart.dart'; import 'package:coronavirus_visualizer/src/ui/splash/error_screen.dart'; import 'package:coronavirus_visualizer/src/ui/splash/loading_screen.dart'; import 'package:coronavirus_visualizer/src/ui/total_timeline/total_timeline_chart_card.dart'; @@ -13,7 +11,6 @@ import 'package:coronavirus_visualizer/src/ui/total_timeline/total_timeline_stat import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:google_fonts/google_fonts.dart'; class GlobalTimelineScreen extends StatefulWidget { @@ -23,8 +20,6 @@ class GlobalTimelineScreen extends StatefulWidget { class _GlobalTimelineScreenState extends State { - final _scaffoldKey = GlobalKey(); - GlobalTimeline _globalTimeline; String appTitle; @override diff --git a/frontend/coronavirus_visualizer/lib/src/ui/pick_timeline/pick_timeline_screen.dart b/frontend/coronavirus_visualizer/lib/src/ui/pick_timeline/pick_timeline_screen.dart index 033650b..ff02456 100644 --- a/frontend/coronavirus_visualizer/lib/src/ui/pick_timeline/pick_timeline_screen.dart +++ b/frontend/coronavirus_visualizer/lib/src/ui/pick_timeline/pick_timeline_screen.dart @@ -4,7 +4,6 @@ import 'package:coronavirus_visualizer/src/bloc/timeline_bloc/bloc.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:google_fonts/google_fonts.dart'; class PickTimelineScreen extends StatelessWidget { @override diff --git a/frontend/coronavirus_visualizer/lib/src/ui/splash/error_screen.dart b/frontend/coronavirus_visualizer/lib/src/ui/splash/error_screen.dart index e7ec619..8b42c33 100644 --- a/frontend/coronavirus_visualizer/lib/src/ui/splash/error_screen.dart +++ b/frontend/coronavirus_visualizer/lib/src/ui/splash/error_screen.dart @@ -30,7 +30,7 @@ class _ErrorScreenState extends State with TickerProviderStateMixin _color = ColorTween(begin: Colors.white, end: Colors.red).animate(_sizeCurve); final _rotationCurve = CurvedAnimation(curve: Curves.bounceInOut, parent: _shakeController); - _roatation = Tween(begin: -math.pi/12, end: math.pi/12).animate(_shakeController); + _roatation = Tween(begin: -math.pi/12, end: math.pi/12).animate(_rotationCurve); _resizeController.forward(); _shakeController.forward(); diff --git a/frontend/coronavirus_visualizer/lib/src/ui/splash/loading_screen.dart b/frontend/coronavirus_visualizer/lib/src/ui/splash/loading_screen.dart index 7b8b11d..32105f6 100644 --- a/frontend/coronavirus_visualizer/lib/src/ui/splash/loading_screen.dart +++ b/frontend/coronavirus_visualizer/lib/src/ui/splash/loading_screen.dart @@ -1,11 +1,6 @@ - - - - import 'package:coronavirus_visualizer/src/ui/splash/animated_virus.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:google_fonts/google_fonts.dart'; class LoadingScreen extends StatelessWidget { final String title; diff --git a/frontend/coronavirus_visualizer/lib/src/ui/total_timeline/total_timeline_chart_card.dart b/frontend/coronavirus_visualizer/lib/src/ui/total_timeline/total_timeline_chart_card.dart index b70a94d..993973d 100644 --- a/frontend/coronavirus_visualizer/lib/src/ui/total_timeline/total_timeline_chart_card.dart +++ b/frontend/coronavirus_visualizer/lib/src/ui/total_timeline/total_timeline_chart_card.dart @@ -48,6 +48,6 @@ class TotalTimelineChartCard extends StatelessWidget { ) ], ) - );; + ); } } \ No newline at end of file diff --git a/frontend/coronavirus_visualizer/lib/src/ui/total_timeline/total_timeline_stats.dart b/frontend/coronavirus_visualizer/lib/src/ui/total_timeline/total_timeline_stats.dart index 7890132..e1f1395 100644 --- a/frontend/coronavirus_visualizer/lib/src/ui/total_timeline/total_timeline_stats.dart +++ b/frontend/coronavirus_visualizer/lib/src/ui/total_timeline/total_timeline_stats.dart @@ -3,7 +3,6 @@ import 'package:coronavirus_visualizer/src/models/timeline_item.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:google_fonts/google_fonts.dart'; import 'package:meta/meta.dart'; class TotalTimelineStats extends StatelessWidget { @@ -51,11 +50,11 @@ class TotalTimelineStats extends StatelessWidget { crossAxisCount: 2, children: [ // new cases and new deaths are only submitted a day before - _info("Total cases", timeline.first.totalCases), - _info("New cases", timeline[1].newCases), - _info("Total deaths", timeline.first.totalDeaths), - _info("New deaths", timeline[1].newDeaths), - _info("Total recovered", timeline.first.totalRecovered), + _info("Total cases", timeline.first.totalCases ?? 0), + _info("New cases", timeline[1].newCases ?? 0), + _info("Total deaths", timeline.first.totalDeaths ?? 0), + _info("New deaths", timeline[1].newDeaths ?? 0), + _info("Total recovered", timeline.first.totalRecovered ?? 0), ], ) diff --git a/frontend/coronavirus_visualizer/lib/src/ui/utils/message_snackbar.dart b/frontend/coronavirus_visualizer/lib/src/ui/utils/message_snackbar.dart new file mode 100644 index 0000000..75d0df4 --- /dev/null +++ b/frontend/coronavirus_visualizer/lib/src/ui/utils/message_snackbar.dart @@ -0,0 +1,21 @@ + + + +import 'package:flutter/material.dart'; + +void showDefaultSnackbar(context, {String message, Duration duration = const Duration(seconds: 2), Color backgroundColor, TextStyle textStyle}) { + Scaffold.of(context).hideCurrentSnackBar(); + Scaffold.of(context).showSnackBar(SnackBar( + backgroundColor: backgroundColor, + content: Text(message, style: textStyle), + duration: duration, + )); +} + +void showSuccessSnackbar(context, {String message, Duration duration = const Duration(seconds: 2)}) { + showDefaultSnackbar(context, message: message, duration: duration, backgroundColor: Colors.green, textStyle: TextStyle(color: Colors.white)); +} + +void showErrorSnackbar(context, {String message, Duration duration = const Duration(seconds: 2)}) { + showDefaultSnackbar(context, message: message, duration: duration, backgroundColor: Colors.red, textStyle: TextStyle(color: Colors.white)); +} \ No newline at end of file diff --git a/frontend/coronavirus_visualizer/pubspec.lock b/frontend/coronavirus_visualizer/pubspec.lock index b7d9a18..edc8ee7 100644 --- a/frontend/coronavirus_visualizer/pubspec.lock +++ b/frontend/coronavirus_visualizer/pubspec.lock @@ -132,6 +132,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.9" + flutter_slidable: + dependency: "direct main" + description: + name: flutter_slidable + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.4" flutter_test: dependency: "direct dev" description: flutter @@ -172,6 +179,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.16.1" + localstorage: + dependency: "direct main" + description: + name: localstorage + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1+4" logging: dependency: transitive description: diff --git a/frontend/coronavirus_visualizer/pubspec.yaml b/frontend/coronavirus_visualizer/pubspec.yaml index 8e4c2d0..3a87fa9 100644 --- a/frontend/coronavirus_visualizer/pubspec.yaml +++ b/frontend/coronavirus_visualizer/pubspec.yaml @@ -19,6 +19,8 @@ dependencies: dio: ^3.0.8 google_fonts: ^0.3.10 charts_flutter: ^0.9.0 + localstorage: ^3.0.1+4 + flutter_slidable: "^0.5.4" dev_dependencies: flutter_test: